快速排序的几种方法及其优化

本文详细介绍了快速排序算法,包括其核心思想、三种单趟排序方法、递归与非递归实现方式,以及两种优化策略。同时分析了快速排序的性能特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       快速排序的整体思想是分治的思想,也就是说首先对一个大区间进行单趟排序,然后将大区间又进行分割,然后再进行单趟排序,直到这个区间剩下单个元素就不再分割了,小区间有序了返到上一层大区间就有序了,也就是递归思想。

下面是一个快排的动图:



一.根据以上分析,单趟排序是将复杂问题简单化的关键,下面就来分析一下单趟排序的几种方法:

1.左右指针法

步骤可分为:

(1)选择一个关键字,可通过三数取中法来对其进行优化,避免每次取得最大或最小数,从而降低了效率;

(2)给定下标left,right相当于左右两个指针,维护这段区间;左边找一个比关键字大的数,右边找比关键字小的数,然后进行交换,直到left与right指向同一位置,再将关键字换到left或right指向的这个位置,即满足关键字的左区间元素小于关键字,其右区间元素大于关键字;

(3)区间分割再重复上述两个步骤。

//左右指针法
int PartSort1(int* a,int begin,int end)
{
	int left=begin;
    int right=end;
	//用三数取中法来对其进行优化,避免每次取得最大或最小数
	int mid=GetMidOfThree(a,begin,end); 
	std::swap(a[mid],a[end]);
	int key=a[end];
	while(left < right)
	{
		while(left<right && a[left]<=key)
		{
			++left;
		}

		while(left<right && a[right]>=key)
		{
			--right;
		}
		if(left < right)
		{
			std::swap(a[left],a[right]);
		}
	}
	//left==right
	std::swap(a[left],a[end]);
	return left;
}


2.挖坑法

它的思想见下图分析:



//挖坑法
int PartSort2(int* a,int begin,int end)
{
	int left=begin;
	int right=end;
	int mid=GetMidOfThree(a,begin,end); 
	std::swap(a[mid],a[end]);
	int tmp=a[end];
	while(left < right)
	{
		while(left < right && a[left]<=tmp)
		{
			++left;
		}
		a[right]=a[left];   //左边找比tmp大的,右边找比它小的

		while(left <right && a[right]>=tmp)
		{
			--right;
		}
		a[left]=a[right];
	}
	//此时left=right
	a[left]=tmp;
	return left;
}

3.前后指针法

分析入下图,这里需要明确一点,prev指针不能定义为-1,因为begin代表的是一段区间的开始,prev起初指向的是cur的前一位置,cur的作用是找一个比关键字小的,找到后prev++,然后将cur和prev指向的元素进行交换,当cur指向end时,将prev指向的下一个值与end指向的这个值进行较换,如此便可完成单趟排序,具体分析过程如下:



//前后指针法
int PartSort3(int* a,int begin,int end)
{
	int prev=begin-1;
	int cur=begin;
	int mid=GetMidOfThree(a,begin,end); 
	std::swap(a[mid],a[end]);
	int key=a[end];
	while(cur<end)
	{
		//只有a[cur]<key时,prev才往后走
		if(a[cur]<key)
		{
			++prev;
			if(prev!=cur)
				std::swap(a[cur],a[prev]);
		}
		++cur;
	}
	++prev;     
	std::swap(a[prev],a[end]);
	return prev;
}


二.快排的两种算法

1.递归快排

其思想就是先对整个区间进行单趟排序,然后不断分割,使得各个子区间都有序,实现如下:

void QuickSort(int* a,int begin,int end)
{
	assert(a);
	//int div=PartSort1(a,begin,end);
	//int div=PartSort2(a,begin,end);
	int div=PartSort3(a,begin,end);
	if(div-1 > begin)
		QuickSort(a,begin,div-1);

	if(div+1 < end)
		QuickSort(a,div+1,end);
}

2.非递归快排

递归其实就是不断压栈,然后满足递归结束条件后又层层往上返的过程,那么我们同样也可以用栈的方式来模拟递归的过程,如下实现:

