快速排序的简单实现

  算法这一块是我的弱项。就以快速排序这样简单的算法,大二学完以后,就没有回顾过了。因为C中有qsort()接口,而C++中也有sort()接口。前一阵子想巩固一下基础知识,回顾了这一著名算法。

  因为大学学过,所以大致知道它的一个过程——也就是一个递归。设给定一序列arr[0...N],首先通过arr[0]将arr[0...N]一分为二(我比较懒,不画图了,大家将就看哈),如下:

  __前半部分,特点:这半部分序列中元素<arr[0]__ arr[0] __后半部分,特点:这半部分序列元素>= arr[0]_ (1)

                                    ↑

                     pilot所在位置(是不是叫pilot的?我忘了,^_^)

  接下来,就是递归排序前半部分和后半部分,递归终止条件就是待排序的序列长度<=1。这个过程是不是很简单?那怎么找arr[0]所在位置,换句话说,怎么把原来的序列一分为二,满足(1)给定的条件?其实,这个问题我一开始也想了好久,第一版代码写出来运行结果还是不对的,经过调试才最后运行正确。

  这里必然要对序列进行遍历,并且遍历过程中要保持一定的要求——就是所谓的循环不变式(《算法导论》一开始就介绍这个概念了)。我们这里要满足的要求(或学术化一点叫循环不变式)可以这么描述:遍历过程中,假设现在循环变量值为i,我们要保证:

          arr'[0]...arr'[pilot-1] < arr'[pilot] <= arr'[pilot+1]...arr'[i]      (2)

我们这里符号用的是arr'(上撇号),这里arr'[pilot]存放的是arr[0],采用不同符号以表示与原始序列不同了。

问题:怎么在遍历过程中,保持(2)成立???

  其实,想到也不难,可能要花点心思。首先把arr[0]保存下来,放在变量pilot_value中,初始的情形如下:

        ____ arr[1] arr[2]...arr[N]

          ↑   ↑

        pilot  i

因为arr[0]保存下来了,所以pilot所指的位置可以认为是没有意义了,所以我在画了一条线——表示这可以看做是空的。遍历从i=1开始,遍历的目的是把所有小于pilot_value的值放到pilot所指位置的左边(如上:初始的时候,pilot左边是没有元素)。现在我们假设循环变量到i+1了,表明前面i个元素满足(2)式要求了,现在就是移动arr[i+1]到合适位置,如下:

        arr'[0]...arr'[pilot-1] < ___ <= arr'[pilot+1]...arr'[i] arr[i+1]                (3)

                     

                    pilot

很简单,就是比较pilot_value和arr[i+1],两种情况咯:

     (1)若 pilot_value <= arr[i+1],不需要做任何操作;

     (2)若pilot_value > arr[i+1],此时:直接将pilot位置上放置arr[i]吗?那就试试看,如果这么做,就会出现如下情况:

              arr'[0]...arr'[pilot-1] arr[i] arr'[pilot+1]...arr'[i] _____              (4)

                                        ↑

                                      新的pilot

这时,pilot应该+1,即箭头指向的位置。我们知道pilot指向的位置在遍历完成后,是要存放arr[0](即:pilot_value)的,而此时pilot指向的是一个有意义的位置。注意(4)中最后一个位置存放的是arr[i+1],这个位置现在存放这个值还有意义吗?显然没意义了!这时只要把arr’[pilot+1]和下划线所在位置的值换一下就好了,这样新的pilot所指向的位置就可以认为没意义了——可以看做是空的了。最后结果如下:

            arr'[0]...arr'[pilot-1] arr[i] _____ arr'[pilot+2]...arr'[i]arr'[pilot+1]        (5)

                           ↑

                         新的pilot

其实现在 新的pilot所指向的位置存放的值时arr[i]。(5)式显然是保持(2)式要求的。遍历完整个序列,得到最后的pilot,然后把pilot_value放到这个位置上,就可以了。最后查找pilot并整理序列以满足(2)式要求的函数实现如下(采用模板):

 1 template <typename Type>
 2 int find_pilot(Type arr[], int iLen) {
 3     int my_pilot = 0;
 4     int pilot_value = arr[0];
 5 
 6     for (int i = 1; i != iLen; ++i) {
 7         if (pilot_value > arr[i]) {
 8             // pilot位置上放上arr[i]
 9             arr[my_pilot] = arr[i];
10 
11             // pilot自增
12             my_pilot++;
13 
14             // pilot和arr[i]交换,以保证pilot指向的值无意义。
15             std::swap(arr[i], arr[my_pilot]);
16         }
17     }
18 
19     arr[my_pilot] = pilot_value;
20     return my_pilot;
21 }

  快速排序就是先通过上述函数将原始序列按pilot分为两个子序列,然后针对两个子序列分别递归调用,代码如下:

 1 template <typename Type>
 2 void quick_sort(Type arr[], int iLen) {
 3     if (iLen <= 1) {
 4         return;
 5     }
 6 
 7     int pilot = find_pilot(arr, iLen);
 8     quick_sort(arr, pilot);
 9     quick_sort(arr + pilot + 1, iLen - pilot - 1);
10 }

  最后,我们测试代码如下:

 1 int main() {
 2     std::cout << "= = = = = 快速排序算法 = = = = =" << std::endl;
 3     std::cout << "排序前的数组:";
 4 
 5     int iTestArray[10] = { 0 };
 6     srand((unsigned int)time(NULL));
 7     for (int i = 0; i != 10; ++i) {
 8         iTestArray[i] = rand() % 100;
 9         std::cout << iTestArray[i] << " ";
10     }
11     std::cout << std::endl;
12 
13     quick_sort(iTestArray, 10);
14 
15     std::cout << "排序后的数组:";
16     for (int i = 0; i != 10; ++i) {
17         std::cout << iTestArray[i] << " ";
18     }
19     std::cout << std::endl;
20 
21     return 0;
22 }

测试结果如下:

原文地址:https://www.cnblogs.com/lijihong/p/4749354.html