快速排序的几个变化形式
Posted zengyg
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了快速排序的几个变化形式相关的知识,希望对你有一定的参考价值。
快速排序最简单的区间切分形式已经在前面的博文中介绍过了,是单向处理的,下面介绍快速排序的另几种形式,都是双向处理,即处理模式是,比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 }
以上是关于快速排序的几个变化形式的主要内容,如果未能解决你的问题,请参考以下文章