八大排序算法--快速排序及其优化

快速排序定义

快速排序的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
在这里插入图片描述

代码实现

在这里插入图片描述
在上图中, l表示最左边的脚标, j表示小于等于v的部分的脚标的最大位置, i表示尚未进行比较的部分的第一个元素位置,然后i不断后移,完成整个数组的比较。在比较过程中, 如果i这个位置的元素>v, 则这个元素归于紫色部分,i直接+1, 移动到下一个位置即可,如果i这个元素小于等于v, 则需要把i这个元素移动到前面橙色部分, 所以把i与 j+1(这个位置为紫色)两个元素交换,然后i移动到i+1的位置,继续进行比较。代码如下:

/**
 * 定义好脚标取值范围:
 * 	对arr[l...r]部分进行快速排序,取值区间前闭后闭
 */
public static void quickSort(int[] arr, int l, int r) {
	if(l >= r) {
		return;
	}
	//排序后,位置p左边的数据都小于p,右边的数据都大于p
	int p = parttion(arr, l, r);
	quickSort(arr, l, p-1);//通过递归继续对左边的数据排序
	quickSort(arr, p+1, r);//通过递归继续对右边的数据排序
}

private static int parttion(int[] arr, int l, int r) {
	int v = arr[l];//                                                                                                                                                                                                                                                                                           
	int j = l;
	for(int i=l; i<=r; i++) {
		if(arr[i] < v) {
			//如果当前数比v小,则把当前数交换到 小于v的集合中
			swap(arr, i, j+1);
			j++;
		}
	}
	swap(arr, l, j);//把这个中间值 真正的移动到中间位置吗,即在排序完成后, 把v和橙色部分最后一个元素进行交换, 这样左边的都是小于v,右边都大于v
	return j;//返回这个中间值的位置
}

private static void swap(int[] arr, int i, int j) {
	int temp = arr[j];
	arr[j] = arr[i];
	arr[i] = temp;
}

快速排序的优化一

高级排序在最后阶段较少数据排序是,都可以使用插入排序进行优化,因为越是高级的排序,实现越是复杂,通常来说在数据较大的效果越是明显,在数据较少时使用插入排序这一种简单排序更加快速。

/**
 * 定义好脚标取值范围:
 * 	对arr[l...r]部分进行快速排序,取值区间前闭后闭
 */
public static void quickSort(int[] arr, int l, int r) {
	if(r - l <= 15) {
		insertionSort(arr, l, r);
		return;
	}
	//排序后,位置p左边的数据都小于p,右边的数据都大于p
	int p = parttion(arr, l, r);
	quickSort(arr, l, p-1);//通过递归继续对左边的数据排序
	quickSort(arr, p+1, r);//通过递归继续对右边的数据排序
}

// 对arr[l...r]范围的数组进行插入排序
private static void insertionSort(int[] arr, int l, int r){

    for( int i = l+1 ; i <= r ; i ++ ) {

        int e = arr[i];
        int j;
        for (j = i; j > l && arr[j-1] > e; j--)
            arr[j] = arr[j-1];
        arr[j] = e;
    }

    return;
}

快速排序的优化二

每次选择的最左边的数据作为比较的数,如果每次这个数都刚好是整个数据中的最小数,那么就不能起到将整个数据一分为二的效果,特别是在数据整体趋向于有序时,更有这种可能:

在这里插入图片描述

所以,我们每次随机选择一个数,作为用来比较的数,这样就尽量的避免了上述情况, 代码增加了一行,修改如下:

private static int parttion(int[] arr, int l, int r) {
	//随机选择一个数,然后交换到最前面,作为比较的数
	//这样改的最少,可以直接在原有代码上优化
	swap(arr, l, Math.round(r-l) + l);
	
	int v = arr[l];
	int j = l;
	for(int i=l; i<=r; i++) {
		if(arr[i] < v) {
			//如果当前数比v小,则把当前数交换到 小于v的集合中
			swap(arr, i, j+1);
			j++;
		}
	}
	swap(arr, l, j);
	return j;//返回这个中间值的位置
}

快速排序的优化三–双路快速排序

上述代码中,都没有考虑一种情况,就是如果数据中有大量重复的数字,那么就这些数字就会集中在在小于等于v 或者 大于等于v的这一端中,从而导致数据没有均匀的分开,有可能退化为上面 优化二所描述的情况。

此时,我们应该尽量把数据都分散开来,尽量均匀的划分为两部分,如下图所示:
在这里插入图片描述

此时的代码逻辑调整为:用两个指针i、j分别指向数据的头尾,当前数e小于v时,就存放到前面部分,当数据e大于v时,就存放到后面部分。在代码中只有小于号< 或 大于号 >, 但是实际上,当不满足 <时, 数据是大于等于v的 就移动到了后半部分, 当不满足 > 时, 数据是小于等于v的,就移动到了前半部分, 从而尽量把等于的数据均匀分散到两端。

在这里插入图片描述

代码如下:

public static void quickSort2(int[] arr, int l, int r) {
	if( r-l <= 15) {
		 insertionSort(arr,l,r);
		return;
	}
	//排序后,位置p左边的数据都小于p,右边的数据都大于p
	int p = partition2(arr, l, r);
	quickSort2(arr, l, p-1);//通过递归继续对左边的数据排序
	quickSort2(arr, p+1, r);//通过递归继续对右边的数据排序
}

private static int partition2(int[] arr, int l, int r){

    swap( arr , l, (int) (Math.random()%(r-l+1)+l) );
    int v = arr[l];

    // arr[l+1...i) <= v; arr(j...r] >= v
    int i = l+1, j = r;
    while( true ){
        while( i <= r && arr[i] < v )
            i ++;

        while( j >= l+1 && arr[j] > v )
            j --;

        if( i > j )
            break;

        swap( arr , i, j);
        i ++;
        j --;
    }
    swap( arr , i, j);
    return j;
}

快速排序的优化三–三路快速排序

三路快速排序,如名字一般,就要维护三个索引,分别是小于v的最右边位置索引,大于v的最左边的位置,以及当前指向数据的位置,

  • 1、 当 当前位置的数小于v时,就和 等于v的数据集合的第一个位置的数交换,然后 lt++, i++;
  • 2、如果当前位置的数大于v,则当前位置 的数和大于v的部分的最小的位置交换,然后gt–, i++;
  • 3、如果当前位置的数等于v, 则直接i++即可
    在这里插入图片描述

代码实现如下:

public static void quickSort3Ways(int[] arr, int l, int r){

    if( r - l <= 15 ){
        insertionSort(arr,l,r);
        return;
    }

    swap( arr , l, (int) (Math.random()%(r-l+1)+l) );

    int v = arr[l];

    int lt = l;     // arr[l+1...lt] < v
    int gt = r + 1; // arr[gt...r] > v
    int i = l+1;    // arr[lt+1...i) == v
    while( i < gt ){
        if( arr[i] < v ){
            swap( arr, i, lt+1);
            i ++;
            lt ++;
        }
        else if( arr[i] > v ){
            swap( arr, i , gt-1);
            gt --;
        }
        else{ // arr[i] == v
            i ++;
        }
    }

    swap( arr, i, lt );

    quickSort3Ways(arr, l, lt-1);
    quickSort3Ways(arr, gt, r);
}

附上自动生成测试数组的代码

public static int[] randomArray(int length) {
	int[] arr = new int[length];
	Random random = new Random();
	for(int i=0; i<length; i++) {
		arr[i] = random.nextInt(100);
	}
	//自动生成随机数组,先进行一次原始数据打印
	System.out.println(Arrays.toString(arr));
	return arr;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值