10.3.2 快速排序

  一趟快排也称"一次划分",即将待排序列 R[s..t]"划分"为两个子序列R[s..i-1] 和 R[i+1..t],i 为一次划分之后的枢轴位置。可以取待排序列中任何一个记录作为枢轴,但为方便起见,通常取序列中第一个记录 R[s] 为枢轴,以它的关键字作为划分的依据。划分可如下进行:设置两个指针 low 和 high,分别指向待排序列的低端 s 和高端 t。若 R[high].key<R[s].key,则将它移动至枢轴记录之前;反之,若 R[low].key>R[s].key,则将它移动至枢轴记录之后,并为避免枢轴来回移动,可先将枢轴 R[s] 暂存在数组的闲置分量 R[0] 中。如右侧所示为"一次划分"过程的一个例子。一次划分(一趟快速排序)的算法如下:

算法 10.11
  int Partition ( RcdType R[], int low, int high)
 {
  // 对记录子序列 R[low..high] 进行一趟快速排序,并返回枢轴记录
  // 所在位置,使得在它之前的记录的关键字均不大于它的关键字,
  // 而在它之后的记录的关键字均不小于它的关键字

  R[0] = R[low];           // 将枢轴记录移至数组的闲置分量
  pivotkey = R[low].key;        // 枢轴记录关键字
  while (low<high) {         // 从表的两端交替地向中间扫描
   while (low<high && R[high].key>=pivotkey)
    --high;
   R[low++] = R[high];        // 将比枢轴记录小的记录移到低端
   while (low<high && R[low].key<=pivotkey)
    ++low;
   R[high--] = R[low];        // 将比枢轴记录大的记录移到高端
  } // while
  R[low] = R[0];           // 枢轴记录移到正确位置
  return low;             // 返回枢轴位置
 } // Partition

  可以推证,快速排序的平均时间复杂度O (nlogn),在三者取中的前提下,对随机的关键字序列,快速排序是目前被认为是最好的排序方法,如果借用起泡排序中设置记录"交换与否"的布尔变量的作法,快速排序也适用于已经有序的记录序列,详情参见《数据结构题集》题10.9。
 
 
 
 

  "一次划分"过程: 动画

 
  思考题 试对上例中得到的子序列
  ( 90, 45, 39, 54, 68, 87, 76 )
继续进行快速排序,你是否发现什么特殊情况?


  为避免出现枢轴记录关键字为"最大"或"最小"的情况,通常进行的快速排序采用"三者取中"的改进方案,即以 R[s]、R[t] 和 R[(s+t)/2] 三者中关键字介于中值者为枢轴。只要将它和 R[s] 互换,一次划分的算法仍不变。

  关于快速排序时间复杂度的详细证明请读者查看教材所述。