//非递归快速排序
void QuickSortNonR(int* a,int begin,int end)
{
	stack<int> s;
	s.push(end);
	s.push(begin);
	while(!s.empty())
	{
		int left=s.top();
		s.pop();
		int right=s.top();
		s.pop();

		//int div=PartSort1(a,left,right);
		//int div=PartSort2(a,left,right);
		int div=PartSort3(a,left,right);
		if(left < div-1)
		{
			s.push(div-1);
			s.push(left);
		}
		if(div+1 < right)
		{
			s.push(right);
			s.push(div+1);
		}
	}
}


三.快排的两种优化

1.前面有提到三数取中法,这种方式用来对快排进行优化还是挺常见的,它的目的就是避免出现我们选出的关键字是最大值或最小值,可以反过来思考如果我们所选关键字都是最大的或最小的,那么一趟快速排序就变成了一趟冒泡排序,从而效率就降低了。每次取得一个中间值,可使快排的时间复杂度尽量接近O(N*lgN).

//三数取中,可避免每次取到的数是最大的或最小的,如果取到关键字为中间值则可提高效率
//使比它小的尽快排到前面去,使比它大的尽快的排到后面去
int GetMidOfThree(int* a,int begin,int end)
{
	//注意符号优先级问题
	int mid=begin+((end-begin)>>1);
	if(a[begin]<a[mid])
	{
		if(a[mid] < a[end])  //3 4 5
			return mid;

		else if(a[mid] > a[end])  //3 5 4
			return end;
		else
			return begin;
	}
	else //a[begin]>a[mid]
	{
		if(a[mid]<a[end])    //4 3 5
			return begin;
		else if(a[mid]>a[end])        //4 3 2
			return mid;
		else 
			return end;
	}
}

2.减少递归栈使用的优化,快速排序的实现需要消耗递归栈的空间,而大多数情况下都会通过使用系统递归栈来完成递归求解。对系统栈的频繁存取会影响到排序的效率。

快速排序对于小规模的数据集性能不是很好,没有插入性能高。

快速排序算法使用了分治技术,最终来说大的数据集都要分为小的数据集来进行处理。

当数据集较小时,不必继续递归调用快速排序算法,使用插入排序代替快速排序。STL中sort就是用的快排+插入排序的,使得最坏情况下的时间复杂度也是O(N*lgN).这一改进被证明比持续使用快速排序算法要有效的多。

当划分的子序列很小的时候,一般认为小于13个元素的时候,插入排序优于快排.因为快速排序对数组进行划分就像一棵二叉树一样,当序列个数小于13的时候,再使用快排的话就相当于增加了二叉树最后几层的节点数目,也就增加了递归的次数,所以我们在当子序列小于13个元素的时候采取直接插入来对这些子序列进行排序。

void QuickSort(int* a,int begin,int end)
{
	assert(a);
	if(end-begin >13)
	{
		//int div=PartSort1(a,begin,end);
	    //int div=PartSort2(a,begin,end);
		int div=PartSort3(a,begin,end);
	    if(div-1 > begin)
		   QuickSort(a,begin,div-1);

	    if(div+1 < end)
		   QuickSort(a,div+1,end);
	}
	else
	{
		InsertSort(a+begin,end-begin+1);  //元素个数需注意
	}
	
}


四.快排的性能分析:

快速排序是一种分而治之的算法,它是已知的最快的排序算法,其平均运行时间为O(N*1gN) 。它的速度主要归功于一个非常紧凑的并且高度优化的内部循环。但是它也是一种不稳定的排序,当基准数选择的不合理的时候它的效率又会变成O(N*N)。

快速排序的最好情况:
  快速排序的最好情况是每次选出的关键字接近中位数,其运行的时间就为O(N*1ogN);
快速排序的最坏情况:
  快速排序的最坏的情况就是当分组重复生成一个空序列的时候(比如说,序列本就有序),这时候其运行时间就变为O(N*N)。
以前我们总说时间复杂度要看最坏情况,现在补充一点当最坏情况出现的可能性很小的时候我们就不再以最坏情况下来分析时间复杂度。

快速排序的空间复杂度其实不再是O(1),而是O(lgN).


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值