【问题标题】:Why sorted data as input is required for most of STL algorithms?为什么大多数 STL 算法都需要排序数据作为输入?
【发布时间】:2016-06-10 10:49:50
【问题描述】:

在 C++ 中使用 STL 算法时,我发现很多方法,如 std::mergestd::inplace_mergestd::set_unionstd::upper_boundstd::lower-bound 等...仅将排序数据作为输入。

在排序的数据上,这些算法会给出更快的结果是有道理的,但为什么它们也不能处理未排序的数据呢?为什么大多数算法都设计有这样的数据依赖关系?。

【问题讨论】:

  • 那是因为它需要检查一个序列是否已排序,这会将像 std::lower_bound 这样的算法的复杂度从 O(log n) 更改为 O(n)。如果您不确定,也没有什么可以阻止您预先排序。
  • 这些是非常常见的算法,不应该浪费时间在继续之前检查数据是否已排序。您始终可以创建一个包装函数,在调用算法之前对数据进行预排序。我想你可以有一个调试版本的算法,如果数据没有事先排序,就会抛出异常。

标签: c++ stl stl-algorithm


【解决方案1】:

在排序的数据上,这些算法会给出更快的结果是有道理的,但为什么它们也不能处理未排序的数据呢?为什么大多数算法都设计有这样的数据依赖关系?。

对于对数据进行排序“有意义”的算法,开发人员应该知道是否会出现这种情况,并且可以在需要时轻松对输入进行排序。算法可以检查数据是否首先自行排序,但这会在不必要的时候浪费时间。例如,upper_bound 在预先排序的输入上可能是 O(logN),而检查排序是 O(N)。还要记住,一般来说,算法没有地方可以存储状态说“我检查过一次并且数据已排序”(如果不了解存在哪些线程,他们如何知道这将持续多长时间?他们使用锁等),因此他们必须对数据的每次调用都这样做。

此外,您提到的一些算法 - 例如std::merge - 可以在 InputIterators 上使用,这意味着您可以处理只能读取一次的输入,例如来自键盘的输入,该输入暂时可用但不会自动保留在任何地方供您重新访问,因此有额外的通行证检查某人输入的值是否已经排序是不切实际的。

【讨论】:

  • 对于对数据进行排序“有意义”的算法,开发人员应该知道是否会出现这种情况,并且可以在需要时轻松对输入进行排序。 这是不正确的。在这种情况下,数据最初必须采用排序形式,并且不应要求开发人员出于使用算法的目的对数据进行排序。因为如果数据没有排序,那么排序将花费至少 O(nLogn)。如果直接使用未排序的数据,通过对数据进行排序然后使用lower-bound 将花费 O(nLogn) 而不是 O(n)。
  • @sameerkn: "不应该为了使用算法的目的而对数据进行排序 [,b] 因为如果数据没有排序,那么排序将花费至少 O(nLogn )。” - 你的推理完全错误。准备好对某个东西进行一次排序是很常见的,这样您就可以在此后多次快速对其应用算法(例如,对 vector 进行排序,然后重复 lower_bounds - 这比重复 std::set 快得多lower_bounds 因为它对缓存更友好)。
  • “如果直接使用未排序的数据,通过对数据进行排序然后使用下限将花费 O(nLogn) 而不是 O(n)。” - 风险需要这样做的原因就是为什么需要但不能依赖已排序输入的更健全的算法实现会在开始 O(NlogN) 排序之前进行一次检查以检查无序元素。换句话说,我的答案集中在最好的情况下,性能至少下降到 O(N);如果实际输入未排序,那肯定会更糟——至少 O(NlogN)。
【解决方案2】:

这些算法说:

如果您对数据进行了排序,我们将为您执行一些操作,非常高效。

就像您实现的普通二分搜索函数期望数据被排序一样,这些函数确实期望数据被排序。如果您没有对数据进行排序,请不要使用它们。

就像您不期望 sort 为您插入元素一样,您也不应该期望这些算法为您排序项目。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-08-04
    • 1970-01-01
    • 2018-01-08
    • 2021-01-02
    • 1970-01-01
    • 2015-02-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多