当使用quicksort对具有不同键的N个项目数组进行排序时,大小为0,1和2的子数组的预期数量是多少?

时间:2015-05-17 03:30:18

标签: algorithm sorting math probability quicksort

我正在阅读Robert Sedgewick的书算法第4版,他有以下练习题:大小为0,1和2时预期的子阵列数是多少quicksort用于使用不同的键对N个项目的数组进行排序?

然后他说,如果你在数学上有倾向,做数学,如果不是,运行实验,我已经进行了实验,看起来大小为0和1的数组具有完全相同的出现次数和大小为2的数组只是发生的一半。

有问题的快速排序版本是双向分区的版本。

据我所知,当分区项目是子阵列中最小/最大的分区项目时,我们得到大小为0的子数组,因此后续的2个排序调用将是

sort(a, lo, j-1); // here if j-1 < lo, we have an array of size 0
sort(a, j+1, hi); // here if j+1 > hi, we have an array of size 0

当分区项目是第二个到第一个最小/最大项目时,大小为1的数组,当它是第三个到第一个最小/最大项目时,大小为2。

那么,我究竟如何得出一个数学公式?

以下是C#中的代码

class QuickSort
{
    private static int zero = 0, one = 0, two = 0;
    private static int Partition<T>(T[] a, int lo, int hi) where T : IComparable<T>
    {
        T v = a[lo];
        int i = lo, j = hi + 1;
        while(true)
        {
            while(Alg.Less(a[++i], v)) if(i == hi) break;
            while(Alg.Less(v, a[--j])) if(j == lo) break;
            if(i >= j) break;
            Alg.Swap(ref a[i], ref a[j]);
        }
        Alg.Swap(ref a[lo], ref a[j]);
        return j;
    }
    private static void Sort<T>(T[] a, int lo, int hi) where T : IComparable<T>
    {
        if(hi < lo) zero++;
        if(hi == lo) one++;
        if(hi - lo == 1) two++;

        if(hi <= lo) return;
        int j = Partition(a, lo, hi);

        Sort(a, lo, j - 1);
        Sort(a, j + 1, hi);
    }
    public static void Sort<T>(T[] a) where T : IComparable<T>
    {
        Alg.Shuffle(a);
        int N = a.Length;
        Sort(a, 0, N - 1);
        Console.WriteLine("zero = {0}, one = {1}, two = {2}", zero, one, two);
    }
}

有一个证据表明平均快速排序使用2NlnN~1.39NlgN比较排序长度为N且具有不同键的数组。

我想我们可以想到1.39NlgN,因为我们进行N次比较~lgN次,所以平均而言我们将数组分成两半,因此在某些时候我们将留下成对进行比较,并且因为有只有要比较的对,例如:&lt; 1,2&gt;,&lt; 3,4&gt;,&lt; 5,6&gt;等等...,我们将在分区后得到大小为0和1的子数组,这只能证明0和1的大小更频繁,但我仍然不明白为什么2的大小几乎只有一半的频率。

2 个答案:

答案 0 :(得分:4)

QuickSort将递归地将数组分区为位置k处的两个较小的数组。 k可以是1到n。每个k具有相同的发生概率。让C0(n)为0大小子集的平均出现次数,C1(n)C2(n)对于1个大小和2个大小的子集是相同的。

除了初始条件,每个都满足:

C(n) = 1/n sum(C(k-1) + C(n-k) for k=1..n)

总和的两个部分是相同的,但是以相反的顺序求和,所以:

C(n) = 2/n sum(C(k-1) for k=1..n)

n*C(n) = 2*sum(C(k-1) for k=1..n)

假设nn-1都不是初始条件的一部分,我们可以通过从双方减去(n-1)C(n-1)来简化:

n*C(n) - (n-1)C(n-1) = 2*C(n-1)

C(n) = (n+1)/n * C(n-1)

从递归关系中导出结果

我们现在有一个递归关系C(n),它同样适用于C0C1C2

对于C0,我们有初始条件C0(0)=1C0(1)=0。我们计算C0(2)以获得1,然后我们可以为n> 2应用简化的递归关系C0(n) = (n+1)/n * C0(n-1)以获得一般结果C0(n)=(n+1)/3

对于C1,我们有初始条件C1(0)=0C1(1)=1。与以前一样,我们计算C1(2)以获取1,并应用与C0相同的过程来获得一般结果C1(n)=(n+1)/3

对于C2,我们有初始条件C2(0)=C2(1)=0C2(2)=1。这次我们计算C2(3) = 1/3 * 2 * (C2(0) + C2(1) + C2(2)) = 2/3。然后应用简化的递归关系推断一般结果C2(n)=(n+1)/4 * C2(3) = (n+1)/4 * 2/3 = (n+1)/6

<强>结论

我们已经显示了在两种情况下(n + 1)/ 3快速分配大小为n的数组时,0个大小和1个大小的子阵列的平均出现次数。对于2个大小的子阵列,我们已经显示它是(n + 1)/ 6。

这证实了您的原始观察结果,即2个大小的子集恰好是0和1个大小的子集的一半,并给出了精确的均值公式。

答案 1 :(得分:3)

不考虑数学,有一点是完全清楚的:当使用双元素分区调用quicksort时,它将发出两个递归调用,其中一个具有零元素分区,另一个具有一个 - 元素分区。

因此,对于计算的每个双元素分区,肯定会有一个单元素分区和一个零元素分区。

此外,当所选分区元素是当前分区中的最大/最小或第二大/最小元素时,单元素和零元素分区可以自发地发生,而没有双元素父元素。粗略地说,这些应该和对方一样可能,并且也可能出现两元素分区。

所以观察到的结果并不出乎意料。