一个酷炫快速排序

浏览知乎时,引用一位知友写回答的快速排序问题。

如下:

看过Jon Bently那个快排,一辈子都忘不了了,背下来就可以了。

void quicksort(int l,int u)
{
	int i,m;
	if(l>=u) return;
	m=l;
	for( i=l+1 ; i<=u ; i++ )
		if(x[i]<x[l])//buggy!!!
			swap( x[++m] , x[i] );
	swap( x[l] , x[m] );
	quicksort( l , m-1);
	quicksort( m+1 , u);
}


上面是他在 beautiful code 上的代码。
最核心的部分就是开始的那个for循环和swap了(paritition),这要怎么背呢?当然是用循环不变式了!
[l, m) 位置保存着当前 已处理元素之中所有的小于pivot的元素
i指向的是 下一个待处理的元素
第l个元素是pivot

循环开始时,[l, m)是空集,已处理元素也是空的,所以循环不变式成立。
for循环每轮把i加了1,这样就有可能破坏循环不变式,
怎么办呢,当然是:

在i加1之前,如果发现当前i指向的值小等于pivot,就将m自增1,并且和i指向的元素交换。

这样,循环不变式又成立啦。
在for循环结束后,循环不变式依然是成立的(根据数学归纳法~)
那么,它告诉了我们什么性质呢?

[l, m)之间包含了l 到 u 之间的小于 pivot 的元素,且第l个元素是pivot。

于是,我们只要将第l个元素和第m个元素交换,就完成了partition的操作,即:pivot在第m个元素上,m元素之前的值都小于pivot,之后的值都大等于pivot。

非常简洁,非常酷炫,但是有点bug(注意我注释的那一行)
这个partition在大量duplicate key的情况下,会把这些key全部放到左边或者右边。导致不公平的切分。

partition要保证在中间把数组分开,可以看这个代码:
代码中,数组不是被切成两份,而是三份,中间那份包含了所有和pivot相同的key。
左边和右边那份分别是比pivot小和比pivot大的key,递归调用仅在它们之上执行,中间那份在partition后就保留在原地不动了。

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

    // quicksort the subarray a[lo .. hi] using 3-way partitioning
    private static void sort(Comparable[] a, int lo, int hi) { 
        if (hi <= lo) return;
        int lt = lo, gt = hi;
        Comparable v = a[lo];
        int i = lo;
        while (i <= gt) {
            int cmp = a[i].compareTo(v);
            if      (cmp < 0) exch(a, lt++, i++);
            else if (cmp > 0) exch(a, i, gt--);
            else              i++;
        }

        // a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]. 
        sort(a, lo, lt-1);
        sort(a, gt+1, hi);
        assert isSorted(a, lo, hi);
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值