快速排序算法原理解析(基于 Java 实现)

快速排序算法解析

快速排序可以说是最频繁的算法之一了,但是它又颇有难度,今天就让我们一起来掌握它吧!

快速排序的核心框架是——二叉树的前序遍历(按中左右顺序排序) + 对撞型双指针。

快速排序的基本思想:

通过一个标记 pivot 元素将n个元素划分为左右两个子序列 left 和 right,其中 left 中的元素都比 pivot 小,right 的都比 pivot 的大。然后再通过递归再次对left 和 right 各自执行快速排序,将左右子序列排序好后,整个序列九有序了。

注意:这里排序进行左右划分的时候是一直划分到子序列只包含一个元素,然后再递归返回。

和二叉树的前序遍历是不是非常想呢,思路是一样的,只不过还运用了对撞双指针来实现。

在这里我们我们以序列 {26,53,48,15,13,48,32,15} 观察划分一次的过程:

上面红框位置表示当前已经被赋值给了元素pivot 或者其他位置,这些位置可以空出来放置需要交换位置的元素。通过一次排序后,可以看到26最终放到属于自己的位置上,不会再变化。左侧的数比26都小,右侧的都比26大,因此26的左右两侧可以分别再进行排序。后面就是递归实现啦!

原理不是很简单,接下来就让我们一起通过代码来解析吧!

public static void quickSort(int[] arr, int left, int right) {
    // 若left >= right 说明此时该(子)序列已经排序完毕
    if (left < right) {
        // 这里以数组最后一个元素作为 pivot
        int pivot = arr[right];
        // i是可以用来存放比pivot小的、交换位置过来的数 
        int i = left - 1;
        // j是遍历数组的指针,用来比较数组每个数与pivot的大小关系
        for (int j = left; j < right; j++) {
            // 若arr[j]比pivot大,则此时不需要交换位置
            // 若arr[j]比pivot小,则此时需要把若arr[j]与arr[i+1]交换位置
            // 这是因为保证比pivot小的数一定在左边,把比其大的逐渐换到右边去
            if (arr[j] < pivot) {
                i++; // i往右移一格,该处为可存放比pivot小的数的最新位置
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }

        // 在将i个比pivot小的数移到其左边后,此时哨兵应该位置i+1上
        // 故将最右边的pivot换到其固定的位置上
        int pivotIndex = i + 1; 
        int temp = arr[pivotIndex];
        arr[pivotIndex] = arr[right];
        arr[right] = temp;
        // 通过递归对其左右子序列分别执行快速排序
        quickSort(arr, left, pivotIndex - 1);
        quickSort(arr, pivotIndex + 1, right);
    }
}

通过上面非常非常详细的解析,是不是已经掌握了呢,下面让我们再来学习另外一种实现方式,这里采用的就是一个对撞的双指针操作,思路其实差不多,也同样是选出一个pivot元素,然后将比它小的移到其左边,比他大的移到其右边。然后再分别递归处理其两侧分支,与二叉树的前序遍历本质上也是一样的,所以下面我们直接通过代码来详细解析下!

private static void quickSort(int[] array, int start, int end) {
    // 当start索引大于等于end索引,说明此时该(子)序列已经排序完毕,直接返回
    if (start >= end) {
        return;
    }
    // left指针:可存放比pivot小的数的最新位置
    // right指针:可存放比pivot大的数的最新位置
    int left = start, right = end;
    // pivot:这里选取了中间元素作为比较元素,以减小判断移动次数,提高效率
    int pivot = array[(start + end) / 2];
    while (left <= right) {
        // 当left指针的数比pivot小,left指针右移,更新可存放比其小的数的位置
        while (left <= right && array[left] < pivot) {
            left++;
        }
        // 当right指针的数比pivot大,left指针左移,更新可存放比其大的数的位置
        while (left <= right && array[right] > pivot) {
            right--;
        }
        // 当left上的数比pivot大,right上的数比pivot小,则交换两者位置,然后更新两指针位置
        if (left <= right) {
            int temp = array[left];
            array[left] = array[right];
            array[right] = temp;
            left++;
            right--;

        }
    }
    // 通过递归对其两侧子序列执行快速排序
    quickSort(array, start, right);
    quickSort(array, left, end);
}

OK~ 到这里快速排序原理以及代码的解析就全部结束啦~

最后让我们分析一下其复杂度:

快速排序的时间复杂度计算比较麻烦一些。从原理来看,如果我们选择的pivot每次都正好在中间,则效率达到最高,但这是无法保证的,所以我们由以下三个情况来分析:

⚪ 最坏的情况就是如果每次选择的恰好都是low结点作为pivot,而元素恰好都是逆序的,此时时间复杂度为O(n^2)

⚪ 最好的情况就是如果元素恰好都是有序的,则时间复杂度为O(n)

⚪ 折中的情况是每次选择的都是中间结点,此时两侧序列每次都是长度相等的序列,类似于折半,则此时的时间复杂度为O(nlogn))

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值