【问题标题】:Fastest method of searching and removing elements from a sorted vector of integers in C++在 C++ 中从整数的排序向量中搜索和删除元素的最快方法
【发布时间】:2014-08-31 16:35:52
【问题描述】:

我有一个大的排序整数向量。我需要快速找到并从数组中删除八个值。

例如,向量 a 包含元素

{1, 4, 7, 15, 16, 19, 24, 26, 31, 53, 67, 68, 73, 75, 77, 82}

向量 b 包含八个值

{4, 15, 19, 24, 67, 68, 73, 75}

完成操作后,向量a现在应该有

{1, 7, 16, 26, 31, 53, 77, 82}

我的旧解决方案很慢:

for (vector<int>::iterator val = b.begin(); val != b.end(); val++)
        a.erase(remove(a.begin(), a.end(), *val), a.end());

有更快的方法吗?

编辑:

实际上,我的“A”向量比“B”向量大很多。也许最好只通过二分搜索搜索单个元素并删除它们?

EDIT2:

也许向量不是这种操作的好容器。我不认为我可以使用 forward_list 因为我不能用 C++11 编译。也许我可以使用不同的容器,然后将结果复制到向量中?

【问题讨论】:

  • 你可以使用二分搜索来查找元素,但是你必须使用向量吗?这不是删除元素的最佳选择。
  • @mohaned 关于性能,实际上可能是这样,请参阅channel9.msdn.com/Events/GoingNative/GoingNative-2012/…
  • 是的,我需要使用矢量。另外,我不能使用 C++11。
  • 我认为搜索排序向量的最佳方法是二分搜索O(lg(n)),因此您可以对 8 个值重复它并删除您的元素,@BaummitAugen 我很快就会阅读它。
  • 其实,既然你提到它,我才意识到 remove() 不使用二分查找。我会在 stl 中查找二进制搜索算法。

标签: c++ vector stl sorted


【解决方案1】:

我可能会这样做:

std::vector<int> temp;

std::set_difference(a.begin(), a.end(), 
                    b.begin(), b.end(),
                    std::back_inserter(temp));
std::swap(a, temp);

根据已编辑的问题进行编辑:

鉴于您的 a 向量远大于您的 b 向量,我要考虑第二个问题:完成后您是否需要 a 保持排序?

如果允许重新排列 a 中的元素,那么您可以大大提高速度:您可以将要删除的项目与最后一个交换,而不是执行删除/擦除以从 a 的中间删除项目a 的元素,然后从末尾删除它(具有恒定的复杂性)。这使得移除量保持不变,因此总体复杂度为 O(N log M)(其中 N = b.size() 和 M = a.size())。

如果您确实必须保持秩序,您仍然可以稍微提高速度:不要从a 中删除元素,然后立即擦除已删除的元素,而是执行std::remove_if 以查找a 中需要的所有元素要删除,然后一次(完成后)执行一次擦除以删除所有这些元素。

就目前而言,您正在为您删除的a 的每个元素单独调用remove。对于每个remove,在每个删除点之后复制(或移动,如果适用)a 的所有元素。这意味着如果您从 a 中删除 10 个元素,您将(平均)复制一半的 a 10 次以上。通过使用单个 remove_if 代替,您只复制了 a 的每个元素一次。

不幸的是,remove_if 并没有为您提供利用b 订购的好方法。您可以使用二分搜索,这对一些人有帮助,但没有您想要的那么多。

如果您不介意编写自己的循环,则可以充分利用 ab 都已排序的事实,如下所示:

#include <vector>
#include <iostream>

// Compute the difference between two "set"s in-place. Each 'set' must be a
// sorted sequence.
//
template <class FwdIt, class InIt>
FwdIt 
inplace_set_difference(FwdIt b1, FwdIt e1, InIt b2, InIt e2) {
    FwdIt pos = b1;
    while (pos != e1 && b2 != e2) {
        if (*pos < *b2)
            *b1++ = *pos++;
        else if (*b2 < *pos)
            ++b2;
        else
            ++pos;
    }
    while (pos != e1)
        *b1++ = *pos++;
    return b1;
}

int main() { 
    std::vector<int> a{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
    std::vector<int> b{ 2, 5, 9 };

    auto it = inplace_set_difference(a.begin(), a.end(), b.begin(), b.end());
    a.erase(it, a.end());

    for (auto i : a)
        std::cout << i << '\t';
}

如果您想使其最大限度地通用,您可能希望将其更改为仅在迭代器上使用后增量,允许用户传递比较运算符而不是直接使用 &lt; 等。这些是留下了可怕的“读者练习”。

可能值得注意的是,这与set_difference 通常使用的算法基本相同,只是稍作调整以就地操作。不过,这确实会导致接口发生重大变化:因为它可以(并且确实)在集合中创建重复元素,所以它可以应用于排序序列(向量、双端队列、散列等),但 不能 em> 到关联容器 ([unordered_][multi_](set|map))。

由于这遍历ab 各一次,显然是O(N + M),但由于我们从M(=b.size()) 很小的想法开始,它实际上是@ 987654351@

注意:测试代码大量使用 C++11 特性(例如,初始化两个向量),但我相信算法实现本身在 C++98/03 中应该没问题。

【讨论】:

  • 如果 A 和 B 的量级大致相同(忽略删除,它在 O(n) 时间内运行),则此方法非常有效。但是,如果 B 非常小(例如我的 8 个元素的示例),最好对每个元素进行二进制搜索,这将是 O(k*log(n) 忽略删除),其中 k 是 B 的大小。
  • @AnimatedRNG:在某些特殊情况下,可能会有利于对b 的每个元素进行二进制搜索并单独删除它。大多数情况下,不仅要知道b 很小,而且b 的元素接近a 的末尾。我给出的方法在 O(N) 包括 删除中运行。一般来说,对 A 中 B 的每个元素进行单独的二分搜索,然后删除该元素将是 O(N M log N)(其中 N=a.size() 和 M=b.size())。
【解决方案2】:

而不是删除元素,只需将项目添加到新向量。假设您的输入是 'original' (A) 和 'toRemove' (B),只需为 original 和 toRemove 创建迭代器:

如果 original 中的下一项与 toRemove 中的下一项匹配,则删除它。否则,将其复制到结果中。 如果它大于 toRemove 中的下一项,则转到 toRemove 中的下一项并再次运行比较。

这样,您只需对每个列表进行一次迭代,而不是在“删除”操作期间不断复制数组值。

此解决方案将以 O(A+B) 运行,比您当前(和建议的)解决方案更快。

出于比较目的: 您现有的解决方案大致为 O(A*A*B)(A 用于删除,A 用于非优化搜索,B 用于迭代 B)。
您的编辑建议进行二进制搜索以删除元素;这只会改进 O(logA*A*B) 的原始解决方案(A 用于删除,logA 用于优化搜索,B 用于迭代 B)。

【讨论】:

  • 太好了,运行时间为 O(n)!
  • 查看我对 Jerry Coffin 帖子的评论
【解决方案3】:

向量标头中的内置函数更快。例如,

向量向量;

添加所有元素后,您可以使用

排序(vect.begin(),vect.end());

这将按升序对列表进行排序。对于下降,您可能必须执行

反向(vect.begin,vect.end());

排序列表上。

【讨论】:

  • 您的第一个代码 sn-p 不会编译,如果您想要降序排序,您可以将适当的比较函数对象传递给 std::sort。此“答案”均不适用于已发布的问题。
  • 第一句也是废话; 函数并不慢。最后一句话没有意义,因为 std::vector 不是 std::list。问题中没有完全错误的几个部分确实是无关紧要的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多