排序时如何找出"progress"?

How to figure out "progress" while sorting?

本文关键字:progress 何找出 排序      更新时间:2023-10-16

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

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

但是(即使我要写自己的排序程序)我怎么能说出我已经取得了多大的进步,还有多少路要走?

我不需要它是精确的,但我需要它是"合理的"(即合理的线性,不伪造,当然也不回溯)。

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

由于您使用的是mergesort,因此比较计数将是一个非常精确的进度度量。如果实现在比较运行之间花费时间来排列矢量,这可能会有点非线性,但我怀疑您的用户是否会看到非线性(无论如何,我们都习惯了不准确的非线性进度条:))。

根据数据的性质,快速排序/内含排序会显示出更多的差异,但即使在这种情况下,也总比什么都没有好,而且你总是可以根据经验添加一个模糊因素。

在你的比较课上,一个简单的计数器几乎不会花你任何钱。就我个人而言,我甚至不会麻烦锁它(锁会影响性能);它不太可能进入不一致的状态,而且无论如何,进度条不会因为得到不一致的进度数字就开始辐射蜥蜴。

将矢量拆分为几个相等的部分,数量取决于所需进度报告的粒度。分别对每个部分进行排序。然后开始与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);
}

稳定排序是基于合并排序的。如果你编写了自己版本的合并排序,那么(忽略一些加速技巧)你会看到它由logN个过程组成。每个过程从2^k个排序的列表开始,生成2^(k-1)个列表,当它将两个列表合并为一个列表时,排序就完成了。所以你可以用k的值来表示进度。

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

(请注意,使用C++稳定排序,您必须希望它找到足够的存储空间来保存数据副本。否则,成本从N log N上升到N(log N)^2,您的预测将过于乐观)。

选择一小部分索引并计数反转。你知道它的最大值,当你完成时,它的值为零。因此,您可以将此值用作"progressor"。你可以把它看作是熵的度量。

最简单的方法是:对一个小向量进行排序,并假设O(n log n)的复杂性来推断时间。

t(n)=C*n*log(n)⇒t(n1)/t(n2

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

快速排序基本上是

  1. 使用pivot元素的分区输入
  2. 递归排序最小部分
  3. 使用尾部递归对最大部分进行排序

所有工作都在分区步骤中完成。您可以直接进行外部分区,然后在完成最小部分时报告进度。因此,在上面的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;
}
}

我花了将近一天的时间来研究如何显示shell排序的进度,所以我将在这里留下我的简单公式。给定一组颜色,它将显示进度。它将从红色到黄色再到绿色的颜色混合在一起。当它被排序时,它是数组的最后一个蓝色位置。对于shell排序,每次通过数组时的迭代都是成比例的,因此进度变得非常准确。(飞镖/长笛代码)

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表示项目数。我用数组大小、数组编号和颜色数组大小的几种组合进行了测试,所以我认为这是一个不错的选择。