1.快速排序性能的关键点分析
决定快排性能的关键点是每次单趟排序后,key 对数组的分割。如果每次选 key 基本二分居中,那么快排的递归树就是颗均匀的满二叉树,性能最佳。但是实践中虽然不可能每次都是二分居中,但是性能也还是可控的。但是如果出现每次选到最小值/最大值,划分为 0 个和 N-1 的子问题时,时间复杂度为 O(N^2),数组序列有序时就会出现这样的问题。我们前面已经用三数取中或者随机选 key 解决了这个问题,也就是说我们解决了绝大多数的问题,但是现在还有一些场景没解决(数组中有大量重复数据时),类似以下代码。
// 数组中有多个跟 key 相等的值
int a[] = { 6, 1, 7, 6, 6, 6, 4, 9 };
int a[] = { 3, 2, 3, 3, 3, 3, 2, 3 };
// 数组中全是相同的值
int a[] = { 2, 2, 2, 2, 2, 2, 2, 2 };
1.1 三路划分算法思想讲解
当面对有大量跟 key 相同的值时,三路划分的核心思想有点类似 hoare 的左右指针和 lomuto 的前后指针的结合。核心思想是把数组中的数据分为三段【比 key 小的值】【跟 key 相等的值】【比 key 大的值】,所以叫做三路划分算法。结合下图,理解一下实现思想:
- key 默认取 left 位置的值。
- left 指向区间最左边,right 指向区间最后边,cur 指向 left+1 位置。
- cur 遇到比 key 小的值后跟 left 位置交换,换到左边,left++, cur++。
- cur 遇到比 key 大的值后跟 right 位置交换,换到右边,right--。
- cur 遇到跟 key 相等的值后,cur++。
- 直到 cur > right 结束。
1.2 hoare 和 lomuto 和三路划分单趟排序代码分析
数组中有大量重复数据时,快排单趟选 key 划分效果对象:
#include <stdio.h>
#include
{
( i = ; i < n; ++i){
(, a[i]);
}
();
}
{
tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
{
keyi = left;
++left;
(left <= right)
{
(left <= right && a[right] > a[keyi]){ right--; }
(left <= right && a[left] < a[keyi]){ left++; }
(left <= right){ Swap(&a[left++], &a[right--]); }
}
Swap(&a[keyi], &a[right]);
right;
}
{
prev = left;
cur = left + ;
keyi = left;
(cur <= right){
(a[cur] < a[keyi] && ++prev != cur){ Swap(&a[prev], &a[cur]); }
++cur;
}
Swap(&a[prev], &a[keyi]); keyi = prev;
keyi;
}
leftKeyi;
rightKeyi;
} KeyWayIndex;
KeyWayIndex {
key = a[left];
cur = left + ;
(cur <= right){
(a[cur] < key){
Swap(&a[cur], &a[left]);
++cur;
++left;
} (a[cur] > key){
Swap(&a[cur], &a[right]);
--right;
} {
++cur;
}
}
KeyWayIndex kwi;
kwi.leftKeyi = left;
kwi.rightKeyi = right;
kwi;
}
{
a1[] = {, , , , , , , };
a2[] = {, , , , , , , };
a3[] = {, , , , , , , };
PrintArray(a1, (a1)/());
keyi1 = PartSort1(a1, , (a1)/());
PrintArray(a1, (a1)/());
(, keyi1);
PrintArray(a2, (a2)/());
keyi2 = PartSort1(a2, , (a2)/());
PrintArray(a2, (a2)/());
(, keyi2);
PrintArray(a3, (a3)/());
keyi3 = PartSort1(a3, , (a3)/());
PrintArray(a3, (a3)/());
(, keyi3);
}
{
a1[] = {, , , , , , , };
a2[] = {, , , , , , , };
a3[] = {, , , , , , , };
PrintArray(a1, (a1)/());
keyi1 = PartSort2(a1, , (a1)/());
PrintArray(a1, (a1)/());
(, keyi1);
PrintArray(a2, (a2)/());
keyi2 = PartSort2(a2, , (a2)/());
PrintArray(a2, (a2)/());
(, keyi2);
PrintArray(a3, (a3)/());
keyi3 = PartSort2(a3, , (a3)/());
PrintArray(a3, (a3)/());
(, keyi3);
}
{
a1[] = {, , , , , , , };
a2[] = {, , , , , , , };
a3[] = {, , , , , , , };
PrintArray(a1, (a1)/());
KeyWayIndex kwi1 = PartSort3Way(a1, , (a1)/());
PrintArray(a1, (a1)/());
(, kwi1.leftKeyi, kwi1.rightKeyi);
PrintArray(a2, (a2)/());
KeyWayIndex kwi2 = PartSort3Way(a2, , (a2)/());
PrintArray(a2, (a2)/());
(, kwi2.leftKeyi, kwi2.rightKeyi);
PrintArray(a3, (a3)/());
KeyWayIndex kwi3 = PartSort3Way(a3, , (a3)/());
PrintArray(a3, (a3)/());
(, kwi3.leftKeyi, kwi3.rightKeyi);
}
{
TestPartSort1();
TestPartSort2();
TestPartSort3();
;
}


