快速排序——学习笔记

早已听过快排的大名,今天去学了快排的两种实现方式,简单记一下笔记

主算法

快排和归并排序均为分而治之,快排也是将原序列划分为两个规模更小的子序列,然后递归地进行排序,但与归并排序不同的是,快排在划分子序列的时候要求:前一序列中的任何元素都不得超过后一序列中的各个元素。因此,在对前一子序列和后一子序列进行排序之后,只需简单将二者串接起来,原序列自然有序。
平凡解(递归基):只剩单个元素时,本身就是有序的解。
因此mergesort的计算量和难点在于,而quicksort在于

轴点

引入轴点(pivor) 的概念:左/右侧的元素,均不比它更大/小

图摘自清华大学数据结构慕课电子讲义

在这里插入图片描述
以轴点为界,原序列的划分自然实现:
[ l o , h i ) = [ l o , m i ) + [ m i ] + ( m i , h i ) [lo, hi)=[lo, mi)+[mi]+(mi, hi) [lo,hi)=[lo,mi)+[mi]+(mi,hi)
注意,每次将mi找到后,均不再将其划分到前缀或后缀中去,而是将其位置固定住。

void quicksort(int lo, int hi)
{
	if (hi - lo < 2)	//单元素区间自然有序
		return;
	int mi = partition(lo, hi);		//先构造轴点,再
	quicksort(lo, mi);		//[lo, mi)前缀排序
	quicksort(mi+1, hi);	//[mi+1, hi)后缀排序	
							//注意轴点找出便不再去动它,因此这里两个递归都不包含已找出的轴点mi
}

在有序序列中,所有元素皆为轴点,反之亦然
因此,快速排序就是将所有元素逐个转换为轴点的过程

快速划分:LUG版

算法流程

  1. 任取一个元素作为候选轴点,将其与首元素互换位置,并记录下该轴点的值。记录之后,lo可在逻辑上看做空闲状态
  2. 从hi-1开始,逆序依次与轴点比较,若不小于轴点,则令hi–,即将该元素归入G中。直到末元素hi不满足此条件,将该元素移至空闲单元lo中(因为此时lo在逻辑上被看做空闲状态,因此可直接赋值,而不用调用swap),赋值完毕后单元hi变为空闲。转入步骤3
  3. 若首元素lo不大于轴点,则令lo++,即将该元素归入子序列L中。直到首元素lo不满足此条件,同样将该元素的值存入单元hi中,此时单元lo变为空闲。转入步骤2
  4. 通过在外层叠加一层循环while(lo<hi),反复执行2、3两步骤。直到lo=hi时,退出循环,此时只剩下空闲单元lo,将记录好的轴点数值放入单元lo中,并返回轴点的秩lo

在以上过程当中,始终保持如下不变性
L ≤ p i v o t ≤ G L\leq pivot\leq G LpivotG U = [ l o , h i ) U=[lo, hi) U=[lo,hi中,单元 [ l o ] [lo] [lo] [ h i ] [hi] [hi]交替空闲

int partition1(int lo, int hi)		//[lo, hi)
{
	swap(nums[lo], nums[lo + rand() % (hi - lo)]);
	int pivot = nums[lo];
	hi--;
	while (lo < hi)
	{
		while ((lo < hi) && (pivot <= nums[hi]))
			hi--;
		nums[lo] = nums[hi];		//由于这种版本当前将lo位置视为逻辑上的空闲位置,所以才能直接复制;其他算法需用swap交换二者
		while ((lo < hi) && (nums[lo] <= pivot))
			lo++;
		nums[hi] = nums[lo];		//由于这种版本当前将hi位置视为逻辑上的空闲位置,所以才能直接复制;其他算法需用swap交换二者
	}
	nums[lo] = pivot;
	return lo;
}

性能分析

算法不稳定(unstable)就地算法(in-place algorithm)

  • 空间O(1) 附加空间
  • 时间:单次partition算法为O(n),而总体上:
    -最好:每次划分都(接近)平均,轴点总是(接近)中央: T ( n ) = 2 T ( ( n − 1 ) / 2 ) + O ( n ) = O ( n l o g n ) T(n)=2T((n-1)/2)+O(n)=O(nlogn) T(n)=2T((n1)/2)+O(n)=O(nlogn)
    最坏:每次划分都极不均衡(比如轴点总是最小/最大元素): T ( n ) = T ( n − 1 ) + T ( 0 ) + O ( n ) = O ( n 2 ) T(n)=T(n-1)+T(0)+O(n)=O(n^2) T(n)=T(n1)+T(0)+O(n)=O(n2)
    平均 O ( n l o g n ) O(nlogn) O(nlogn)

为降低最坏情况出现的概率,每次采用随机选取轴点的方法;或每次选取三个元素,选择数值居中的元素(三者取中)。但这些策略也只能降低最坏情况的概率,而无法杜绝

快速划分:LGU版

算法流程

将整个区间划分为四个区间:
S = [ l o , h i ) = [ l o ] + ( l o , m i ] + ( m i , k ) + [ k , h i ) = p i v o t + L + G + U S=[lo, hi)=[lo]+(lo, mi]+(mi, k)+[k, hi)=pivot+L+G+U S=[lo,hi)=[lo]+(lo,mi]+(mi,k)+[k,hi)=pivot+L+G+U
L < p i v o t < G L<pivot<G L<pivot<G

图摘自清华大学数据结构慕课电子讲义

在这里插入图片描述

  1. 任取一个元素作为候选轴点,将其与首元素互换位置,并记录下该轴点的值。初始令mi=lo
  2. k从lo+1开始, 从左向右考察每一个[k]:若[k]严格小于轴点,则将其内元素与[mi]内元素交换(注意此处是交换swap,不能像上一个版本那样直接赋值!!!),即L向右拓展,然后k++;否则直接将k++,即G向右拓展
  3. 线性扫描完成后,区间划分为L和G两个区间,此时将位于区间起始位置lo处的轴点与L区间末尾元素[mi]交换(swap[]),使轴点归位。返回轴点的秩mi

性能分析

就地算法,依旧不稳定

  • 空间O(1) 附加空间
  • 时间:单次partition算法为O(n),而总体上
    -最好:每次划分都(接近)平均,轴点总是(接近)中央: T ( n ) = 2 T ( ( n − 1 ) / 2 ) + O ( n ) = O ( n l o g n ) T(n)=2T((n-1)/2)+O(n)=O(nlogn) T(n)=2T((n1)/2)+O(n)=O(nlogn)
    -最坏:每次划分都极不均衡(比如轴点总是最小/最大元素): T ( n ) = T ( n − 1 ) + T ( 0 ) + O ( n ) = O ( n 2 ) T(n)=T(n-1)+T(0)+O(n)=O(n^2) T(n)=T(n1)+T(0)+O(n)=O(n2)
    -平均:O(nlogn)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值