快速排序的三路划分和自省排序

三路划分主要是为了解决数组中重复的数数量太多导致的多次递归

按照基准值把数组分成三部分

比基准值小的,比基准值大的,等于基准值的,

相较优化前,如果数组是全由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);
}
相关推荐
qq_296553272 小时前
矩阵转置的两种实现方式:从暴力法到原地算法
数据结构·线性代数·算法·青少年编程·矩阵
2zcode2 小时前
滚压表面强化过程中变形诱导位错演化与梯度晶粒细化机理的数值模拟研究
人工智能·python·算法
渣渣苏2 小时前
硬核拆解 HNSW:亿级向量如何实现毫秒级召回?(下篇:实战调参与工程优化)
人工智能·算法·agent·向量数据库·hnsw·智能体
Felven2 小时前
A. Candies for Nephews
算法
白藏y2 小时前
【算法】常见基础算法
算法
shylyly_2 小时前
内存函数的使用和实现
数据结构·算法
时空自由民.2 小时前
两轮平衡车控制系统
算法