快速排序相关——基本快排实现,优化,第K大数

快速排序

//部分参考维基百科 https://zh.wikipedia.org/wiki/

目录

快速排序

基本介绍

整体的思路

代码实现

第K大数字:

最坏情况优化:

与堆排序、归并排序的比较 


基本介绍

在平均状况下,排序n个项目要{\displaystyle \ O(n\log n)}次比较。在最坏状况下则需要{\displaystyle O(n^{2})}次比较,但这种状况并不常见。事实上,快速排序{\displaystyle \Theta (n\log n)}通常明显比其他算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地达成;快速排序是不稳定的

使用的思想是分治法,分成两组,递归的解决

最坏情况下

出现在输入完全逆序的情况下

每次划分产生的两组分别包含n-1个和0个元素,整个的运行时间递归式可以表示为:

T(n) = T(n-1)+θ(n),也就是O(n^2)级别

最好情况划分

最平衡的划分中,Partition得到的两个子问题的规模都不大于n/2,这种情况下快速排序的性能非常好

T(n)=2*T(n/2)+θ(n),也即是θ(nlogn)

快速排序的平均运行时间更接近于其最好情况,基本上只要划分是常数比例的,算法的运行时间总是O(nlogn)

整体的思路

1、从待排序数列中选择一个基准值(key)

2、重新排列数组,使得比key小的元素在key的左边,比key大的元素在key右边,这个操作称为分割(partition)

3、递归的把小于key的部分数列和大于key的部分数列进行重排

递归到最底部时,数组的长度为0或1,该算法一定会结束,因为至少会把一个元素摆到后面的位置去

C++中的 sort函数就是实现的快排算法

代码实现

下面给出一种原址排序的C++实现方法:

p是第一个下标位置,r是最后一个数字的下标位置

在Partition中,以最后一个元素为key值进行划分,在剩下的元素中找出比k小的元素,交换到左边 --- 以i记录比key小的最后一个元素的下标,j去找等待被交换回来的小于k的元素的下标

关键是记住i的初始化,先更新i再交换,以及最后返回的key在第i+1位置上

int Partition(int p, int r)
{
	int k = a[r];
	int i = p - 1;
	for (int j = p; j < r; j++)
	{
		if (a[j] <= k)
		{
			i++;
			swap(a[i], a[j]);
		}
	}
	swap(a[i + 1], a[r]);
	return i + 1;
}
void quickSort(int p, int r)
{
	if (p < r)
	{
		int q = Partition(p, r);
		quickSort(p, q - 1);
		quickSort(q + 1, r);
	}
}

另一种方法的区别是Partition部分,采用“挖坑填坑”的思想,用i,j分别从两边找,以最后一个为k的值的话,i从前往后找比k大的,j从后往前找比k小的,直到i==j为止

int Partition2(int p, int r)
{
	int k = a[r];
	int i = p, j = r;
	while (i<j)
	{
		while (i<j && a[i]<k)
		{
			i++;
		}
		if (i < j) { a[j] = a[i]; j--; }
		while (i<j && a[j]>k)
		{
			j--;
		}
		if (i < j) { a[i] = a[j]; i++; }
	}
	a[i] = k;
	return i;
}

第K大数字:

直接复用上面的Partition代码就可以

有几点要注意:返回的索引q在当前数组里是第key大的,不是直接比较q与k; 另外在k>key时,不在求的是第k大,而是第k-key大;

还有递归出口是p==r的情况

int selectKBiggest(int p, int r, int k)
{
	if (p == r)return a[p];
	int q = Partition(p, r);
	int key = q - p+1 ;
	if (k == key)return a[q];
	else if (k < key)
		return selectKBiggest(p, q - 1, k);
	else return selectKBiggest(q + 1, r, k-key);
}

非递归版本:

int selectKUnRec(int p, int r, int k)
{
	while (p <= r)
	{
		int q = Partition(p, r);
		int key = q - p + 1;
		if (k == key)return a[q];
		else if (k < key)r = q-1;
		else { p = q + 1; k = k - key; }
	}
}

只需要一个while就可以了,该上下下标和k值就好了

最坏情况优化:

分治方法,分成的两个数组差不多大时,效率是最高的,所以基准的选择很重要;前面都是选择了固定的最后一个元素作为划分的基准;

在此基础上稍作改进可以是随机的选择基准值,能一定程度提高,但是最坏的情况下还是O(n^2);

进一步的改进是选用三数取中的方法:

最佳的取值应当是待排序数组分成相等的两组,选择中间那个数;但是要精确的找到中间那个数是很费时的,所以可以从三个数中选择中间那个值,实际上选择首、尾和中间值,找到这三个数中中间位置的数作为key即可,这样可以消除预输入不好的情况;当然也可以由三数取中换成五数取中等

但是三数取中优化不了重复数组的情况

在这上面的进一步优化的考虑可以是,在划分到一定程度后,用插入排序来替代继续划分,因为当数组部分有序时,快排没有插入排序效率高;

if (high - low + 1 < 10)
{
	InsertSort(arr,low,high);
	return;
}//else时,正常执行快排

或者在一次划分结束后,将与key相等的元素聚集到一起,划分时不对其进行划分:  可以在划分时,将与key相等的数移动到两端,然后划分结束后再移回中轴附近

还有一种优化思路是,在QuickSort中有两次递归,可以改成一次尾递归:

void QSort(int arr[],int low,int high)
{ 
	int pivotPos = -1;
	if (high - low + 1 < 10)
	{
		InsertSort(arr,low,high);
		return;
	}
	while(low < high)
	{
		pivotPos = Partition(arr,low,high);
		QSort(arr,low,pivot-1);
		low = pivot + 1;
	}
}
--------------------- 
作者:insistgogo 
来源:CSDN 
原文:https://blog.csdn.net/insistGoGo/article/details/7785038 
版权声明:本文为博主原创文章,转载请附上博文链接!

上面的博主实验结果:这里效率最好的快排组合 是:三数取中+插排+聚集相等元素,它和STL中的Sort函数效率差不多

与堆排序、归并排序的比较 

  平均时间复杂度 最好 最坏 辅助空间 稳定性
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1) 不稳定
归并排序 O(nlogn) O(nlogn) O(nlogn) O(1) 稳定
快速排序 O(nlogn) O(nlogn) O(n^2) 看实现 不稳定

猜你喜欢

转载自blog.csdn.net/BeforeEasy/article/details/84995117
今日推荐