快速排序是一种基于分治法的排序算法,其中

  1. 通过选择一个枢轴元素(从数组中选择的元素)将数组划分为子数组。

在划分数组时,枢轴元素应该以这样的方式定位,即小于枢轴的元素保留在左侧,大于枢轴的元素位于枢轴的右侧。

  1. 左右子数组也使用相同的方式进行划分。这个过程一直持续到每个子数组都包含一个元素。
  2. 此时,元素已经排序。最后,将元素组合起来形成一个有序数组。

算法的图解

  1. 选择枢轴元素

快速排序有不同的变体,其中枢轴元素是从不同位置选择的。在这里,我们将选择数组最右边的元素作为枢轴元素。

快速排序(Quick Sort) - 图1
选择一个枢轴元素
  1. 重新排列数组
    现在数组的元素被重新排列,使得小于枢轴的元素放在左边,大于枢轴的元素放在右边。
快速排序(Quick Sort) - 图2
将所有较小的元素放在左侧,将较大的元素放在枢轴元素的右侧

下面是我们如何重新排列数组:

2.1 指针固定在枢轴元素上。枢轴元素与从第一个索引开始的元素进行比较。

快速排序(Quick Sort) - 图3
枢轴元素与从第一个索引开始的元素的比较

2.2 如果元素大于枢轴元素,则为该元素设置第二个指针。

快速排序(Quick Sort) - 图4
如果元素大于枢轴元素,则为该元素设置第二个指针。

2.3 现在,将枢轴与其他元素进行比较。如果到达小于枢轴元素的元素,则将较小的元素与较早找到的较大的元素交换。

快速排序(Quick Sort) - 图5
枢轴与其他元素进行比较。

2.4 再次重复该过程以将下一个更大的元素设置为第二个指针。并且,将其与另一个较小的元素交换。

快速排序(Quick Sort) - 图6
重复该过程以将下一个更大的元素设置为第二个指针。

2.5 该过程一直持续到到达倒数第二个元素。

快速排序(Quick Sort) - 图7
该过程一直持续到到达倒数第二个元素。

2.6 最后,枢轴元素与第二个指针交换。

快速排序(Quick Sort) - 图8
最后,枢轴元素与第二个指针交换。
  1. 分治子数组

再次分别为左侧和右侧子部件选择枢轴元素。并且,重复步骤2。
子阵列被划分,直到每个子阵列由单个元素形成。此时,数组已经排序。

快速排序(Quick Sort) - 图9
选择每一半的枢轴元素并使用递归放置在正确的位置

算法的伪码

  1. quickSort(array, leftmostIndex, rightmostIndex)
  2. if (leftmostIndex < rightmostIndex)
  3. pivotIndex <- partition(array,leftmostIndex, rightmostIndex)
  4. quickSort(array, leftmostIndex, pivotIndex - 1)
  5. quickSort(array, pivotIndex, rightmostIndex)
  6. partition(array, leftmostIndex, rightmostIndex)
  7. set rightmostIndex as pivotIndex
  8. storeIndex <- leftmostIndex - 1
  9. for i <- leftmostIndex + 1 to rightmostIndex
  10. if element[i] < pivotElement
  11. swap element[i] and element[storeIndex]
  12. storeIndex++
  13. swap pivotElement and element[storeIndex+1]
  14. return storeIndex + 1

算法可视化

借助下图,您可以了解快速排序算法的工作原理。

快速排序(Quick Sort) - 图10
使用递归对枢轴左侧的元素进行排序
快速排序(Quick Sort) - 图11
使用递归对枢轴右侧的元素进行排序

算法的实现

// Quick sort in Java

import java.util.Arrays;

class Quicksort {

  // method to find the partition position
  static int partition(int array[], int low, int high) {

    // choose the rightmost element as pivot
    int pivot = array[high];

    // pointer for greater element
    int i = (low - 1);

    // traverse through all elements
    // compare each element with pivot
    for (int j = low; j < high; j++) {
      if (array[j] <= pivot) {

        // if element smaller than pivot is found
        // swap it with the greatr element pointed by i
        i++;

        // swapping element at i with element at j
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
      }

    }

    // swapt the pivot element with the greater element specified by i
    int temp = array[i + 1];
    array[i + 1] = array[high];
    array[high] = temp;

    // return the position from where partition is done
    return (i + 1);
  }

  static void quickSort(int array[], int low, int high) {
    if (low < high) {

      // find pivot element such that
      // elements smaller than pivot are on the left
      // elements greater than pivot are on the right
      int pi = partition(array, low, high);

      // recursive call on the left of pivot
      quickSort(array, low, pi - 1);

      // recursive call on the right of pivot
      quickSort(array, pi + 1, high);
    }
  }
}

// Main class
class Main {
  public static void main(String args[]) {

    int[] data = { 8, 7, 2, 1, 0, 9, 6 };
    System.out.println("Unsorted Array");
    System.out.println(Arrays.toString(data));

    int size = data.length;

    // call quicksort() on array data
    Quicksort.quickSort(data, 0, size - 1);

    System.out.println("Sorted Array in Ascending Order: ");
    System.out.println(Arrays.toString(data));
  }
}

算法复杂性

时间复杂度
最好 O(n*log n)
最差 O(n2)
平均 O(n*log n)
空间复杂度 O(log n)
稳定

时间复杂度

  • 最坏情况复杂性[Big-O]:O(n**2**)

当提取的枢轴元件是最大或最小的元素时,会发生。
这种情况导致枢轴元素位于已排序数组的最末端的情况。一个子数组始终为空,另一个子数组包含 n - 1 个元素。因此,仅在此子数组上调用快速排序。
但是,快速排序算法对于分散的枢轴具有更好的性能。

  • 最佳情况复杂度 [Big-omega]:O(n*log n)
    当枢轴元素始终是中间元素或靠近中间元素时,就会发生这种情况。

  • 平均情况复杂度 [Big-theta]:O(n*log n)
    当上述条件不发生时,就会发生。

空间复杂度

  • 快速排序的空间复杂度为 O(log n)

算法的应用

快速排序算法用于

  • 编程语言有利于递归
  • 时间复杂度很重要
  • 空间复杂度很重要

相似的算法

  • 插入排序
  • 归并排序
  • 选择排序
  • 桶排序