算法导论笔记 第7章 快速排序

快速排序是用于排序的最佳使用选择,这是因为其平均性能相当好,期望运行时间是O(nlgn),且记号中隐藏的常数因子很小。另外:它还能够进行就地排序。

快速排序是基于分治模型的:

  1. 分解:数组A[p..r)被划分成两个(可能为空)子数组A[p..q)和A[q+1..r),使得A[p..q)中的每个元素都小于A[q], 而且,小于等于A[q+1..r)中的元素。下标q也在这个划分过程中进行计算。
  2. 解决:通过递归调用快排程序,对子数组A[p.q)和A[q+1..r)进行排序。
  3. 合并:因为两个子数组是就地排序的,将它们合并不需要操作:整个A[p..r)已经排序。

 

void quicksort(int* A, int p, int r){
  if (p < r) {
    int q = partition(A, p, r);
    quicksort(A, p, q);
    quicksort(A, q+1, r);
  }
}

为排序一个完整的数组A,最初的调用是quicksort(A, 0, __count_of(A) )

 

快排算法的关键是PARTITION过程,它对子数组A[p...r)进行就地重排 

int  partition(int*  A, int p, int r) {
  int x = A[r -1];
  int i = p - 1;
  int j = p;
  for (; j < r-1; j++) {
    if (A[j] <=x) {
      i++;
      int tmp = A[i]; A[i] = A[j]; A[j] = tmp;// A[i]  A[j]
    }
  }
  i++;
  int tmp = A[i]; A[i] = A[j]; A[j] = tmp;// A[i]  A[j]
  return i;
}

可以用循环不变式来分析一下Partition程序:

循环不变量是.任何在[p,i]的数都小于等于x, 在[i,j)的数都大于x。 在[j,r)的数未知。 A[r-1] = X。

  1. 初始化:在循环的第一轮迭代开始之前,i = p-1, j=p。 在p与i之间没有值。在i+1与和j-1之间也没有值,且x = A[r -1]  因此满足循环不变式。
  2. 保持:当A[j]>x时,循环唯一的操作是j++. j自加后满足在[i,j)的数都大于x。且其它所有项保持不变。  当A[j]<=x时,自加i, 然后交换A[i]和A[j]。因为进行了交换,所以有A[i] <=x,因此满足[p,i]的数都小于等于x。并且A[j-1] > x因为根据循环不变式,被交换进A[j-1]的项目是大于x的。
  3. 终止: 当终止时, j=r-1。在partition过冲中的而最后两行,将主元与最左的,大于x的元素进行交换,就将它移动到数组中间的位置上。因此partition的输出满足分解步骤所做的规定要求。

快排性能最差时是Θ(n2)

平均性能Θ(nlgn)

 

快排的随机化版本

int  randomized-partition(int*  A, int p, int r) {
    int i = random(p,r);
    int tmp = A[r-1]; A[r-1] = A[i]; A[i] = tmp;
   return partition(A, p, r);
}

 新的快速排序过程不再调用partition,而是调用randomized-partition

void quicksort(int* A, int p, int r){
  if (p < r) {
    int q = randomized-partition(A, p, r);
    quicksort(A, p, q);
    quicksort(A, q+1, r);
  }
}

扩展:

快排除了算法导论的版本外,c语言的库函数stdlib.h也提供了一个qsort函数。它的源代码可以从这里下载http://www.gnu.org/software/libc/

代码路径在glibc-2.27/stdlib/qsort.c. 不再累述。

posted @ 2018-02-05 16:26  jackson_zhou  阅读(141)  评论(0)    收藏  举报