看了这篇,你不可能学不会快速排序和归并排序

字数 293阅读 4

一、两者对比

快速排序

  • 当两个子数组都有序时,整个数组也就有序
  • 递归调用发生在处理整个数组之后
  • 切分的位置取决于数组的内容

归并排序

  • 将数组分为两个子数组分别排序,并将有序的子数组归并,以将整个数组排序
  • 递归调用发生在处理整个数组之前
  • 一个数组被等分为两半

二、快速排序详解

源码解析

void QuickSort(int[] a) {
    shuffle(a);
    sort(a, 0, a.length - 1);
}

void sort(int[] a, int lo, int hi) {
    if (hi <= lo) return;
    int j = partition(a, lo, hi);
    sort(a, lo, j - 1);
    sort(a, j + 1, hi);
}

int partition(int[] a, int lo, int hi) {
    int i = lo;                 // 左指针开始位置
    int j = hi + 1;             // 右指针开始位置
    int v = a[lo];              // 轴点
    while (true) {
        while (a[++i] < v)      // 从左向右扫描,直到找到一个大于等于轴点的元素
            if (i == hi) break; // 右边界
        while (a[--j] > v)      // 从右向左扫描,直到找到一个小于等于轴点的元素
            if (j == lo) break; // 左边界
        if (i >= j) break;
        exch(a, i, j);          // 交换这两个没有排好的元素
    }
    // 轴点和左子数组最右元素交换
    // from   v    <=v    >=v
    //  to   <=v    v     >=v
    exch(a, lo, j);
    return j;                   // 返回轴点位置
}

void exch(int[] a, int i, int j) {
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

快速排序最好和最坏的情况

最好情况:

  • 轴点(pivot)总是落在子数组的中位数上

最坏情况:

  • 坏排序:原数组已经有序
  • 坏元素:所有元素都是重复的

在快速排序中,一个数组的partition的递归函数调用树看上去就像二叉树一样,如果pivot每次都接近子数组的中位数,那么整棵函数调用二叉树也会更接近平衡。

三、归并排序详解

源码解析

void MergeSort(int[] a) {
    int[] aux = new int[a.length];
    sort(a, 0, a.length - 1);
}

void sort(int[] a, int lo, int hi) {
    if (hi <= lo) return;
    int mid = lo + (lo + hi) / 2;
    sort(a, lo, mid);       // 排序左半边
    sort(a, mid + 1, hi);   // 排序右半边
    merge(a, lo, mid, hi);
}

void merge(int[] a, int lo, int mid, int hi) {
    for (int k = lo; k <= hi; k++) {
        aux[k] = a[k];
    }
    int i = lo;         // 左半边起始位置
    int j = mid + 1;    // 右半边起始位置
    for (int k = lo; k <= hi; k++) {
        if      (i > mid)         a[k] = aux[j++];  // 左半边用完,取右半边元素
        else if (j > hi)          a[k] = aux[i++];  // 右半边用完,取左半边元素
        else if (aux[j] < aux[i]) a[k] = aux[j++];  // 右半边元素更小,取右半边元素
        else                      a[k] = aux[i++];  // 左半边元素更小,取左半边元素
    }
}

四、参考资料

  • Robert Sedgewick & Kevin Wayne《算法(第四版)》

  • UC Berkeley CS 61B