繁体   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