基础算法学习 | 5.快速排序

104 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情

image.png

往期内容

基础算法学习 | 1.二分查找

基础算法学习 | 2.冒泡排序

基础算法学习 | 3.选择排序

基础算法学习 | 4.插入排序

前言

快速排序算法通过多次比较和交换来实现排序

算法实现

算法描述

  • 每一轮排序选择一个基准点(pivot)进行分区

    • 让小于基准点的元素的进入一个分区,大于基准点的元素的进入另一个分区
    • 当分区完成时,基准点元素的位置就是其最终位置
  • 在子分区内重复以上过程,直至子分区元素个数少于等于 1,这体现的是分而治之的思想

  • 从以上描述可以看出,一个关键在于分区算法,常见的有洛穆托分区方案、双边循环分区方案、霍尔分区方案

单边循环快排(lomuto 洛穆托分区方案)

  • 选择最右元素作为基准点元素
  • j 指针负责找到比基准点小的元素,一旦找到则与 i 进行交换
  • i 指针维护小于基准点元素的边界,也是每次交换的目标索引
  • 最后基准点与 i 交换,i 即为分区位置
public static void quick(int[] a, int l, int h) {
    if (l >= h) {
        return;
    }
    int p = partition(a, l, h); // p 索引值
    quick(a, l, p - 1); // 左边分区的范围确定
    quick(a, p + 1, h); // 左边分区的范围确定
}

private static int partition(int[] a, int l, int h) {
    // 最右边元素为基准点元素
    int pv = a[h];
    // i 指针维护小于基准点元素的边界
    int i = l;
    // j 指针负责找到比基准点小的元素
    for (int j = l; j < h; j++) {
        if (a[j] < pv) {
            // 找到则与 i 进行交换
            if (i != j) {
                int tmp = a[i];
                a[i] = a[j];
                a[j] = tmp;
            }
            i++;
        }
    }
    // 最后基准点与 i 交换
    if (i != h) {
        int tmp = a[h];
        a[h] = a[i];
        a[i] = tmp;
    }
    System.out.println(Arrays.toString(a) + " i=" + i);
    // 返回值代表了基准点元素所在的正确索引,用它确定下一轮分区的边界
    return i;
}

双边循环快排(不完全等价于 hoare 霍尔分区方案)

  • 选择最左元素作为基准点元素
  • j 指针负责从右向左找比基准点小的元素,i 指针负责从左向右找比基准点大的元素,一旦找到二者交换,直至 i,j 相交
  • 最后基准点与 i(此时 i 与 j 相等)交换,i 即为分区位置

要点

  • 基准点在左边,并且要先 j 后 i
  • while(i < j && a[j] > pv ) j--
  • while (i < j && a[i] <= pv ) i++
private static void quick2(int[] a, int l, int h) {
    if (l >= h) {
        return;
    }
    int p = partition(a, l, h);
    quick(a, l, p - 1);
    quick(a, p + 1, h);
}

private static int partition2(int[] a, int l, int h) {
    int pv = a[l];
    int i = l;
    int j = h;
    while (i < j) {
        // j 从右找小的
        while (i < j && a[j] > pv) {
            j--;
        }
        // i 从左找大的
        while (i < j && a[i] <= pv) {
            i++;
        }
        swap(a, i, j);
    }
    swap(a, l, j);
    System.out.println(Arrays.toString(a) + " j=" + j);
    return j;
}

快排特点

  • 平均时间复杂度是 O(nlog2n)O(nlog_2⁡n ),最坏时间复杂度 O(n2)O(n^2)
  • 数据量较大时,优势非常明显
  • 属于不稳定排序

洛穆托分区方案和霍尔分区方案对比

大家多多讨论学习salute

image.png