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

5 sorting algorithm math probability quicksort

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

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

有问题的quicksort版本是具有双向分区的版本.

据我所知,当分区项是子数组中最小/最大的分区项时,我们得到大小为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
Run Code Online (Sandbox Code Playgroud)

当分区项目是第2个到第一个最小/最大项目时,大小为1的数组发生,当它是第3个到第一个最小/最大项目时,大小为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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

inv*_*sal 5

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)
Run Code Online (Sandbox Code Playgroud)

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

C(n) = 2/n sum(C(k-1) for k=1..n)
Run Code Online (Sandbox Code Playgroud)

或者

n*C(n) = 2*sum(C(k-1) for k=1..n)
Run Code Online (Sandbox Code Playgroud)

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

n*C(n) - (n-1)C(n-1) = 2*C(n-1)
Run Code Online (Sandbox Code Playgroud)

或者

C(n) = (n+1)/n * C(n-1)
Run Code Online (Sandbox Code Playgroud)

从递推关系推导出结果

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

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

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

对于C2,我们有初始条件C2(0)=C2(1)=0, 和C2(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 的数组进行快速排序时,大小为 0 和大小为 1 的子数组的平均出现次数在两种情况下都是 (n+1)/3。对于 2 大小的子数组,我们已经证明它是 (n+1)/6。

这证实了您最初的观察,即 2 大小的子集出现的频率恰好是 0 和 1 大小的子集的一半,并给出了均值的精确公式。