手写快速排序


快速排序由C. A. R. Hoare在1960年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。快速排序算法是冒泡排序的一种改进,在面试中经常会有排序算法题,快速排序是高频的算法考查点。

算法思想

快速排序属于分治思想,即把原数组按一定的规则划分为两个部分,然后再以同样的规则继续划分这两个部分,直到不能再划分为止。那为什么使用二分法把数组分为两个部分,而不使用三分法把数组分成三个部分呢。二分法相比于三分法思路更清晰简单。而且就算法复杂度而言,三分法也没什么优势。算法步骤也很简单,我总结分为四步

  1. 选基准数:在数组中随机选择一个数字作为基准数。不要被基准数这个名词吓到,说白了就是在数组中随便选一个数,我们把它叫做基准数。一般的我们就选择数组的第一个元素作为基准数。
  2. 比较交换:设置两个游标,一个游标从数组的右边左走,遇到比基准数小的就停下,另一个游标从数组的左边往右走,遇到比基准数大的就停下,然后交换这个数字的位置。重复步骤2,直到两个游标相遇时进入步骤3(其实就是在用两个游标遍历数组,最终目的就是把数组中大于基准数的数字放数组右边,把小于基准数的数字放数组左边)
  3. 校准中轴:步骤2中两个游标相遇的点,我们称之为中轴。这一步我们要做的就是把基准数和中轴点所在的数字交换位置。(因为中轴的位置和基准数的位置不一定会重合,经过步骤2后,我们需要保证数组被分为的两部分左边的比基本数小,右边的比基准数大,这必须把基准数和中轴数位置交换。这是整个快速排序的关键步骤)
  4. 递归快排:经过步骤3后,数组的两个部分已经划分完毕,只需以同样的方式,继续划分这个两个部分即可

算法图解

这里以一个具体的实例来图解算法过程吧,假设有一个数组 int[] array = {3,1,4,7,5,2,8,9,6} ,用快速排序进行排序。

第一次递归

第一次递归的数组是原始数组,我们选择数组的第一个元素3作为基准数

首先哨兵j开始出动。一定需要让哨兵j先出动,这一点非常重要(也是算法实现的一个细节点,如果不是左边的哨兵先动,最后无法完成排序)。哨兵j一步一步地向左挪动(即j--),直到找到一个小于3的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于3的数停下来。最后哨兵j停在了数字2上,哨兵i停在了数字4上。

然后交换4和2的位置。交换后数组的序列顺序如下

到此,第一次交换结束。接下来开始哨兵j继续向左挪动。这时移动到2数字上时,哨兵j发现了2比3小于是停下。然后i往右移。正当哨兵i准备右移时,他发现哨兵j就在2上,他们两个相遇了。

至此第一次递归结束,这时我们可以看到中轴数是2,中轴数的右边全都比基准数3大,中轴数的左边除了3都比基准数小。这是需要校准中轴了,即把中轴数和基准数交换位置,交换位置后,数组序列如下

第二次递归

经过第一次递归后,原始数组被划分为两个数组,分别是 [2,1][7,5,4,8,9,6]。以第二个数组为例。

后面的就不说了,和前面的步骤一样。

Java代码实现

package com.example.sort;

/**
 * @Auther: Lushunjian
 * @Date: 2020/4/18 14:07
 * @Description:
 */
public class SortTest {

    public static void main(String[] args){
        int[] array = {3,1,4,7,5,2,8,9,6};
        quickSort(array,0,array.length-1);
        for (int item : array) {
            System.out.println(item);
        }
    }

    /**
     * 快速排序
     * */
    public static void quickSort(int[] arr,int start,int end){
        if(start>end){
            return;
        }
        int low = start;
        int height = end;
        // 基准数
        int base = arr[low];

        while (start<end){
            // 注意点
            // 1.必须从数组的右边先开始
            // 2.与基准数比较时必须是>=或<=,不能只是>或<
            while (arr[end]>=base && start<end)
                end--;

            while (arr[start]<=base && start<end)
                start++;

            // 左边找到一个比基准数大的,右边找到一个比基准数小的。两个数交换位置
            int temp = arr[start];
            arr[start] = arr[end];
            arr[end] = temp;
        }

        // 找完一轮后,将轴中数与基准数交换位置(目的是保证基准数始终处于交换轴的位置)
        arr[low] = arr[start];
        arr[start] = base;

        // 左右两个数组继续递归快排
        quickSort(arr,low,start-1);
        quickSort(arr,start+1,height);
    }
}

注意点

在上面代码注释中我已经提到了两个注意点,这里总结一下,应该有四个注意点

  1. quickSort方法中首先应判断 start>end ,为 true 则结束方法。如不判断递归调用时会无限递归致使堆栈溢出
  2. 左右两个哨兵移动时,要保证总是右边的哨兵先往左移动。
  3. 在元素与基准数比较的时候要使用 >=<=。如果仅仅使用 >< ,会导致死循环。
  4. 比较完一轮后,基准数要和中轴数交换位置,否则会导致排序失败

Author: 顺坚
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source 顺坚 !
评论
 Previous
LeetCode-两数相加 LeetCode-两数相加
在代码的世界中有一句名言,即程序=数据结构+算法。工作中大部分开发其实都用不到多高深的算法,因为前辈们已经造好了各种高级轮子,我们要做的仅仅只需要调用他们封装好的API就行了。这样就算不懂这些知识,只要Java API、开发框架用得熟练,照
2020-04-19
Next 
开发必需学会的Linux命令 开发必需学会的Linux命令
市场大部分服务都运行Linux之上,开发过程中难免需要到服务器上查看服务日志,环境部署,排查问题等。操作系统是计算机科学都要接触的基本概念,抛开那些纯理论的操作系统底层实现,在Linux下做软件开发这么多年,每次程序运行出现问题,都要一步一
2020-04-16
  TOC