快排的基础实现&优化

这段时间,决心复习并深入研究常见的那几种排序算法,首先是快排。

以下内容来自 Sedgewick 的 Algorithms (Fourth Edition):

快排是原地算法( 见wiki:

in-place algorithm 写道
在计算机科学中,一个原地算法(in-place algorithm)是一种使用小的,固定数量的额外之空间来转换资料的算法。当算法执行时,输入的资料通常会被要输出的部份覆盖掉。不是原地算法有时候称为非原地(not-in-place)或不得其所(out-of-place)。

 ),给长度为N的数组排序,平均花(Nlog N)的时间。所以是综合考虑到空间和时间的算法,优于众多排序算法。但它有一个重要缺点是:很“脆弱”,必须要防止“坏”情况的出现。在优化版本将看到。

基础实现:

快排是种分而治之的排序方法。 它把数组划分为两个子数组,再将两个子数组分别独立地排序。

快排是归并排序的补充:对于归并,我们把数组分为两个子数组,然后分别排序,然后再归为整体进行排序;对于快排,当我们把两个子数组排好序之后,整个数组自然就排好序了。

以下是书上给出的代码,是大概的实现,如果要跑起来的话,还要自己去实现未实现的类和方法:

public class QuickSort {

	public static void sort(Comparable [] a){
		StdRandom.shuffle(a);			// eliminate dependence on input
		sort(a, 0, a.length - 1);
	}
	
	private static void sort (Comparable[] a, int lo, int hi){
		if(hi <= lo) return;
		int j = partition(a, lo, hi);
		sort(a, lo, j-1);			// sort left part a[lo .. j-1]
		sort(a, j+1, hi);			// sort right part a[j+1 .. hi]
	}
	
	private static int partition(Comparable[] a, int lo, int hi){
		// Partition into a[lo..i-1], a[i], a[i+1..hi]
		int i = lo, j = hi+1;
		Comparable v = a[lo];			// partitioning item
		while (true){
			// Scan right, scan left, check for scan complete, and exchange
			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);     // Put v = a[j] into position
		return j;           // with a[lo..j-1] <= a[j] <= a[j+1..hi]
		
	}
}

以上算法实现有几点考虑:

原地划分:如果不是原地,即我们另外使用一个数组来实现划分,比较容易但是一来消费空间,二来还要拷回给原有的数组。

不要超出范围:如果那个被选作划分标兵的元素恰巧是最小值或者最大值,那我们得小心,别让指针跑出左右范围。上面的partition方法中使用明显的测试条件来防御出界。不过,测试条件(j == lo)其实是多余的,因为标兵自身起到一个guard的效果,即标兵自己不会小于自己。所以,其实可以用同样的办法来防御右边出界:做完shuffle,就将数组最大值放在最右边来守卫。

保持随机性:上面的方法是将数组重新随机排一遍来保证随机性,另一个方法是在partition方法里,随机选取一个元素作为标兵。

终止循环:其实呢,上面的算法实现有可能无限循环下去=.= 如果数组中有元素与标兵元素相同,那么指针可能就不会到交叉的那一步。

处理与标兵元素相同的元素:最好在大于等于标兵元素的时候停止左边的扫描,在小于等于标兵元素的时候停止右边的扫描,哪怕会有看上去不必要的交换元素,但不这么做的话,会导致n平方的时间。具体证明是作为习题的,就不展开。稍后的版本将看到更好的处理含有相同元素的数组的快排算法。

终止递归:专业点的程序员会特别留心以保证递归方法终止。快排算法常见的一个错误是没有确定一个元素的最终位置,而陷入无限递归(当标兵元素是最小或最大时,考虑这种情况的出现)。

ok,下面是算法改进

快排是C.A.R Hoare 在上世纪60年代发明的,后人是各种学习研究和改进呐。如果你的算法代码将使用很多次,或者更进一步将被使用为库函数的话,那么很有必要研究一下算法优化。

今天在快排花的时间已经蛮多了,待续。

猜你喜欢

转载自wtchuang.iteye.com/blog/1847170