日拱一卒#快速排序#

178 阅读3分钟

认识快速排序

快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。

不同的是,冒泡排序在每一轮中只把 1 个元素冒泡到数列的一端。而快速排序则在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列的一边,比它小的元素移动到数列的一边,从而把数列拆解成两个部分,这种思路叫做分治法

如图所示,紫色为基准元素:

快速排序具体实现

从上面的概念中我们可以知道,快速排序中很重要的一个问题 是,基准元素的选取(如何选取基准元素?);还有一个就是基于基准元素对元素进行比较然后移动,从而达到快速排序的目的。

双边循环法

选择基准元素(pivot)

选定了 pivot 之后,并且设置两个指针 left 和 right 指向数列的最左和最优两个元素,如下图 pivot =4:

循环比较交换

  • 第 1 次循环:
    • 从 right 指针开始,让指针所指向的元素和基准元素做比较。如果大于或等于 pivot,则指针向右移动;如果指针小于 pivot ,则 right 指针停止移动,切换到 left 指针。
    • 轮到 left 指针行动,让指针所指向的元素和 pivot 做比较。如果小于等于 pivot,则指针向右移动;如果大于 pivot,则 left 指针停止移动(由于 left 指针开始指向的是基准元素,判断肯定相等,所以 left 右移 1 位)。
    • 左右指针指向的元素交换位置。

如果所示: 由于 7>4,left 指针在元素 7 的位置停下。这时,让 left 指针和 right 指针所指向的元素进行交换,得到的结果如下图所示:

  • 第 2 次循环,重新切换到 right 指针,向左移动。right指针先移动到 8 的位置,8>4,继续左移、由于 2<4,则在 2 的位置上停止(此时 right 指针指的位置为 2),left 指针向右移动一位,如果大于 pivot 则停止移动,此时 left 指针指向元素 6;

接下来就是对 4 左右两边的元素进行一个排序,比较交换排序的过程。我们看下代码的实现:

代码实现

/**
 * 快速排序
 * 双边循环法
 * 1. 选定基准元素 pivot
 * 2. 设置两个指针,分布为 left,right,指向数列的最左边和最右边
 * 3. 开始循环
 *  从 right 指针开始,让指针所指向的元素跟基准元素(pivot)进行比较;
 *      如果 大于等于 pivot,将 right 指针向左移动(移动后 right = right -1)
 *      如果小于 pivot,让 right 指针停止移动,切换到 left 指针
 *      此时 left 指针开始移动,让此时的 left 指针所指向的元素和基准元素做比较:
 *          如果小于或等于 pivot,则 left 指针向右移动(此时 left = left + 1)
 *          如果大于 pivot,让 left 指针停止移动,将 left 指针指向的元素与 right 指针指向的元素进行位置交换
 */
public class QuickSortTwoSide {
    public static void main(String[] args) {
        int[] nums = new int[]{9,4,7,3,5,6,2,8,1};
        quickSortTwo(nums,0,nums.length -1);
        System.out.println(Arrays.toString(nums));
    }
    public static void quickSortTwo(int[] arr,int start,int end){
        // 递归的结束条件,start >= end
        if(start >= end){
            return;
        }
        // 1. 获取基准元素
        int pivotIndex = getPivot(arr,start,end);
        // 2. 根据基准元素,分层两个部分进行递归排序
        quickSortTwo(arr,start,pivotIndex - 1);
        quickSortTwo(arr,pivotIndex + 1,end);

    }
    private static int getPivot(int[] arr, int start,int end){
        // 取第 1 个位置(也可以随机选取位置)的元素作为基准元素
        int pivot = arr[start];
        int left = start;
        int right = end;
        while (left != right){
            // 控制 right 指针比较并左移
            while (left < right && arr[right] > pivot){
                right --;
            }
            // 控制 left 指针比较并右移
            while (left < right && arr[left] <= pivot){
                left ++;
            }
            // 如果都不满足的话,交换两个指针指向的元素的位置
            if(left < right){
                int p = arr[left];
                arr[left] = arr[right];
                arr[right] = p;
            }
        }
        // 左右指针汇合
        arr[start] = arr[left];
        arr[left] = pivot;
        return left;
    }

}

Console 日志信息:
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Process finished with exit code 0

时间复杂度:O(nlogn)

单边循环法

单边循环法只从数组的一边进行遍历和交换。首先选定基准元素 pivot,同时,设置一个 mark 指针指向数列的起始位置,这个 mark 指针代表小于 基准元素的区域边界。

循环比较交换

  • 从基准元素的下一个位置开始遍历数组:
    • 如果遍历到的元素大于基准元素,就继续往后遍历
    • 如果遍历到的元素小于基准元素,则需要做两件事
        1. 把 mark 指针右移 1 位,因为小于 pivot 的区域边界增大了 1
        1. 让最新遍历到的元素和 mark 指针所指向的元素交换位置,因为最新遍历的元素归属于小于 pivot 的区域
  • 首先第一遍历得到元素 7 ,7>4,所以继续遍历
  • 接下啦第二次遍历得到元素 3, 3<4,所以 mark 指针需要右移 1 位

  • 然后,让元素 3 和移位后的 mark 指针指向的元素进行交换(因为元素 3 归属于小于 pivot 的区域)

  • 按照这个思路,继续遍历

然后继续递归,最终得到排序好的数列。

代码实现


import java.util.Arrays;

/**
 * 快速排序
 *  单边循环法
 *
 */
public class QuickSortOneSide {
    public static void main(String[] args) {
        int[] nums = new int[]{9,4,7,3,5,6,2,8,1};
        quickSort(nums,0,nums.length -1);
        System.out.println(Arrays.toString(nums));
    }
    public static void quickSort(int[] arr,int start,int end){
        // 递归的结束条件,start >= end
        if(start >= end){
            return;
        }
        // 1. 获取基准元素
        int pivotIndex = getPivot(arr,start,end);
        // 2. 根据基准元素,分层两个部分进行递归排序
        quickSort(arr,start,pivotIndex - 1);
        quickSort(arr,pivotIndex + 1,end);

    }
    private static int getPivot(int[] arr, int start,int end){
        // 取第 1 个位置(也可以随机选取位置)的元素作为基准元素
        int pivot = arr[start];
        int mark = start; // 遍历的开始位置
        for (int i = start + 1; i <= end ; i++) {
            if(arr[i] < pivot){
                // 如果大于基准元素,继续遍历
                mark ++;
                int p = arr[mark];
                // 此时 mark 位置
                arr[mark] = arr[i];
                arr[i] = p;
            }
        }
        arr[start] = arr[mark];
        arr[mark] = pivot;
        return mark;
    }

}

console 日志:
[1, 2, 3, 4, 5, 6, 7, 8, 9]

快速排序的时间复杂度是:O(nlogn)