【问题标题】:Difference between two vector<MyType*> A and B两个向量<MyType*> A 和 B 之间的区别
【发布时间】:2010-06-28 18:53:10
【问题描述】:

我有两个 vector&lt;MyType*&gt; 对象,分别称为 AB。 MyType 类有一个字段ID,我想得到MyType*,它们在A 中,但不在B 中。我正在开发一个图像分析应用程序,我希望找到一个快速/优化的解决方案。

【问题讨论】:

  • 大概是因为你在问,他们不是按ID排序的?
  • 它们有多大? vector 有多重要?可以换成set吗?需要更多细节才能提供一个好的答案。
  • 我修改了我的原始帖子以包含一个更快的解决方案,并且可以处理未排序的向量。但是,它有些复杂。

标签: c++ set set-difference


【解决方案1】:

无序方法通常具有二次复杂度,除非数据预先排序(按您的 ID 字段),在这种情况下它将是线性的并且不需要通过 B 重复搜索。

struct CompareId
{
    bool operator()(const MyType* a, const MyType* b) const
    {
        return a>ID < b->ID;
    }
};
...
sort(A.begin(), A.end(), CompareId() );
sort(B.begin(), B.end(), CompareId() );

vector<MyType*> C;
set_difference(A.begin(), A.end(), B.begin(), B.end(), back_inserter(C) );

另一种解决方案是使用带有用于 StrictWeakOrdering 模板参数的 CompareId 的有序容器,例如 std::set。如果您需要应用大量集合操作,我认为这会更好。这有它自己的开销(作为一棵树),但如果你真的发现这是一个效率问题,你可以实现一个快速内存分配器来超快速地插入和删除元素(注意:只有在你分析并确定这是瓶颈)。

警告:进入有些复杂的领域。

您可以考虑另一种解决方案,如果适用,它可能会非常快,而且您不必担心对数据进行排序。基本上,让任何共享相同 ID 的 MyType 对象组存储一个共享计数器(例如:指向 unsigned int 的指针)。

这将需要创建一个 ID 到计数器的映射,并且需要在每次基于其 ID 创建 MyType 对象时从映射中获取计数器。由于您拥有具有重复 ID 的 MyType 对象,因此您不必像创建 MyType 对象那样频繁地插入到地图中(大多数可能只获取现有的计数器)。

除此之外,还有一个全局“遍历”计数器,每当它被获取时就会递增。

static unsigned int counter = 0;
unsigned int traversal_counter()
{
    // make this atomic for multithreaded applications and
    // needs to be modified to set all existing ID-associated
    // counters to 0 on overflow (see below)
    return ++counter;
}

现在让我们回到 A 和 B 向量存储 MyType* 的地方。要获取 A 中不在 B 中的元素,我们首先调用 traversal_counter()。假设这是我们第一次调用它,这将给我们一个遍历值 1。

现在遍历 B 中的每个 MyType* 对象,并将每个对象的共享计数器设置为从 0 到遍历值 1。

现在遍历 A 中的每个 MyType* 对象。计数器值与当前遍历值 (1) 不匹配的是 A 中不包含在 B 中的元素。

当遍历计数器溢出时会发生什么?在这种情况下,我们遍历存储在 ID 映射中的所有计数器,并将它们与遍历计数器本身一起设置回零。如果它是 32 位无符号整数,则这只需要在大约 40 亿次遍历中发生一次。

这是您可以应用于给定问题的最快解决方案。它可以对未排序的数据执行任何线性复杂度的集合操作(​​并且始终,不仅仅是在像哈希表这样的最佳情况下),但它确实引入了一些复杂性,所以只有在你真的需要时才考虑它。

【讨论】:

    【解决方案2】:

    根据ID对两个向量(std::sort)进行排序,然后使用std::set_difference。例如,您将需要定义一个自定义比较器以传递给这两种算法

    struct comp
    {
        bool operator()(MyType * lhs, MyType * rhs) const
        {
            return lhs->id < rhs->id;
        }
    };
    

    【讨论】:

    • 您好 Avakar,感谢您的回复!您能否举例说明如何使用 set_difference 以使我获得具有差异的新向量?
    • OP 能否只为MyType 定义operator&lt;
    • @Bill 如果他存储 MyType*,则不会。
    • @pollux std::set_difference 将 OutputIterator 作为最后一个参数。使用std::back_insertervector&lt;MyType*&gt; 创建一个。
    【解决方案3】:

    首先看问题。您想要“A 中的所有内容而不是 B 中的所有内容”。这意味着您将不得不访问“A 中的所有内容”。您还必须访问 B 中的所有内容以了解 B 中存在和不存在的内容。因此这表明应该有一个 O(n) + O(m) 解决方案,或者冒昧地忽略 n 和 m 之间的差异 O(2n)

    让我们考虑std::set_difference 方法。每个排序是O(n log n),set_difference 是O(n)。所以 sort-sort-set_difference 方法是O(n + 2n log n)。我们称之为O(4n)

    另一种方法是首先将 B 的元素放在一个集合(或映射)中。遍历 B 以创建集合是 O(n) 加上每个元素的插入 O(log n),然后是遍历 A O(n),查找 A 的每个元素 (log n),得到总数:O(2n log n)。我们叫它O(3n),稍微好一点。

    最后,使用 unordered_set(或 unordered_map),假设我们得到O(1) 插入和O(1) 查找的平均情况,我们有一个方法是O(2n)。啊哈!

    真正的胜利在于 unordered_set(或 map)可能首先是表示数据的最自然选择,即,正确的设计会产生优化的实现。这并不总是发生,但发生时很好!

    【讨论】:

    • “[...] 方法是 O(n + 2n log n)。我们称之为 O(4n)。”您需要阅读以下内容:en.wikipedia.org/wiki/Big_O_notation
    • 很好地分析了算法的复杂性,并且很好地指出了 unordered_set,但是无序向量的情况是 O(N) * O(M),而不是 O(N) + O(M) ,或 O(N^2)。我们必须在 B 中重复搜索 A 中的每个元素。O(NlogN) 也与 O(2N) 有很大不同。如果 N 为 1000,则 2N 为 2000,而 NlogN 为 ~10,000。我认为我们不能把它简化那么多。
    【解决方案4】:

    如果 B 先存在于 A,那么在填充 A 时,您可以在 C 向量中记账。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-24
      • 2016-05-04
      • 1970-01-01
      • 2019-12-18
      • 2016-01-13
      • 2013-08-08
      • 2012-06-26
      相关资源
      最近更新 更多