簡體   English   中英

C ++中集合的有效集合交集

[英]Efficient set intersection of a collection of sets in C++

我有一個std::set 我想以最快的方式找到該集合中所有集合的交集。 集合中的集合數量通常很小(〜5-10),每個集合中的元素數量通常少於1000,但偶爾可以增加到10000左右。但是我需要做這些交集成千上萬的時間,盡快。 我嘗試對幾種方法進行基准測試,如下所示:

  1. std::set對象中的就地交集,該對象最初復制第一組。 然后,對於后續集合,它會迭代其自身的所有元素以及集合的第i個集合,並根據需要從自身中刪除項目。
  2. 使用std::set_intersection到臨時std::set ,將內容交換到當前集合,然后再次找到當前集合與下一個集合的交集,並插入到臨時集合中,依此類推。
  3. 像1)中一樣手動遍歷所有集合的所有元素,但是使用vector代替std::set作為目標容器。
  4. 與4中相同,但是使用std::list而不是vector ,懷疑list會從中間提供更快的刪除速度。
  5. 使用哈希集( std::unordered_set )並檢查所有集中的所有項目。

事實證明,當每個集合中的元素數量較小時,使用vector的速度略快,而對於更大的集合,使用list的速度略快。 就地使用set要比兩者都慢得多,其次是set_intersection和哈希集。 是否有更快的算法/數據結構/技巧來實現這一目標? 如果需要,我可以發布代碼段。 謝謝!

您可能想嘗試std::set_intersection()的概括:算法是對所有集合使用迭代器:

  1. 如果有任何迭代器到達其對應集合的end() ,則操作完成。 因此,可以假定所有迭代器都是有效的。
  2. 將第一個迭代器的值作為下一個候選值x
  3. 在迭代器列表中移動,並在第一個元素std::find_if()中移動至少與x一樣大的元素。
  4. 如果該值大於x則將其設為新的候選值,然后按迭代器順序再次搜索。
  5. 如果所有迭代器都在值x您找到了交集的元素:記錄該交集,增加所有迭代器,重新開始。

晚上是個好顧問,我想我可能有個主意;)

  • 如今,內存要比CPU慢得多,如果所有數據都適合L1緩存,但它很容易溢出到L2或L3:5組1000個元素已經是5000個元素,這意味着5000個節點,並且一個集合節點包含至少3個指針+對象(即32位計算機上至少16個字節,而64位計算機上至少32個字節)=>至少有80k的內存,而最近的CPU對於L1D來說只有32k,所以我們已經在溢出進入L2
  • 先前的事實因以下問題而變得更加復雜:設置節點可能分散在內存周圍,並且沒有緊密包裝在一起,這意味着高速緩存行的一部分充滿了完全不相關的內容。 可以通過提供一個使節點相互靠近的分配器來緩解這種情況。
  • 而且,事實是,CPU在順序讀取方面要好得多(它們可以在需要之前預取內存,因此您不必等待它)比隨機讀取要好得多(不幸的是,樹形結構會導致隨機讀取)閱讀)

這就是為什么速度很重要的原因, vector (或deque )是如此出色的結構:它們在內存中發揮得很好。 因此,我絕對建議使用vector作為我們的中介結構; 盡管只需要小心地從四肢插入/刪除四肢,以避免重新定位。

所以我想到了一個相當簡單的方法:

#include <cassert>

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

// Do not call this method if you have a single set...
// And the pointers better not be null either!
std::vector<int> intersect(std::vector< std::set<int> const* > const& sets) {
    for (auto s: sets) { assert(s && "I said no null pointer"); }

    std::vector<int> result; // only return this one, for NRVO to kick in

    // 0. Check obvious cases
    if (sets.empty()) { return result; }

    if (sets.size() == 1) {
        result.assign(sets.front()->begin(), sets.front()->end());
        return result;
    }


    // 1. Merge first two sets in the result
    std::set_intersection(sets[0]->begin(), sets[0]->end(),
                          sets[1]->begin(), sets[1]->end(),
                          std::back_inserter(result));

    if (sets.size() == 2) { return result; }


    // 2. Merge consecutive sets with result into buffer, then swap them around
    //    so that the "result" is always in result at the end of the loop.

    std::vector<int> buffer; // outside the loop so that we reuse its memory

    for (size_t i = 2; i < sets.size(); ++i) {
        buffer.clear();

        std::set_intersection(result.begin(), result.end(),
                              sets[i]->begin(), sets[i]->end(),
                              std::back_inserter(buffer));

        swap(result, buffer);
    }

    return result;
}

看來是正確的 ,但是顯然我不能保證它的速度。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM