快速排序

背景

快速排序 (Quicksort) 由 C.A.R. Hoare (著名计算机科学家,1980年图灵奖得主) 于1962年提出。他在 Computer Journal 中发表的经典论文 "Quicksort" 中描述了这一算法。采用了分治法的策略。

基本思想

  • 先从数列中取出一个数作为基准数
  • 将比这个数小的放到它的左边,比这个数大的放到它的右边
  • 对左右区间重复上一步,直到各个区间只有一个数

算法复杂度

  • 最坏情况复杂度为 $$O({n}^{2} )$$

每个内点只有一个叶子结点。 比较次数为:

\[0 + 1 + 2 + ... + n = \frac{n * (n + 1)}{2} \]

  • 最好情况复杂度为 $$O({nlog_{2}}^{n} )$$

每个内点有两个叶子结点,若叶子数为n,内点数为 n - 1 ,顶点数为 2n - 1, 叶子到根的距离为 $$O({log_{2}}^{n} )$$ (上下取整)。

顶点到根的距离之和 = 2 * 叶子到跟的距离之和 - 顶点数 + 1

比较次数约为 $$2 * {nlog_{2}}^{n} - (n - 1) \approx {nlog_{2}}^{n} - 2n + 1$$

  • 平均时间复杂度为 $$O({nlog_{2}}^{n} )$$

平均情况下,Tn为n个对象平均比较次数,若取第k个数为分割标准,则一个序列有 k - 1 个数,另一个序列有 n - k 个数。

{ T n = 1 n k = 1 n ( n 1 + T k 1 + T n k ) = n 1 + 2 n k = 0 n 1 T k T 2 = 1 , T 1 = 0 , T 0 = 0

求解:

{ n T n = n ( n 1 ) + 2 k = 0 n 1 T k (n+1) T n + 1 = ( n + 1 ) n + 2 k = 0 n T k

相减:

\[(n+1)T_{n + 1} = (n + 2)T_{n} + 2n \]

\[\frac{T_{n+1}}{n+2} = \frac{T_{n}}{n-1} + \frac{2n}{(n+1)(n+2)} \]

变换:$$S_{n} = \frac{T_{n}}{n+1}$$

{ S n + 1 S n = 2 n ( n + 1 ) ( n + 2 ) S 0 = 0 , S 1 = 0 , S 2 = 1 3

\[S_{n} = \sum_{k=0}^{n-1}\frac{2k}{(k+1)(k+2)} = 4\sum_{k=0}^{n-1}\frac{1}{k+2} -2\sum_{k=0}^{n-1}\frac{1}{k+1}=2\sum_{k=2}^{n}\frac{1}{k} + \frac{4}{n+1} -2 \]

估计和,用求和进行逼近,只需上界

\[\sum_{k=2}^{n}\frac{1}{k} < \int_{1}^{n}\frac{1}{x}dx = ln(n) - ln(1) = ln(n) \]

\[S_{n} < 2ln(n) + \frac{2}{n+1} + 2 = 2ln(n) + O(1) \]

调和极数求和:$$1 + \frac{1}{2} + \frac{1}{3} + ... + \frac{1}{n} = ln(n+1)+r$$
这里r是欧拉常数约为0.57721。

则:$$T_{n} = 2(n+1)ln(n) + O(n) = 1.3863nlog_{2}{n} + O(n)$$

 

算法实现

quicksort 1

通过从左到右的扫描完成排序。容易忽略swap(l,m)这一步 (移动哨兵),而当t是子数组中严格最大的元素时,会导致死循环。
在一些常见输入下,可能退化为平方时间算法。

void quick_sort(int l, int r)
{
    if (l < r)
    {
        int i = l, j = r, m = l;
	    for( i = l + 1; i < r; i++)
	    {
		    if(x[i] < x[l])
		    swap(++m,i);
	    }
	    swap(l, m);

        quick_sort(l, m - 1); // 递归调用 
        quick_sort(m + 1, r);
    }
}

quicksort 2

将划分方案改为从右向左进行。循环终止时,x[m] == x[l],直接使用参(l,m-1)和(m+1,r)递归,不再需要swap操作。

void quick_sort(int l, int r)
{
    if (l < r)
    {
        int i,m = r + 1;
	    for( i = r ; i >= l; i--)
	    {
		    if(x[i] >= x[l])
		    swap(--m,i);
	    }

        quick_sort(l, m - 1); // 递归调用 
        quick_sort(m + 1, r);
    }
}

考虑极端情况,如n个相同元素组成的数组,对于这种输入,插入排序的性能非常好,每个元素移动距离都为0。 而quicksort 1的性能却非常糟糕,n - 1 次划分每次都要 O(n) 的时间来去掉一个元素。 总时间是$$O({n}^{2} )$$。采用双向划分可以避免这个问题。
下标i 和 j 初始化为数组的两端,主循环中有两个内循环,第一个循环向右移动遇到较大元素时停止,第二个循环向左移动遇到较小元素时停止。然后主循环测试两个下标是否交叉并替换值。当元素相同时,停止扫描并交换i和j的值,这样虽然交换次数增加了,但是将所有元素相同的最坏情况变成了差不多$$O({nlog_{2}}^{n} )$$的最好情况。如下:

quicksort 3

void quick_sort(int l, int r)
{
    if (l < r)
    {
        int i = l, j = u + 1, t = x[l];
	    while(1)
	    {
		    while(i <= r && x[i] < t) i++;
		    while(x[j] > t) j--;
		    if(i > j)
			    break;
		    swap(i,j);
	    }
	    swap(i,j);

        quick_sort(l, j - 1); // 递归调用 
        quick_sort(j + 1, r);
    }
}

quicksort 4

下面是一种常见的双向写法。

void quick_sort(int s[], int l, int r)
{
    if (l < r)
    {
        int i = l, j = r, x = s[l];
        while (i < j)
        {
            while(i < j && s[j] >= x) // 从右向左找第一个小于x的数
				j--;  
            if(i < j) 
				s[i++] = s[j];
			
            while(i < j && s[i] < x) // 从左向右找第一个大于等于x的数
				i++;  
            if(i < j) 
				s[j--] = s[i];
        }
        s[i] = x;
        quick_sort(s, l, i - 1); // 递归调用 
        quick_sort(s, i + 1, r);
    }
}

算法优化

  • 上面的快速排序都是围绕第一个元素进行的。对于随机的输入,这样没有问题,但是对于某些常见的输入,这样做需要的时间和空间更多, 如数组已经升序时。随机选择哨兵可以得到更好的性能。
  • 快排花费了很多时间来排序小的子数组,如果用插入排序之类的简单方法实现小数组的排序,程序速度会更快。

不妨令 r - l < cutoff 时采用插入排序,cutoff取值为50比较理想。

  • 代码调优时可以将循环体内的swap改为内联代码(另外的swap调用次数少,不计)。

quicksort 5

void quick_sort(int l, int r)
{
    if (r - l < cutoff) return;
    else
    {
	    swap(l, randint(l,u));
        int i = l, j = r + 1, t = x[l];
	    while(1)
	    {
		    while(i <= r && x[i] < t) i++;
		    while(x[j] > t) j--;
		    if(i > j)
			    break;
		    int temp = x[i];
		    x[i] = x[j];
		    x[j] = temp;
	    }
	    swap(i,j);

        quick_sort(l, j - 1); // 递归调用 
        quick_sort(j + 1, r);
    }
}
posted @ 2015-04-21 00:19  AgateLee  阅读(213)  评论(0)    收藏  举报