用于随机访问和循环元素

时间:2017-10-20 16:41:10

标签: c++ data-structures

我有以下问题:我有一组N个元素(N介于几百到几千个元素之间,假设介于500到3000个元素之间)。在这些元素中,小百分比将具有一些属性“X”,但元素以半随机的方式“获得”和“失去”该属性;因此,如果我将它们全部存储在一个数组中,并将1分配给具有属性X的元素,否则为零,此N个元素的数组将具有n 1和N-n个零(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范围内。最后,速度真的很重要;有很多缓慢的方法,所以我正在寻找最快的方式。

4 个答案:

答案 0 :(得分:4)

由于顺序看起来并不重要,您可以使用单个数组并在前面保留属性X的元素。您还需要一个索引或迭代器,指向数组中从X set到unset的转换。

  • 要设置X,请递增索引/迭代器并将该元素与要更改的元素交换。
  • 要取消设置X,请执行相反的操作:递减索引/迭代器并将该元素与要更改的元素交换。

当然,对于多个线程,您需要某种互斥锁来保护数组和索引。

编辑:为了保持半开放范围,因为通常使用迭代器,你应该颠倒上面操作的顺序:swap,然后递增/递减。如果你保留一个索引而不是一个迭代器,那么索引会作为X的数量计算双重任务。

答案 1 :(得分:1)

N=3000并非如此。如果为每个位使用单个位,则结构小于400个字节。您可以使用std::bitset。如果您使用unordered_setset但请注意,您将为列表中的每个n元素花费更多字节:如果您只是为在64位架构中的每个元素,您将使用至少8 * 50 = 400字节,远远超过bitset

答案 2 :(得分:0)

@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)。因此,即使std :: unordered_map中的添加/删除可能在恒定时间O(1)中完成,对于小“n”,如果该恒定时间是1纳秒或1微秒或1毫秒仍然很重要。所以我真的在寻找最适合小“n”的建议,而不是大型“n”的渐近极限。

答案 3 :(得分:0)

另一种方法(在我看来,只有元素数量增加至少十倍才有价值)可能会保持双重指数:

#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;
        }
};