快速排序的深入优化——三路划分,内省排序(C语言)

1. 快排性能的关键点分析

决定快排性能的关键点是每次单趟排序后,key对数组的分割,如果每次选key基本⼆分居中,那么快 排的递归树就是颗均匀的满⼆叉树,性能最佳。但是实践中虽然不可能每次都是⼆分居中,但是性能 也还是可控的。但是如果出现每次选到最⼩值/最⼤值,划分为0个和N-1的⼦问题时,时间复杂度为 O(N^2),数组序列有序时就会出现这样的问题,我们前⾯已经⽤三数取中或者随机选key解决了这个问题,也就是说我们解决了绝⼤多数的问题,但是现在还是有⼀些场景没解决(数组中有⼤量重复数据 时),例如下面:

多个key值相等,或者全是相同的数。

cpp 复制代码
int a[] = { 6,1,7,6,6,6,4,9 };
int a[] = { 3,2,3,3,3,3,2,3 };
int a[] = { 3,3,3,3,3,3,3,3 };

2.三路划分算法

这里使用三路划分可以提升当数组中,有大量相同的值时,排序的时间效率,三路划分的核⼼思想有点类似hoare的左右指针和lomuto的前后指针的结合(这里的hoare的左右指针和lomuto的前后指针在小编前面"各种排序方法"文章中有所讲解)。核⼼思想是把数组中的数据分为三段**⽐key⼩的值;跟key相等的值;⽐key⼤的值**,所以叫做三路划分算法。

实现的思想:

  1. key默认取left位置的值。

  2. left指向区间最左边,right指向区间最后边,cur指向left+1位置。

  3. cur遇到⽐key⼩的值后跟left位置交换,换到左边,left++,cur++

  4. cur遇到⽐key⼤的值后跟right位置交换,换到右边,right--。

  5. cur遇到跟key相等的值后,cur++。

  6. 直到cur>right结束

排序成这样以后,再递归left之前的数组,和right之后的数组就可以了。

注意:这种排序方法,主要是当数组中有大量相同的数据时,时间效率较高,但如果数组中没有大量相同的数据,这种算法的时间效率甚至还不如,lomuto和hoare版的快速排序。

三路划分针对有⼤量重复数据时,效率很好,其他场景就⼀般,但是三路划分思路还是很有价 值的,有些快排思想变形体,要⽤划分去选数,他能保证跟key相等的数都排到中间去,三路划分的价值就体现出来了。

cpp 复制代码
void _QuicksortPro(int* arr, int left, int right)//三路划分:有大量相同数据
{
	if (left >= right)
		return;
	int begin = left;
	int end = right;
	int randi = left + (rand()%(right - left + 1));
	Swap(&arr[left], &arr[randi]);
	int key = arr[left];
	int cur = left + 1;
	while (cur <= right)
	{
		if (arr[cur] < key)
		{
			Swap(&arr[left++], &arr[cur++]);
		}
		else if (arr[cur > key])
		{
			Swap(&arr[cur], &arr[right--]);
		}
		else
		{
			cur++;
		}
	}
	_QuicksortPro(arr, begin, left - 1);
	_QuicksortPro(arr, right + 1, end);
}

void Quicksort(int* arr, int left, int right)
{
	srand(time(0));
	_QuicksortPro(arr, left, right);
}

3.introsort的快排排序(内省排序)

introsort是由DavidMusser在1997年设计的排序算法,C++sgi STL sort中就是⽤的introspective sort(内省排序)思想实现的。内省排序可以认为不受数据分布的影响,⽆论什么原因划分不均匀, 导致递归深度太深,他就是转换堆排了,堆排不受数据分布影响。

首先确定递归深度达到多少时,深度一般为logn的两倍(数据个数取二的对数:2*logn),就停止这种排序转换成堆排序(堆排序,在小编之前"堆的应用"文章中已有详细讲解);

其次当数组长度小于十六时,就直接使用插入排序,简单递归次数;

代码如下:

cpp 复制代码
//直接插入排序
void InsertSort(int* arr, int size)
{
	for (int i = 0; i < size-1; i++)
	{
		int end = i;
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			if (tmp < arr[end])
			{
				arr[end + 1] = arr[end];
			}
			else {
				break;
			}
			end--;
		}
		arr[end + 1] = tmp;
	}
}

//堆排序
void AdjustDwon(int* a, int n, int root)
{
	int parent = root;
	int child = 2 * parent + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child += 1;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapSort(int* a, int n)
{
	int parent = (n - 1 - 1) / 2;
	while (parent >= 0)
	{
		AdjustDwon(a, n, parent);
		parent--;
	}
	int size = n - 1;
	while (size > 0)
	{
		Swap(&a[0], &a[size]);
		AdjustDwon(a, size, 0);
		size--;
	}
}

void IntroSort(int* arr, int left, int right, int depth, int defaultDepth)
{
	if (left >= right)
	{
		return;
	}
	if (right - left + 1 < 16)
	{
		InsertSort(arr + left, right - left + 1);
			return;
	}
	if (depth > defaultDepth)
	{
		HeapSort(arr+ left, right - left + 1);
		return;
	}
	depth++;

	int begin = left;
	int end = right;

	int randi = left + (rand() % (right - left + 1));
	Swap(&arr[randi], &arr[left]);

	int keyi = left;
	int prev = left, cur = left + 1;
	while (cur <=right)
	{
		if (arr[cur] < arr[keyi] && ++prev != cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		cur++;
	}
	Swap(&arr[keyi], &arr[prev]);
	keyi=prev;

	IntroSort(arr, begin, keyi - 1, depth, defaultDepth);
	IntroSort(arr, keyi+1, end, depth, defaultDepth);
}

void QuickSort(int *arr,int left,int right)
{
    int depth = 0;
    int logn = 0;
    int size=right-left+1; 
    for (int i = 1; i < size; i*=2)
    {
    	logn++;
    }
    IntroSort(arr, left, right, depth,logn*2);
}
相关推荐
Ethan Hunt丶31 分钟前
C语言实现IIR型零相位带通滤波器
c语言·开发语言·算法
孑么1 小时前
力扣 最小路径和
java·算法·leetcode·职场和发展
geng小球1 小时前
LeetCode 93-复制 IP地址
java·算法·leetcode
AnFany1 小时前
LeetCode【0019】删除链表的倒数第N个结点
python·算法·leetcode·链表·快慢双指针
pen-ai1 小时前
【数据科学】1. 假设检验
人工智能·算法·机器学习·数据挖掘·数据分析
神秘的土鸡2 小时前
机器情绪及抑郁症算法
人工智能·算法
鬣主任2 小时前
二叉树的前序遍历---一个简单高效的算法
数据结构·算法
jianbaigreat3 小时前
代码随想录打卡Day22、23、24、25
数据结构·算法
_OLi_3 小时前
力扣 LeetCode 206. 反转链表(Day2:链表)
算法·leetcode·链表
运维&陈同学3 小时前
【HAProxy05】企业级反向代理HAProxy调度算法之静态算法与动态算法
linux·运维·算法·nginx·云原生·负载均衡·lvs·haproxy