快速排序法从思路到实现

快速排序法

从冒泡排序谈起

冒泡排序法体现了数学归纳法的一种朴素应用。以递增(非递减)排序为例,每一轮冒泡将当前数列[a0,a1…an-1]的最大元素交换至数列最右端,从而将数列的无序部分的长度收缩至n-1。经过n-1次冒泡,数列的无序部分长度收缩至1,排序完成。java实现代码如下:

	for(int i = 0; i < array.length; i++){
		for(int j = 0; j < array.length - i - 1; j++){
			if(array[j] < array[j + 1]){
				int t = array[j];
				array[j] = array[j + 1];
				array[j + 1] = t;
			}
		}
	}

冒泡排序的每轮冒泡(即代码中的外层循环)中,数列整个无序部分的所有元素均参与比较,比较运算次数A(n)与数列无序程度无关,交换次数B(n)则受数列无序程度影响且收敛于比较次数。因此,选择比较次数为时间复杂度的计算基准,易得时间复杂度为O(n^2)。

快速排序法:分治策略的应用

由于数学归纳法的特性,问题规模的缩小速度为常数速度(问题规模以n的正比例函数收敛,至1结束)。为了进一步提高排序算法的速度,需要考虑以新的数学思路进行处理,分治法的思路是将一个问题规模为n的大问题分割成两个问题规模为k:n-k的小问题,再继续递归的分割,最终有概率在lg(n)的分割深度下解决问题。
对于排序问题来说,在理想的分治情况下对数列进行分割(即分割点在数列中点),分治效率最高,问题规模的收敛速度最快,需log2(n)趟排序结束。在最坏情况下,按1:n-1分割数列,分治效率最低,需n-1趟结束,事实上快排在这种情况下的时间复杂度将退化为O(n^2)。
下面是快速排序法的数学描述:
(1)从A[0,n-1]中选取枢轴元素
(2)重排A中元素,并将其划分为左右两部分,使得数组中所有比枢轴元素小的元素在左半部分,比枢轴元素大的在右半部分
(3)对左右两部分递归地执行(1)(2)直至子数组长度为1
基于上述思路进行排序,能否有效地将平均情况下的时间复杂度控制到O(nlogn)有两个问题,一是前面提到的分割深度问题,二是处理每一层的2^k(k为分割的深度)个子数组的程序步可否控制在O(n)。可喜的是,问题二的答案是肯定的,而问题一涉及到枢轴元素的选取,优秀的选取策略将能更好地对数列进行分割。
为了减少额外空间的消耗,多数语言对步骤(2)的代码实现都是基于交换的,通过左右哨兵向中间移动,遍历整个数列,直至相遇,是一种很巧妙的元素交换手段。一般情况下,枢轴元素会选择待排序数组的首个元素。
一趟快速排序的算法是(来自百度百科):
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j–),找到第一个小于key的值A[j],将A[j]的值赋给A[i];
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]的值赋给A[j];
5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

案例演示

待排序的数组如下:

下标012345
元素518479
哨兵ij

初始状态下,哨兵i位于数组的最左端,哨兵j位于数组的最右端,枢轴元素a0=5。首先,哨兵j自右向左移动,直至寻找到第一个小于key的元素4,然后与key进行交换,交换后的结果如下所示

下标012345
元素418579
哨兵ij--
由于哨兵自右向左经历的元素已被验证大于key,因此key的右侧已满足分割条件,至此,满足分割条件的区域为[a4,a5](图中用"-"表示),待处理区域为[a0, ...a3]。接下来哨兵i自左向右移动,直至找到大于key的元素8,并与key进行交换,交换后结果如下所示
下标012345
元素415879
哨兵ij
至此,已分割区域为[a0,a1]∪[a4,a5],待处理区域为[a2,a3]。j继续向左移动至i=j。结果如下所示
下标012345
元素415879
哨兵i,j
至此,第一趟快排结束,已分割区域为[a0,a1]∪[a3, ...a5],分割完毕,之后将对[a0,a1]和[a3, ...a5]两个部分继续进行同样步骤,直至分割出的数组长度为1。java实现代码如下:
public void quicksort(int[] a, int left, int right){
	if(right <= left){
		return;
	}
	int l = left;  //左哨兵
	int r = right;  //右哨兵
	int k = left;  //枢轴
	while(l != r){
		int t = 0;
		while((a[r] >= a[k])&&(r > l)){  //当枢轴右方发现第一个小于枢轴的元素,交换
			r--;
		}
		t = a[r];
		a[r] = a[k];
		a[l] = t;
		k = r;
		while((a[l] <= a[k])&&(r > l)){  //当枢轴左方发现第一个大于枢轴的元素,交换
			l++;
		}
		t = a[l];
		a[l] = a[k];
		a[k] = t;
		k = l;		
	}
	//完成一趟枢轴分割后递归地进行分割
	quicksort(a, left, k - 1);
	quicksort(a, k + 1, right);		
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值