我正在使用stable_sort
对大型vector
进行排序。
排序需要几秒钟(比如5-10秒),我想向用户显示一个进度条,显示到目前为止已经完成了多少排序。
但是(即使我要编写自己的排序例程)我怎么能说出我取得了多大的进展,还剩下多少还剩下什么呢?
我不需要它确切,但我需要它“合理”(即合理的线性,不是假的,当然也不是回溯)。
答案 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基本上是
所有工作都在分区步骤中完成。您可以直接执行外部分区,然后在完成最小部分时报告进度。 所以上面的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表示项数。我用数组大小,数组编号和颜色数组的大小的几种组合进行了测试,所以我认为这样做很好。