三路划分主要是为了解决数组中重复的数数量太多导致的多次递归
按照基准值把数组分成三部分
比基准值小的,比基准值大的,等于基准值的,
相较优化前,如果数组是全由2组成的数组,会递归很多次,但三路划分优化后就很快
三路划分
代码
void kuai_pai_max(int* a, int start, int end)
{
if (start >= end)
return;
if (end - start + 1 < 11)
{
cha_ru(a + start, end - start + 1);
return;
}
int cur = start+1;
int left = start;
int right = end;
int* q = san_zhong(&a[(left + right) / 2], &a[left], &a[right]);
jiao_huan(&a[left], q);
int zhong = a[left];
while (cur <= right)
{
if (a[cur] > zhong)
{
jiao_huan(&a[cur],&a[right--]);
}
else if (a[cur] == zhong)
{
cur++;
}
else
{
jiao_huan(&a[cur++], &a[left++]);
}
}
kuai_pai_max(a, start, left - 1);
kuai_pai_max(a, right + 1, end);
return;
}
一个左指针 left 初始化指向最左边,
cur 表示当前查看的位置初始指向最左边加一
right 右指针初始化指向最右面
定义完是一小步三数取中获得基准值,用整型变量zhong记一下之后开始判断cur位置的值
三种可能,大于,小于,等于基准值
若大于 跟right指针指向的位置交换值,然后right减一
若小于 跟left指针指向的位置交换值,然后left和cur都加一
若等于 cur加一,看下一个值
最后cur大于right时结束循环
左边到left-1就是小于基准值的
right+1到右边就是大于基准值的
left到right就是等于基准值的
原理
cur从左往右走,left左边放入了所有小于基准值的数
right右边放入了所有大于基准值的数
如果不大于基准值,不可能在right右边
如果不小于基准值,不可能在left左边
可以看出left刚开始指向的就是基准值
如果跟cur交换,cur还在left前面加一的位置,换完都加一,left指向的数还是基准值
啥时候cur不在left下一个位置,只有cur指向的值等于基准值时才会cur自己往后走
所以可以得出left指向的是基准值,并且左开右闭区间 [ left , cur ) 里一直都是基准值
cur>right循环才会结束,循环结束的时候cur在right下一位
所以结束时 [ left , right] 区间就都是基准值了。
cur会与right相遇一次,若相遇时right若这个位置不是基准值,
那么right会减一,
刚好在cur前一位。
自省排序
目的:
在上一个优化后加上一个条件,递归层数超过 2 * log n 就别再递归下去了,改用堆排序
正常情况下深度不会超过 2 * log n ,只有出现某些极限的组合才会超过,
那么就不要再往下递归,一条路到底,圈出区间,改为堆排序接着排序
处理特殊情况,保证效率
代码
void zi_sheng(int* a, int start, int end,int n,int m)
{
if (start >= end)
return;
if (end - start + 1 < 11)
{
cha_ru(a + start, end - start + 1);
return;
}
if (n > m)
{
HeapSort(&a[start], end - start + 1);
return;
}
int cur = start + 1;
int left = start;
int right = end;
int* q = san_zhong(&a[(start + end) / 2], &a[start], &a[end]);
jiao_huan(&a[left], q);
int zhong = a[left];
while (cur <= right)
{
if (a[cur] > zhong)
{
jiao_huan(&a[cur], &a[right--]);
}
else if (a[cur] == zhong)
{
cur++;
}
else
{
jiao_huan(&a[cur++], &a[left++]);
}
}
zi_sheng(a, start, left - 1,n + 1, m);
zi_sheng(a, right + 1, end, n + 1, m);
return;
}
跟上一个代码没多大区别
就多传俩值 n m 和 多写个 if
(
每次递归传的就是n+1和一直不变的m
不变的 m 表示最大深度,超过了就换堆排序,n 表示当前深度
判断 n 是否大于 m 了
)
封装 (优化)
形参有 数组地址,区间左边,右边,当前递归深度,最大深度2 * log n
整整5个形参
封装就是让使用者不用传这么多实参
如果知道数组地址,有多少个元素。(size)
就传这俩的话
第一次区间左边就是 0 右边是size - 1 当前递归深度 0 最大深度拿size算一下
可以看出,只需要传入地址和元素个数size就能算出其他
代码
//算 log2(n) 向下取整
int log2_floor(int size)
{
int cnt = 0;
while (size > 1)
{
size /= 2;
cnt++;
}
return cnt;
}
// 封装调用,对外只需要传数组和长度
void Introsort(int* a, int size)
{
if (size <= 1) return;
int max_depth = 2 * log2_floor(size); // 自省最大深度
zi_sheng(a, 0, size - 1, 0, max_depth);
}