【问题标题】:Quicksort with one recursive call一次递归调用的快速排序
【发布时间】:2016-10-27 08:37:02
【问题描述】:

我们都知道如何使用两个或多个递归调用来编写快速排序。

几天前老师说可以通过一个递归调用来实现。 实际上我不知道如何只用一个递归调用来保存 O(n log n)

有什么想法吗?

【问题讨论】:

  • 您可以将两个递归调用之一设为尾调用,可以消除(变成迭代而不是递归)。
  • 这几乎就像使用循环来进行两个递归调用。不知道有什么好处。
  • 为了避免最坏情况下的堆栈溢出,使用了循环和递归的组合。进行分区后,对较小的部分使用递归,然后代码循环返回以拆分较大的部分。使用这种方法,最坏情况下的堆栈空间是 O(log2(n))。
  • 已经在这里讨论过stackoverflow.com/questions/19854007/…,也是流行的“loguy higuy”实现:github.com/atgreen/moxiedev/blob/master/benchmarks/MiBench/…。只需在 Google 上搜索“qsort loguy higuy”,您就会发现很多。

标签: c sorting quicksort


【解决方案1】:

示例 C++ 快速排序,每次迭代一次递归调用,以将堆栈开销减少到 O(log(n))。还使用 3 的中值作为枢轴,并排除分区 == 枢轴的中间值。

void QuickSort(int a[], size_t lo, size_t hi) {
    while(lo < hi){
        size_t i = lo, j = (lo+hi)/2, k = hi;
        int p;
        if (a[k] < a[i])            // median of 3
            std::swap(a[k], a[i]);
        if (a[j] < a[i])
            std::swap(a[j], a[i]);
        if (a[k] < a[j])
            std::swap(a[k], a[j]);
        p = a[j];
        i--;                        // Hoare partition
        k++;
        while (1) {
            while (a[++i] < p);
            while (a[--k] > p);
            if (i >= k)
                break;
            std::swap(a[i], a[k]);
        }
        i = k++;
        while(i > lo && a[i] == p)  // exclude middle values == pivot
            i--;
        while(k < hi && a[k] == p)
            k++;
        // recurse on smaller part, loop on larger part
        if((i - lo) <= (hi - k)){
            QuickSort(a, lo, i);
            lo = k;
        } else {
            QuickSort(a, k, hi);
            hi = i;
        }
    }
}

要在代码中只有一个递归调用,最后一部分可以替换为:

        // recurse on smaller part, loop on larger part
        size_t ll, rr;
        if((i - lo) <= (hi - k)){
            ll = lo;
            rr = i;
            i = hi;
        } else {
            ll = k;
            rr = hi;
            k = lo;
        }
        QuickSort(a, ll, rr);
        lo = k;
        hi = i;
    }
}

【讨论】:

  • 技术上你仍然有两个递归调用
  • @BasileStarynkevitch - 每次迭代一次递归调用,以避免堆栈溢出,否则 nephi12 的链接显示一个递归调用示例。
  • @BasileStarynkevitch,如果不是对较小的部分进行递归调用,而是默认为第一部分(与大小无关),您只会得到一个递归调用......从技术上讲,循环是另一种形式递归,这是取消其中一个递归调用的方式。正确的?您在列表中接到两个电话,但您永远不会在同一个电话中同时接到两个电话。
  • @BasileStarynkevitch - 我添加了一个只使用一个递归调用的替代代码片段。
猜你喜欢
  • 2015-08-27
  • 2021-06-30
  • 1970-01-01
  • 2020-08-14
  • 1970-01-01
  • 2013-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多