74-快速排序——一路快排

快速排序是影响二十世纪最伟大的排序算法之一。

JDK的双轴快速排序就是对快排的优化,本质还是快排。

  1. 从待排序区间选择一个数,作为基准值/分区点(pivot),此时默认选择数组的第一个元素作为比较的基准值。
  2. partition:遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等)放到基准值的右边。即选取一个分区点,将数组分成三部分:基准值之前的数组<基准值<基准值之后的数组。
  3. 采用分治思想,对左右两个小区间按照同样的方式处理,直到小区间的长度==1,代表已经有序,或者小区间长度==0,代表没有数据。

/**
 * 快速排序 一路快排
 * @param arr
 */
public static void quickSort(int[] arr) {
    quickSortInternal(arr, 0, arr.length - 1);
}

/**
 * 在l...r上进行快速排序
 * @param arr
 * @param l
 * @param r
 */
private static void quickSortInternal(int[] arr, int l, int r) {
    //递归终止时,小数组使用插入排序
    if(r - l <= 15) {
        insertBase(arr, l ,r);
        return;
    }
    //选择基准值,找到该值对应的下标
    int p = partition(arr, l, r);
    //在<基准值区间进行快速排序
    quickSortInternal(arr, l, p - 1);
    //在>=基准值区间进行快速排序
    quickSortInternal(arr, p + 1, r);
}

/**
 * 在arr[l...r]上选择基准值,将数组划分为 <v 和 >= v 两部分,返回基准值对应的元素下标
 * @param arr
 * @param l
 * @param r
 * @return
 */
private static int partition(int[] arr, int l, int r) {
    //默认选择数组的第一个元素作为基准值
    int v = arr[l];

    //arr[l + 1...j] < v
    int j = l;
    //i是当前处理的元素下标
    //arr[l + 1...j] < v 最开始为空区间 [l + 1...l] = 0
    //arr[j + 1...i] >= v 最开始为空区间 [l + 1...l + 1] = 0
    for (int i = l + 1; i <= r; i++) {
         if(arr[i] < v) {
             swap(arr, j + 1, i);
             //小于v的元素值新增一个
             j++;
         }
    }
    //此时j下标对应的就是最后一个 < v 的元素,交换j和l的值,选取的基准值交换到j的位置
    swap(arr, l, j);
    return j;
}

代码在电脑上若出现问题,可能是栈溢出,解决:设置当前栈的大小为1M大小(用于50w数据)。

Environment Variables:-Xss = 1M

稳定性:不稳定。

分区时,当扫描arr[i] < v时,交换了arr[j] 和arr[i],对于arr[j + 1] >= v,就有可能把一个 = v从前面交换到了后面,分区函数无法保证稳定性。

时间复杂度:O(nlogn)递归过程中调用分区函数。

  • 分区函数的时间复杂度是O(n)
  • 递归过程就是不断将原数组根据基准值拆分为子数组,有点类似归并拆分。递归次数就是递归树的高度,时间复杂度是O(logn)

①当待排序元素接近有序时,递归树会退化为单支树(链表),快速排序的性能衰减为O(n ^ 2)。

解决->随机化快排:在数组中随机选取一个元素作为基准值,平衡左右两个子树的元素个数。

private static final ThreadLocalRandom random = ThreadLocalRandom.current();

 /**
  * 快速排序 一路快排
  * @param arr
  */
public static void quickSort(int[] arr) {
    quickSortInternal(arr, 0, arr.length - 1);
}

/**
 * 在l...r上进行快速排序
 * @param arr
 * @param l
 * @param r
 */
private static void quickSortInternal(int[] arr, int l, int r) {
    //递归终止时,小数组使用插入排序
    if(r - l <= 15) {
        insertBase(arr, l ,r);
        return;
    }
    //选择基准值,找到该值对应的下标
    int p = partition(arr, l, r);
    //在<基准值区间进行快速排序
    quickSortInternal(arr, l, p - 1);
    //在>=基准值区间进行快速排序
    quickSortInternal(arr, p + 1, r);
}

/**
 * 在arr[l...r]上选择基准值,将数组划分为 <v 和 >= v 两部分,返回基准值对应的元素下标
 * @param arr
 * @param l
 * @param r
 * @return
 */
private static int partition(int[] arr, int l, int r) {
    //优化:随机选择一个元素值作为基准值(在当前数组中)
    int randomIndex = random.nextInt(l, r);
    swap(arr, l, randomIndex);
    int v = arr[l];

    //arr[l + 1...j] < v
    int j = l;
    //i是当前处理的元素下标
    //arr[l + 1...j] < v 最开始为空区间 [l + 1...l] = 0
    //arr[j + 1...i] >= v 最开始为空区间 [l + 1...l + 1] = 0
    for (int i = l + 1; i <= r; i++) {
         if(arr[i] < v) {
             swap(arr, j + 1, i);
             //小于v的元素值新增一个
             j++;
         }
    }
    //此时j下标对应的就是最后一个 < v 的元素,交换j和l的值,选取的基准值交换到j的位置
    swap(arr, l, j);
    return j;
}

②当集合中包含大量重复元素时,最坏的情况是:集合中全是相等的元素。按照我们的分区,一个分区没有元素,一个分区元素为n,快排性能再次衰减为O(n ^ 2)。

导致蓝色区域元素个数远远大于橙色区域的元素个数,造成递归树的严重倾斜。

解决->二路快排/三路快排:将相等的元素均匀分布在左右两个子区间,保证递归树的平衡性。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值