簡體   English   中英

用於隨機訪問和遍歷元素的最佳數據結構(在C ++中)

[英]Optimal data structure (in C++) for random access and looping through elements

我有以下問題:我有一組N個元素(N在幾百到幾千個元素之間,比如說在500到3000個元素之間)。 在這些元素中,小百分比將具有某些屬性“ X”,但是元素“獲得”和“丟失”該屬性的方式是半隨機的。 因此,如果我將它們全部存儲在一個數組中,然后將1分配給具有屬性X的元素,否則將其分配為零,則此N個元素的數組將具有n個1和Nn個零(n在20-50范圍內很小)。

問題如下:這些元素以半隨機的方式非常頻繁地更改(這意味着任何元素都可以從0翻轉到1,反之亦然,但是控制該過程的過程有些穩定,因此總數“ n”波動一點,但在20-50范圍內相當穩定); 我經常需要集合中的所有“ X”元素(換句話說,就是數組的索引,其中數組的值為1),以對它們執行某些任務。

一種簡單而緩慢的方法是簡單地遍歷數組,如果索引k的值為1,則執行該任務,但這有點慢,因為超過95%的所有元素的值為1。將所有1放入不同的結構(具有n個元素)中,然后遍歷該結構,而不是遍歷所有N個元素。 問題是最好使用什么結構?

元素將從0隨機翻轉到1,反之亦然(從幾個不同的線程開始),因此沒有任何順序(元素從0翻轉到1與時間無關,它將隨時間翻轉)我遍歷它們(從另一個線程),我不需要以任何特定的順序進行遍歷(換句話說,我只需要獲取它們的全部即可,但這與以什么順序無關)。

有什么建議對此的最佳結構是什么? 我想到了“ std :: map”,但是由於對std :: map的鍵進行了排序(而且我不需要該功能),所以問題是是否有更快的東西?

編輯:澄清一下,數組示例只是解決問題的一種(慢速)方法。 問題的實質是,在帶有“ N”個元素的一個大集合“ S”中,有一個不斷變化的“ n”個元素的子集“ s”(n遠小於N),我需要循環設置“ s”。 速度是至關重要的,既要在“ s”中添加/刪除元素,又要在它們之間循環。 因此,盡管從迭代的角度來看,像擁有2個數組並在它們之間移動元素這樣的建議會很快,但向數組中添加和刪除元素的速度卻會非常慢。 聽起來像std :: set之類的基於散列的方法在迭代和添加/刪除方面都可以相當快地工作,問題是有什么更好的方法嗎? 閱讀有關“ unordered_map”和“ unordered_set”的文檔並沒有真正弄清楚相對於std :: map和std :: set而言,元素的添加/刪除速度有多快,或者通過它們進行迭代的速度有多慢。 要記住的另一件事是,我不需要一個在所有情況下都效果最佳的通用解決方案,我需要一個在N處於500-3000范圍內且n處於20-50范圍內時效果最佳的解決方案。 最后,速度確實至關重要。 有很多慢速的方法,所以我正在尋找最快的方法。

由於順序似乎並不重要,因此可以使用單個數組,並在屬性的前面保留元素X。 您還需要一個索引或迭代器,指向數組中從X設置到未設置的過渡點。

  • 要設置X,請增加索引/迭代器,然后將該元素與要更改的元素交換。
  • 要取消X的設置,請執行相反的操作:減少索引/迭代器,然后將該元素交換為要更改的元素。

自然,對於多個線程,您將需要某種互斥量來保護數組和索引。

編輯:如通常使用迭代器一樣,要保持半開范圍,則應顛倒上述操作的順序:交換, 然后遞增/遞減。 如果保留索引而不是迭代器,則索引將作為X數量的計數加倍。

N=3000並不是很多。 如果每個位使用一個位,則結構小於400字節。 您可以使用std::bitset 如果您使用unordered_setset ,請注意,您將為列表中的n元素中的每個元素花費更多的字節:如果僅為64位體系結構中的每個元素分配一個指針,則至少要使用8 * 50 = 400字節,遠遠超過位集

@geza:也許我誤解了你所說的兩個數組的含義; 我假設您的意思是說有一個std :: vector(或類似的東西),其中我存儲了所有具有屬性X的元素,而另一個我存儲了其余元素? 實際上,我不在乎其他人,因此我確實需要一個陣列。 如果可以將元素添加到數組的末尾,添加元素顯然很簡單; 現在,如果我在這里錯了,請更正我,但是在該數組中找到一個元素是O(n)操作(因為該數組未排序),然后再次從數組中刪除它需要將所有元素移位一個位置,因此這平均需要n / 2次運算。 如果我使用鏈表而不是向量,則刪除元素的速度更快,但是找到它仍然需要O(n)。 這就是我說過慢的意思。 如果我誤解了您,請澄清一下。

聽起來好像std :: unordered_set或std :: unordered_map在添加/刪除元素方面最快,因為O(1)可以找到一個元素,但是我不清楚一個循環遍歷所有鍵有多快。 該文檔明確指出,通過std :: unordered_map的鍵進行的迭代比通過std :: map的鍵進行的迭代要慢,但是並未以任何方式量化“慢”和“快”的速度。

最后,重復一遍,我對一般解決方案不感興趣,我對小“ n”感興趣。 因此,例如,如果我有兩個解決方案,一個是k_1 * log(n),第二個是k_2 * n ^ 2,第一個原則上可能會更快(對於大n而言),但是如果k_1 >> k_2(比如說例如k_1 = 1000且k_2 = 2且n = 20),則第二個對於相對較小的“ n”仍然可以更快(1000 * log(20)仍大於2 * 20 ^ 2)。 因此,即使可以在恆定時間O(1)中完成std :: unordered_map中的添加/刪除操作,對於較小的“ n”,恆定時間為1納秒或1微秒或1毫秒仍然很重要。 因此,我確實在尋找最適合小“ n”而不是大“ n”的漸近極限的建議。

另一種方法(我認為僅當元素的數量增加至少十倍時才值得)可能會保持雙倍索引:

#include<algorithm>
#include<vector>

class didx {
        // v == indexes[i] && v > 0  <==> flagged[v-1] == i
        std::vector<ptrdiff_t> indexes;
        std::vector<ptrdiff_t> flagged;
public:
        didx(size_t size) : indexes(size) {}
        // loop through flagged items using iterators
        auto begin() { return flagged.begin(); }
        auto end() { return flagged.end(); }

        void flag(ptrdiff_t index) {
                if(!isflagged(index)) {
                        flagged.push_back(index);
                        indexes[index] = flagged.size();
                }
        }
        void unflag(ptrdiff_t index) {
                if(isflagged(index)) {
                        // swap last item with item to be removed in "flagged", update indexes accordingly
                        // in "flagged" we swap last element with element at index to be removed
                        auto idx = indexes[index]-1;
                        auto last_element = flagged.back();
                        std::swap(flagged.back(),flagged[idx]);
                        std::swap(indexes[index],indexes[last_element]);

                        // remove the element, which is now last in "flagged"
                        flagged.pop_back();
                        indexes[index] = 0;
                }

        }
        bool isflagged(ptrdiff_t index) {
                return indexes[index] > 0;
        }
};

暫無
暫無

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

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