快速排序的几个变化形式

  快速排序最简单的区间切分形式已经在前面的博文中介绍过了,是单向处理的,下面介绍快速排序的另几种形式,都是双向处理,即处理模式是,比Pivot小的往左移,比Pivot大的往右移,当两个方向相交后,把Pivot移动到相交位置往后的一个位置,最后形成三段,分别为比Pivot大,等于Pivot,大于Pivot。双向处理比单向处理的好处是,比Pivot大的元素一次移到右边后不需要再次移动,而单向处理里面,比Pivot大的元素则要不停的一次次往右移。

  第一种:直接取最右边的元素为Pivot,此种切分方式代码如下:

 1 int Partition1(int* pArr, int l, int r) {
 2     //此分割直接取最右边的元素作为pivot,所以在j的循环中,需要判断是否越界,因为一直到最左边可能没有比pivot小的元素,而不i不需要,因为最右边就是Pivot
 3     //找个分别比pivot小的和大的元素,然后交换位置,如果pivot是最大的,则不会交换任何元素(会交换最后一次,和本身)
 4     int i = l - 1;
 5     int j = r - 1;
 6     int pivot = pArr[r];
 7     while (true) {
 8         while (pArr[++i] < pivot) {}
 9         while (pArr[j] > pivot) {
10             --j;
11             if (j < l)
12                 break;
13         }
14 
15         if (i >= j) //可以取=,表示指向了同一个位置,这时候无需交换值
16             break;
17         Swap(pArr + i, pArr + j);
18     }
19     Swap(pArr + j + 1, pArr + r);
20     return j + 1;
21 }

  第二种:随机化切分元素Pivot

  第一种方式的问题是,如果数组是降序排好了的,那取最右边的元素就是最坏的选择,因为取到了最小的元素,最后算法一轮下来,只把自己移到最左边,其它元素都没有动。随机化选择Pivot,可以获得较好的平均期望性能,这里代码就不贴了,跟前面的一样,只是pivot是随机取的,然后将随机的数切换到最右边即可。这样做的前提是数组中所有元素出现的概率是等随机的

  第三种:三取样切分的快速排序

  意思是说取l,(l+r)/2,r的三个索引位置的元素,然后把最小的元素放左边,中间大的元素作为Pivot放在最右边,再对数组进行切分,之所以用中间大的元素,是因为这样可以获得平均期望性能。此外还可以按第一种形式,随机三个元素,然后再按这种方式排序后,再进行切分。

  三取样这样做的好处,一是除了可以避免取到最坏的元素拿来做切分,二是还可以去掉第一二种情况里面的while循环的越界检查。代码如下:

 1 void _Sort3(int* pArr, int l, int r) {
 2     if (r - l >= 2) {
 3         int m = _Partition3(pArr, l, r);
 4         _Sort3(pArr, l, m - 1);
 5         _Sort3(pArr, m, r);
 6     }
 7     else {
 8         if (pArr[l] > pArr[r])
 9             Swap(pArr + l, pArr + r);
10     }
11 }
12 
13 int Partition3(int* pArr, int l, int r)  {
14     //此分割取左中右三个元素,然后取中间大的元素作为pivot,所以在j的循环中,无需判断j >= 0,因为到最左边一定有比pivot小的元素
15     //如果是已经排好序的,则也不会交换任何元素,只会交换最后一次,和本身
16     int m = (r + l) / 2;
17     if (pArr[l] > pArr[r])
18         Swap(pArr + l, pArr + r);
19     if (pArr[r] > pArr[m])
20         Swap(pArr + r, pArr + m);
21     if (pArr[l] > pArr[r])
22         Swap(pArr + l, pArr + r);
23 
24     int i = l;
25     int j = r;
26     int pivot = pArr[r];
27 
28     while (true) {
29         //当i达到最右的r时,pArr[i]==pivot, while中断,不会越界
30         while (pArr[++i] < pivot) {}
31 
32         //当j达到最左的l时,pArr[l]<pivot, while中断,不会越界
33         while (pArr[--j] > pivot) {}
34 
35         if (i >= j)
36             break;
37 
38         Swap(pArr + i, pArr + j);
39     }
40     Swap(pArr + (++j), pArr + r);
41     return j;
42 }

  第四种:三取向切分,针对大量重复元素的快速排序

  直接取最右边元素为pivot,如果能取到最多的相同元素作为Pivot那最好了,可以最大限度的减少数据移动,代码如下:

 1 void QuickSort(int* pArr, int l, int r) {
 2     if (r > l) {
 3         int i = l;
 4         int pivot = pArr[r];
 5         int low = l;
 6         int high = r;
 7         while (i <= high) {
 8             if (pArr[i] < pivot) {
 9                 //这一步,除非下面的两个else有执行到,否则全部是跟自己本身在交换值
10                 Swap(pArr + (low++), pArr + i++);
11                 //比pivot小的元素,都往左边移,low只有在这时候才增加
12             }
13             else if (pArr[i] > pivot) {
14                 Swap(pArr + (high--), pArr + i);
15                 //比pivot大的元素,都往右边移,high只有在这时候才减小
16             }
17             else {
18                 ++i;
19             }
20         }
21         QuickSort(pArr, l, low - 1);
22         QuickSort(pArr, high + 1, r);
23     }
24 }

 

posted on 2018-04-28 00:55  凄夜  阅读(404)  评论(0编辑  收藏  举报

导航