快速排序有多快?

快速排序算法

快速排序算法是在实际运用当中运用最广泛的,也是在面试中被问到最频繁的。
下面我们先看看它的实现过程:先从数组arr中选一个数作为哨兵(mid),然后利用left从数组的左边向中间遍历,遇到arr[left]>arr[mid]时停下,利用right从数组的右边向中间遍历,遇到arr[right]<arr[mid]时停下,然后交换arr[left]和arr[right]的值,当leftright相遇时,将arr[mid]置于中间,此时arr[mid]左边的数都是小于arr[mid]的,arr[mid]右边的数都是大于arr[mid]的,然后分别对arr[mid]两边的数重复上述操作,直到数组被分解为只有一个元素的时候,此时数组已经完成排序,下面看图演示,排序的过程:
在这里插入图片描述
快速排序算法使用了分治思想,它的分治过程正如上面演示的:
(1)分解:将数组A[p…r]分解成两个子数组A[p…q-1]和A[q+1…r],使得A[p…q-1]的元素都小于A[q],A[q+1…r]的元素都大于A[q],正如上面的数组arr,5左边和右边的元素分别是数组arr的两个子数组。
(2)解决:通过递归调用快速排序,对两个子数组进行排序

我们来看看它的递归实现,看不懂没关系,接下来我们进行时间复杂度分析的时候,会对理解递归过程有很大帮助。

#include <iostream>
#include <vector>
#include <memory>
using namespace std;
int quik(vector<int>&arr, int left, int right)     //完成一次排序
{
	int left1 = left;
	int key = arr[left];
	while (left < right)
	{
		int temp;
		while (arr[right] >= key && right>left)
			right--;
		while (arr[left] <= key &&right>left)
			left++;
		temp = arr[left];
		arr[left] = arr[right];
		arr[right] = temp;
	}
	//左右哨兵相遇的时候 将key值放到相遇点
	for (int i = left1;i < left;i++)
	{
		arr[i] = arr[i + 1];
	}
	arr[left] = key;
	return left;
}
void quick_sort(vector<int>&arr,int left,int right)
{
	if (left >= right)  //递归的终止条件  当数组只有一个元素的时候 自然是有序的所以不能再分解了
	{
		return;
	}
	int index = quik(arr,left,right);
	//分为左右两个子数组
	quick_sort(arr,0,index-1);    
	quick_sort(arr,index+1,right);
}
int main() {
	vector<int>arr= {4,2,9,5,3,10,6};
	int size=arr.size();
	quick_sort(arr,0,size-1);
	for (int i = 0;i < size;i++)
		cout << arr[i] << " ";
	cout << endl;
	system("pause");
	return 0;
}

快速排序的性能分析:

最坏情况下的划分:

什么情况下是最坏的呢?如果每次分解的两个子数组都有一个是空的,这时候就是最坏的情况。假设每一次进行划分的时候都是最坏的结果,我们知道划分的时间复杂度为n,当n=0时,递归调用会直接返回,所以T(0)=1;所以最坏情况下的时间运行可以表示为T(n)=T(n-1)+n;其实就是一个差值为1的等差数列n+n-1+n-2+…+1;所以T(n)=n(n+1)/2;即时间复杂度为O(n^2);

最好情况下的划分:根据快速排序的思想,每次分解我们都希望分解为两个大小一样的子数组,这种情况是最好的也是我们最想要的。

最好情况下的时间复杂度可以表示为T(n)=2T(n/2)+n;这个怎么求解呢?
在这里插入图片描述
如图 我们可以看到,将数组一步步划分,最终划分为了lgn +1层的树,每一层处理的时间复杂度为n,所以他的总代价为n(lgn + 1)=nlgn+n=nlgn;也即
T(n)= 2T(n/2)+n=O(nlgn)
结论:
快速排序是一种最坏情况时间复杂度为O(n^2)的排序算法,虽然最坏情况的时间复杂度很差,但是快速排序在实际应用中是最好的选择,因为它的平均性能很好,实际上,在快速排序中,只要每次进行划分的比例是常数的的,算法的时间复杂度总是O(nlgn)

实际上我们也可以采取一些措施来防止最坏情况的发生,比如每次选取哨兵的时候,不一定选择数组的第一个元素,可以选着第一个元素、中间元素和最后一个元素这三个元素中的中位数作为哨兵,这样可以有效防止最坏情况的发生。

快速排序的非递归该怎么实现呢?

实际上只需要通过栈先将左右哨兵入栈,然后判断栈是否为空,依次从栈中取出左右哨兵left和right,再调用quik()排序函数,此时产生左右两个子数组,同样依次将两个子数组的左右哨兵入栈,重复上述操作,当栈为空时,完成排序。

#include <iostream>
#include <vector>
#include <stack>
#include <memory>
using namespace std;
int quik(vector<int>&arr, int left, int right)   //完成一次分解
{
	int left1 = left;
	int key = arr[left];
	while (left < right)
	{
		int temp;
		while (arr[right] >= key && right>left)
			right--;
		while (arr[left] <= key &&right>left)
			left++;
		temp = arr[left];
		arr[left] = arr[right];
		arr[right] = temp;
	}
	//左右哨兵相遇的时候 将key值放到相遇点
	for (int i = left1;i < left;i++)
	{
		arr[i] = arr[i + 1];
	}
	arr[left] = key;
	return left;
}

int main() {
	vector<int>arr = { 4,2,9,5,3,10,6 };
	int size = arr.size();
	stack<int> stac;
	stac.push(0);
	stac.push(size-1);
	while (!stac.empty())
	{
		int right = stac.top();
		stac.pop();
		int left = stac.top();
		stac.pop();
		int index = quik(arr,left,right);
		if (index>left)
		{
			stac.push(left);
			stac.push(index-1);
		}
		if (index < right)
		{
			stac.push(index+1);
			stac.push(right);
		}

	}
	for (int i = 0;i < size;i++)
		cout << arr[i] << " ";
	cout << endl;
	system("pause");
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值