《算法(第四版)》2.3.17和2.3.18快速排序改进,练习题学习小结

《算法(第四版)》2.3.17:哨兵,2.3.18:三取样切分
哨兵。正如书中所说,while循环中的边界检查是多余的,对于左侧边界,v不可能小于a[lo],故左侧边界的检查是多余的;对于右侧边界,只要将数组最大元素放置到末尾该元素就永远不会移动,v不可能大于a[hi]。
标准快排和哨兵快排关键代码对比:
标准快排

public void sort (Comparable[] a) {
	StdRandom.shuffle(a);
	sort(a, 0, a.length -  1);
}

哨兵快排,先将最大元素置于末尾

public void sort (Comparable[] a) {
	StdRandom.shuffle(a);
	exch(a, maxIndex(a), a.length - 1);
	sort(a, 0, a.length -  1);
}

标准快排,有边界检查

private int partition(Comparable[] a, int lo, int hi) {  //划分
		int i = lo, j = hi +1;
		Comparable v = a[lo];
		while(true) {  //扫描左右,检查是否结束并交换元素
			while(less(a[++i], v))  if(i == hi) break;
			while(less(v, a[--j]))  if(j == lo) break;
			if(i >= j) break;
			exch(a, i, j);
		}
		exch(a, lo, j);
		return j;
}

哨兵快排,无边界检查

private int partition(Comparable[] a, int lo, int hi) {  //划分
		int i = lo, j = hi +1;
		Comparable v = a[lo];
		while(true) {  //扫描左右,检查是否结束并交换元素
			while(less(v, a[--j]));  //去掉左边边界检查
			while(less(a[++i], v));  //去掉右边边界检查
			if(i >= j) break;
			exch(a, i, j);
		}
		exch(a, lo, j);
		return j;
	}

对标准快排和哨兵法快排进行双倍测试,每次测试数组长度为2000,测试次数为4000。在某次测试中,快排历时约0.683秒,哨兵快排历时约0.508秒。可见磨刀不误砍柴工,在排序前遍历数组得到最大元素是有必要的,边界检查付出的时间代价可能更大。

三取样切分。按照书中的描述,改进快排的一个方法是使用数组的一小部分元素的中位数来切分数组,但是代价是计算中位值,当取样大小为3时效果最好。同样,将取样元素放在数组末尾作为哨兵来去掉数组边界检测。具体措施是,先对比取样的3个元素是否有大于数组末尾的元素,若有则与其置换;然后将取样位置上的3个元素中的中位数作为切分元素置于最前。
三个元素确定中位数的方法为做差相乘,中位数在第二位:(a[lo+1] - a[lo]) * (a[lo+1] - a[lo+2]) <= 0;中位数在第三位:(a[lo+2] - a[lo]) * (a[lo+2] - a[lo+1]) <= 0;剩下一种可能就是中位数在首位。
关键代码:

private void mid(Comparable[] a, int lo, int hi) {
	for(int i = 0; i < 3; i ++) {  //3个元素中的最大值若大于hi项,则置换
		if(less(a[hi], a[lo+i])) exch(a, hi, lo+i);
	}
	
	if(a[lo+1].compareTo(a[lo]) * a[lo+1].compareTo(a[lo+2]) <= 0) exch(a, lo, lo+1);
	else if(a[lo+2].compareTo(a[lo]) * a[lo+2].compareTo(a[lo]) <= 0) exch(a, lo, lo+2);
}

切分

private int partition(Comparable[] a, int lo, int hi) {
	int i = lo, j = hi + 1;
	if(hi - lo  >= 2) {
		mid(a, lo, hi);
		Comparable v = a [lo];
		while(true) {
			while(less(v, a[--j]));
			while(less(a[++i], v));
			if(i >= j) break;
			exch(a, i, j);
		}
		exch(a, lo, j);
	} else if(less(a[hi], a[lo])) {
		exch(a, hi, lo); j --;
	} else j --;
	return j;
}

同样地进行双倍测试,3取样快排历时0.560秒,对三个元素求中位数的时间代价可能小于边界比较的时间代价,但总的代价也可能大于遍历求最大元素的代价。
在这里插入图片描述
若要显著提升快速排序的速度,需要结合插入排序,当子数组元素个数较小时,次数子数组呈上升趋势,此时采用插入排序可大大提升排序速度。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值