快速排序

快速排序是分治策略的一个应用。

算法思想就是基准元素的划分,这点可以参考书上的分析。

主要给出快速排序算法的分析:

快速排序算法的时间主要耗费在划分操作上,并与划分是否平衡密切相关。对于长度为n的待排序序列,一次划分算法Partition需要堆整个待排序序列扫描一遍,其所需要的计算时间显然为O(n)。

下面从三种情况来讨论一下快速排序算法的时间复杂度。

(1)最坏时间复杂度

最坏情况是每次划分选取的基准元素都是在当前待排序序列中的最小(或最大)元素,划分的结果所基准元素左边的子序列为空(或右边的子序列为空),而划分所得的另一个非空的子序列中元素个数仅仅比划分前的排序序列中元素个数少1个。

在这样的情况下,快速排序算法必须做n-1次划分,那么算法的运行时间T(n)的递归形式为:

                         O(1)                                         n = 1

T(n) =

                          T(n-1) + O(n)                   n > 1

因此,快速排序算法的最坏时间复杂度为O(n*(n+1)/2)=O(n*n)

(2)最好时间复杂度

在最好情况下,每次划分所去的基准元素都是当前待排序序列的“中间值”,划分的结果所基准元素的左右两个子序列的长度大致相同,此时,算法的运行时间T(n)的递归形式为:

                         O(1)                                         n = 1

T(n) =

                         2 T(n/2) + O(n)                   n > 1

解出后得到最好时间复杂度为O(nlogn).

(3)平均时间复杂度

在平均情况下,采用归纳法可求得T(n)的数量级也为O(nlogn).

尽管快速排序的最坏时间复杂度为n*n,但是就平均性能来说,它是基于元素比较的内部排序算法中速度最快的,因此得名:Quick_Sort


快速排序的空间复杂度

因为快速排序采用递归执行,所以需要一个栈来存放每一层递归调用的必要信息,其最大容量与递归调用的深度一致。最好情况下,若每次划分都比较均匀,则递归树的高度为O(logn),故递归需要栈空间为O(logn)。最坏情况下,递归树的高度为O(n),所需要的栈空间为O(n)。平均情况下,所需要的栈空间为O(logn)。


 快速排序也有一个有趣的副产品:快速选择给出的一些数中第k小的数。一种简单的方法是使用上述任一种O(nlogn)的算法对这些数进行排序并返回排序后数组的第k个元素。快速选择(Quick Select)算法可以在平均O(n)的时间完成这一操作。它的最坏情况同快速排序一样,也是O(n^2)。在每一次分割后,我们都可以知道比关键字小的数有多少个,从而确定了关键字在所有数中是第几小的。我们假设关键字是第m小。如果k=m,那么我们就找到了答案——第k小元素即该关键字。否则,我们递归地计算左边或者右边:当k<m时,我们递归地寻找左边的元素中第k小的;当k>m时,我们递归地寻找右边的元素中第k-m小的数。由于我们不考虑所有的数的顺序,只需要递归其中的一边,因此复杂度大大降低。

给出测试代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;

const int N = 10;
int arr[N];

/******************快速排序稳定版本*************************************/
void QuickSort(int low, int high)
{
	if(low >= high)
		return ;
	int i, j, pivot;
	i = low, j = high, pivot = arr[(high + low) >> 1]; //每次取中间元素作为基准
	swap(arr[low], arr[(high + low) >> 1]);
	while(i < j)
	{
		while(i < j && arr[j] >= pivot) --j;
		arr[i] = arr[j];
		while(i < j && arr[i] <= pivot) ++i;
		arr[j] = arr[i];
	}
	arr[i] = pivot;
	QuickSort(low, i - 1); //递归左边
	QuickSort(j + 1, high); //递归右边
}
/*************************************************************************/

/******************快速排序一般版本(可求第K大元素)*****************/
int Partition(int low, int high)
{
	int i, j, pivot;
	i = low, j = high, pivot = arr[low];
	while(i < j)
	{
		while(i < j && arr[j] >= pivot) --j;
		if(i < j) swap(arr[i++], arr[j]);
		while(i < j && arr[i] <= pivot) ++i;
		if(i < j) swap(arr[i], arr[j--]);
	}
	return j; //返回基准元素位置
}

void Quick_Sort(int low, int high)
{
	int pivotpos;
	if(low < high)
	{
		pivotpos = Partition(low, high);
		Quick_Sort(low, pivotpos - 1); //基准左递归排序
		Quick_Sort(pivotpos + 1, high); //基准右递归排序
	}
}

int select(int low, int high, int k)
{
	int pivotpos, num;
	if(low == high)
		return arr[high];
	pivotpos = Partition(low, high);
	num = pivotpos - low + 1; //左边元素个数
	if(k == num)
		return arr[pivotpos];
	else if(k < num)
		return select(low, pivotpos - 1, k);
	else
		return select(pivotpos + 1, high, k - num);
}
/*************************************************************************/

int main()
{
	int k;
	cout<<"输入10个数字:"<<endl;
	for(int i = 0; i < 10; ++i)
		scanf("%d", &arr[i]);
	QuickSort(0, 9);
	cout<<"快速排序后:"<<endl;
	for(int i = 0; i < 10; ++i)
		cout<<arr[i]<<" ";
	cout<<endl;
	cout<<"输入第K大元素:"<<endl;
	cin>>k;
	cout<<select(0, 9, k)<<endl;
	return 0;
}

快速排序稳定版本的思想:

每次取待排序数组的中间元素作为基准,满足排序完成后中间元素左边元素都比它小,右边都比它大。(很稳定,每次基准元素都是中间元素)

我们先取出基准元素 pivot = arr[(high + low) >> 1]; 这样相当于中间位置已空, 因为我们开始从右边找第一个比基准元素小的值并放在左边,而左边初始化没有空余位置,为了防止覆盖元素造成错误,我们将第一个元素和基准元素换一下位置,这实际上相当于我们把arr[low]空出来了(因为我们已经用pivot把基准元素取出来了),这时,数组相当于N-1个元素,arr[low]相当于空,这样,我们在右边找到第一个小于基准元素的时候,就可以把它放在arr[low](覆盖也没有问题,应该理解吧?),这样,右边找到的那个元素的位置又空出来了,接下来我们从左边找第一个比基准元素大的元素,找到后放在刚才右边空出来的位置,这样循环下去,最后肯定是low和high相遇,这时退出后,数组中相当于还是N-1个元素,我们只需要把中间元素用pivot赋值就可以了,因为这次调整就是以它为标准的。。。。这样就得到了一个序列(不是最终结果),保证中间那个基准元素的左边都比它小,右边都比它大。之后,我们递归基准元素的左边进行同样的调整,再递归基准元素的右边进行同样的调整。。当全部完成后,就得到了正确的排序结果。。。。。。。OVER~~~~

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值