快速排序

快速排序是递归的思路谈起来是很简单的:

(1)当待排元素S个数为1的时候,什么也不做。

(2)在待排元素S中取一个元素作为枢纽pivot。

(3)将待排元素分成三份:小于pivot的元素S1、pivot它自己、大于pivot的元素S2。

(4)对小于pivot的元素S1进行快速排序,对大于pivot的元素S2进行快速排序。

但是,具体实现这个思路的时候,是比较复杂的。

对于(2),我们取枢纽元的时候,决定了S1和S2的个数。我们希望S1和S2各占S的一半左右,如此的话,元素可以被细分logN次,每次两份,就是2*logN,对每一份的比较是线性的,为N,快速排序的时间复杂度为O(NlogN)。

但是如果每次S1和S2极度不平衡,例如S2大大超过S1,考虑最坏的情况,S1每次0个,其余都在S2中,则元素要被分N-1次,每次的比较也是线性的,为N,则快速排序的时间复杂度为O(N^2)。

所以枢纽的选择很重要,最糟糕的选法是把第一个元素当成枢纽,在待排元素基本有序的情况下,快速排序的时间复杂度是二次的。最安全的选法是随机选择元素当枢纽。另外有一种流行的并且安全的方法是Median-of-Three Partitioning,其实就是选择第一个,中间一个和最后一个元素,然后把这三个位置根据大小进行排序,并把最小值放在最左边,最大值放在最右边,中间的值放在中间,作为枢纽。之后,我们把枢纽与当前待排序集合的倒数第二个位置(或者最后)交换,那么枢纽右边的值自然是大于枢纽的,不用考虑。方便我们将来的分割。

对于(3),当我们分割元素的时候,要考虑当元素等于枢纽的值的情况。考虑当待排数组的元素的值完全相同,如果我们将枢纽视单独视为划分到S1或S2,那么同样会造成O(N^2)的时间复杂度。所以我们在分割的时候,i和j遇到等于枢纽的情况也要停下。

另外,当我们使用Median-of-THree分割法的时候,遇到少于三个元素的,我们将它丢到插入排序去处理。如此避免我们在快排的实现中考虑额外的特殊情况。这个做法依据这样一个工程实践:

对于很小的数组(N<=20),快速排序不如插入排序好。对于小的数组,我们不递归使用快速排序,而以插入排序这样对小数组有序的排序算法来取代。使用这种策略,针对自始至终使用快速排序的情况,可以节省大约15%的运行时间。

实现的代码如下:

  1 #include <cstdio>
  2 #include <cstdlib>
  3 
  4 #define MINLENGTH 10
  5 
  6 typedef int Item;
  7 // inline使Swap内联展开。提高程序效率。 
  8 void inline
  9 Swap(Item* a, Item* b)
 10 {
 11     if (a != b) {
 12         *a ^= *b;
 13         *b ^= *a;
 14         *a ^= *b;
 15     }
 16 }    
 17 // 在arr中取左中右三个位置,比较。pivot取中间值,将pivot放在right-1的位置上。
 18 // left的位置小于pivot,right的位置大于pivot。 
 19 Item
 20 Median3(Item arr[], int left, int right)
 21 {
 22     int center = (left + right) / 2;
 23     
 24     if (arr[left] > arr[center]) {
 25         Swap(&arr[left], &arr[center]);
 26     }
 27     if (arr[left] > arr[right]) {
 28         Swap(&arr[left], &arr[right]);
 29     }
 30     if (arr[center] > arr[right]) {
 31         Swap(&arr[center], &arr[right]);
 32     }
 33     
 34     Swap(&arr[center], &arr[right-1]);
 35     return arr[right-1];
 36 }
 37 
 38 void
 39 InsertSort(int arr[], int len)
 40 {
 41     for (int i = 1; i < len; i++) {
 42         int tmp = arr[i];
 43         int j;
 44         for (j = i; j >= 1 && arr[j-1] > tmp; j--) {
 45             arr[j] = arr[j-1];
 46         }
 47         arr[j] = tmp;
 48     }
 49 }
 50 // 这里选用i,j遇到等于pivot的情况下停止(因为如果不停止,那会达到O(N^2)。)
 51 // 另外,程序考虑待排数字不少于3个的情况。少于3个的话,需要考虑额外的特殊情况。 
 52 void 
 53 QSort(Item arr[], int left, int right) 
 54 {
 55     if (right - left >= MINLENGTH) {
 56         int pivot = Median3(arr, left, right);
 57         int i = left;
 58         int j = right - 1;
 59         for ( ; ; ) {
 60             while (arr[++i] < pivot) {}
 61             while (arr[--j] > pivot) {}
 62             if (i < j) {
 63                 Swap(&arr[i], &arr[j]);
 64             } else {
 65                 break;
 66             }
 67         }
 68         Swap(&arr[i], &arr[right-1]); // 将pivot放在i的位置上。 
 69         QSort(arr, left, i - 1);
 70         QSort(arr, i + 1, right);
 71     } 
 72     else {
 73         InsertSort(arr + left, right - left + 1);
 74     }
 75 }
 76 
 77 void
 78 QuickSort(Item arr[], int len)
 79 {
 80     QSort(arr, 0, len-1);
 81 }
 82 
 83 int 
 84 main(int argc, char** argv)
 85 {
 86     //Item arr[6] = {17, 11, 2, 23, 5, 7};
 87     Item arr[100000] = {0};
 88     
 89     for (int i = 0; i < 100000; i++) {
 90         arr[i] = 100000/(i+1);
 91     }
 92 /*
 93     for (int i = 0; i < 10000; i++) {
 94         printf("%d\t", arr[i]);
 95     }
 96     printf("\n");
 97     */
 98     QuickSort(arr, 100000);
 99     //InsertSort(arr, 10000);
100     
101     for (int i = 0; i < 100000; i++) {
102         if (arr[i] == 0)
103         printf("%d\t", arr[i]);
104     }
105     printf("\n");
106     
107     system("pause");
108     
109     return 0;
110 }

 

posted @ 2014-10-30 16:23  nipan  阅读(257)  评论(0编辑  收藏  举报