如何在排序时弄清楚“进度”?

时间:2012-12-16 04:42:41

标签: c++ algorithm sorting progress

我正在使用stable_sort对大型vector进行排序。

排序需要几秒钟(比如5-10秒),我想向用户显示一个进度条,显示到目前为止已经完成了多少排序。

但是(即使我要编写自己的排序例程)我怎么能说出我取得了多大的进展,还剩下多少还剩下什么呢?

我不需要它确切,但我需要它“合理”(即合理的线性,不是假的,当然也不是回溯)。

7 个答案:

答案 0 :(得分:6)

标准库排序使用用户提供的比较功能,因此您可以在其中插入比较计数器。 quicksort / introsort或mergesort的比较总数将非常接近log 2 N * N(其中N是向量中元素的数量)。这就是我导出到进度条的内容:比较次数/ N * log 2 N

由于您正在使用mergesort,因此比较计数将非常精确地衡量进度。如果实现花费时间在比较运行之间置换向量,它可能略微是非线性的,但我怀疑你的用户会看到非线性(并且无论如何,我们都习惯于不准确的非线性进度条:))。

Quicksort / introsort会显示更多的变化,具体取决于数据的性质,但即使在这种情况下,它总比没有好,你总是可以根据经验添加一个软糖因子。

比较课程中的一个简单计数器几乎不会花费你。我个人甚至不打扰它(锁会伤害性能);它不太可能进入一个不一致的状态,无论如何,进度条不会因为获得一个不一致的进度数而开始辐射蜥蜴。

答案 1 :(得分:1)

将矢量分成几个相等的部分,数量取决于您所需的进度报告的粒度。单独对每个部分进行排序。然后开始与std::merge合并。您可以在对每个部分进行排序之后以及每次合并之后报告您的进度。您需要进行试验,以确定与合并相比,应计算各部分的排序百分比。

修改

我做了一些自己的实验,发现合并与排序相比无关紧要,这就是我提出的功能:

template<typename It, typename Comp, typename Reporter>
void ReportSort(It ibegin, It iend, Comp cmp, Reporter report, double range_low=0.0, double range_high=1.0)
{
    double range_span = range_high - range_low;
    double range_mid = range_low + range_span/2.0;
    using namespace std;
    auto size = iend - ibegin;
    if (size < 32768) {
       stable_sort(ibegin,iend,cmp);        
    } else {
        ReportSort(ibegin,ibegin+size/2,cmp,report,range_low,range_mid);
        report(range_mid);
        ReportSort(ibegin+size/2,iend,cmp,report,range_mid,range_high);
        inplace_merge(ibegin, ibegin + size/2, iend);
    }   
}

int main()
{
    std::vector<int> v(100000000);
    std::iota(v.begin(), v.end(), 0);
    std::random_shuffle(v.begin(), v.end());

    std::cout << "starting...\n";

    double percent_done = 0.0;
    auto report = [&](double d) {
        if (d - percent_done >= 0.05) {
            percent_done += 0.05;
            std::cout << static_cast<int>(percent_done * 100) << "%\n";
        }
    };
    ReportSort(v.begin(), v.end(), std::less<int>(), report);
}

答案 2 :(得分:0)

最简单的方法:对小矢量进行排序,并假设O(n log n)复杂度推断时间。

t(n)= C * n * log(n)⇒t(n 1 )/ t(n 2 )= n 1 / n 2 * log(n 1 )/ log(n 2

如果排序10个元素需要1μs,那么100个元素需要1μs* 100/10 * log(100)/ log(10)=20μs。

答案 3 :(得分:0)

稳定排序基于合并排序。如果您编写了自己的合并排序版本(忽略了一些加速技巧),您会看到它包含log N次。每个传递以2 ^ k个排序列表开始,并产生2 ^(k-1)个列表,当它将两个列表合并为一个时,排序完成。因此,您可以使用k的值作为进度的指示。

如果您要进行实验,您可以检测比较对象以计算所进行的比较次数,并尝试查看比较次数是否是n log n的合理可预测倍数。然后,您可以通过计算完成的比较次数来跟踪进度。

(请注意,对于C ++稳定排序,您必须希望它找到足够的存储来保存数据副本。否则成本从N log N到N(log N)^ 2并且您的预测将会太过乐观了。)

答案 4 :(得分:0)

选择一小部分索引并计算反转。你知道它的最大值,你知道当你完成时,值为零。因此,您可以将此值用作“进度器”。您可以将其视为熵的度量。

答案 5 :(得分:0)

Quicksort基本上是

  1. 使用pivot元素进行分区输入
  2. 递归排序最小的部分
  3. 使用尾递归排序最大的部分
  4. 所有工作都在分区步骤中完成。您可以直接执行外部分区,然后在完成最小部分时报告进度。 所以上面的2到3之间会有一个额外的步骤。

    • 更新进度器

    这是一些代码。

    template <typename RandomAccessIterator>
    void sort_wReporting(RandomAccessIterator first, RandomAccessIterator last)
    {
    double done = 0;
    double whole = static_cast<double>(std::distance(first, last));
    
    typedef typename std::iterator_traits<RandomAccessIterator>::value_type value_type;
    
    while (first != last && first + 1 != last)
    {
        auto d = std::distance(first, last);
        value_type pivot = *(first + std::rand() % d);
    
        auto iter = std::partition(first, last, 
            [pivot](const value_type& x){ return x < pivot; });
        auto lower = distance(first, iter);
        auto upper = distance(iter, last);
        if (lower < upper)
        {
            std::sort(first, iter);
            done += lower;
            first = iter;
        }
        else
        {
            std::sort(iter, last);
            done += upper;
            last = iter;
        }
    
        std::cout << done / whole << std::endl;
    }
    }
    

答案 6 :(得分:0)

我花了将近一天的时间弄清楚如何显示shell排序的进度,因此这里我将保留简单的公式。给定一系列颜色,它将显示进度。它混合了从红色到黄色,然后到绿色的颜色。排序后,它是数组的最后一个蓝色位置。对于外壳排序,每次通过数组时的迭代都是成比例的,因此进度变得非常准确。 (Dart / Flutter中的代码)

List<Color> colors = [
    Color(0xFFFF0000),
    Color(0xFFFF5500),
    Color(0xFFFFAA00),
    Color(0xFFFFFF00),
    Color(0xFFAAFF00),
    Color(0xFF55FF00),
    Color(0xFF00FF00),
    Colors.blue,
  ];
[...]
style: TextStyle(
    color: colors[(((pass - 1) * (colors.length - 1)) / (log(a.length) / log(2)).floor()).floor()]),

基本上是交叉乘法。 均值数组。 (log(a.length)/ log(2))。floor()表示将log2(N)向下取整,其中N表示项数。我用数组大小​​,数组编号和颜色数组的大小的几种组合进行了测试,所以我认为这样做很好。