【问题标题】:Finding elements of std::vector in std::set在 std::set 中查找 std::vector 的元素
【发布时间】:2018-01-11 01:48:47
【问题描述】:

我有两个容器std::setstd::vector,我的任务是从std::vector 返回存在于std::set 中的元素。实现它的最有效方法是什么? 简单的解决方案: 遍历vector的元素并在每个元素上调用set.find,如果没有找到则调用vector.erase

【问题讨论】:

  • 向量是排序的还是未排序的?
  • 听起来你可能想要std::set_union 之类的东西(但它需要对向量进行排序)。
  • 抱歉不一致。暂时(并且它可能保持不变)向量是未排序的并且很小。不过,Set 有更多的元素。

标签: c++ c++11 vector stl set


【解决方案1】:

只查找每个元素怎么样?如果您的向量未排序,则无法绕过n log(n)

#include <algorithm>

std::vector<int> result;
for(auto&& el: myvector) {
    auto it_found = myset.find(el);
    if(it != myset.end())
        result.push_back(*it_found);
}

现在result 拥有两者中的所有元素。

PS:代码未编译,可能有小错误。

【讨论】:

  • 不是 100% 确定,但这不是 O(n^2) 吗?不需要迭代vector然后使用集合的find成员函数得到O(n log n)吗?
  • @NathanOliver 实际上我不确定。可能是n^2。我有点不知所措,因为std::set 已排序。
  • 但您不是在搜索集合。 for(auto&amp;&amp; el: myset) 迭代集合,所以是 n,然后 std::find(myvector.begin(), myvector.end(), el); 搜索另一个向量 n,所以是 O(n^2) 对吗?
  • @NathanOliver 是的,你是对的。我猜是n^2
  • 我相信如果你把它翻过来,你会得到O(n log n)
【解决方案2】:

你可以使用更多的 STL :)

#include <algorithm>
#include <set>
#include <vector>
#include <iostream>
#include <iterator>

int main() {
    std::vector<int> v {5, 4, 3, 2, 1};
    std::set<int> s {1, 3, 5};

    v.erase(std::remove_if(v.begin(), v.end(), 
                          [&s](int a) { return s.find(a) == s.end(); }),
            v.end());

    std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "));
}

【讨论】:

  • 由于我想保留 set 中存在的向量中的元素,因此解决方案需要稍作修正return s.find(a) == s.end();
【解决方案3】:

最短的方法可能是使用std::set_intersection。但是您应该对向量进行排序以使其工作:

int main()
{
    std::set<int>    s{1,2,3,4,5,6,7,8};
    std::vector<int> v{7,5,10,9};
    std::sort(v.begin(), v.end()); // should not bother you if vector is small

    std::vector<int> intersection;
    std::set_intersection(s.begin(), s.end(), v.begin(), v.end(), std::back_inserter(intersection));

    for(int n : intersection)
        std::cout << n << ' ';
}

打印:5 7

【讨论】:

  • 如果n 是向量的大小,m 是集合的大小,则为O(n*lg(n) + n + m)。可以在O(n*lg(m)) 中完成。 (而且集合迭代很慢。)
【解决方案4】:

根据集合和向量的相对大小,remove_if 可能是正确的...

#include <set>
#include <vector>
#include <iostream>
#include <algorithm>

int main()
{
    std::set<int>    s{1,2,3,4,5,6,7,8};
    std::vector<int> v{7,5,10,9};

    v.erase(std::remove_if(v.begin(), v.end(), [&](int e){return s.count(e) == 0;}), v.end());


    for(int n : v)
        std::cout << n << ' ';
}

【讨论】:

    【解决方案5】:

    如果你在复杂性方面寻找最cpu-有效的方法,拥有额外的内存和良好的哈希函数,你可以在O中做到这一点(n + m):

    std::vector<int> v;
    std::set<int> s;
    std::unordered_set<int> us{s.cbegin(), s.cend(), s.size()};
    
    v.erase(
        std::remove_if(v.begin(), v.end(),
            [&us] (const int entry) { return us.find(entry) == us.cend(); }),
        v.end());
    

    解释:你迭代你的 set 一次 (O(m)) 以准备 unordered_set。然后你遍历你的vector一次(O(n)),每一步执行unordered_set::find(0(1))。它为您提供了 O(n+m) 的复杂度。

    另外,unordered_set 的大小等于set 的大小,一个好的散列函数有助于减少std::unordered_set::find 复杂度中的常数部分。

    live example

    但是请记住,较低的复杂性并不一定意味着在特定情况下执行速度更快(例如,由于额外的分配)。

    【讨论】:

    • 谢谢您的解释。但是(正如您所提到的)我想在不使用额外内存的情况下“即时”擦除元素。
    • 在这种情况下,如果您不关心set 的排序属性,您可以将set 替换为unordered_set,或者使用boost::multi_index_containerordered_unique 索引类型来使用set -like 属性和hashed_unique 用于过滤掉复杂度为 O(n) 的不需要的条目。
    猜你喜欢
    • 1970-01-01
    • 2019-07-30
    • 2013-03-08
    • 2017-12-15
    • 2015-10-11
    • 1970-01-01
    • 2011-12-27
    • 1970-01-01
    • 2014-08-10
    相关资源
    最近更新 更多