0

所以我遇到了一个编程竞赛问题,涉及在不同的图表上运行大量 DFS。

起初,我将我的图(邻接表表示)表示为一个集合向量:

vector< set<int> > graph;

每次我使用空集时,还要根据给定的节点数初始化图形:

set<int> tmpSet;

我像这样初始化它:

for(int j=0;j<N;j++)//N was the number of nodes needed for the graph
   graph.push_back(tmpSet);

我用过

graph.clear();

每次清空图表。

为了在之后插入边,我使用了 std::set 的插入功能。

//Insert directed edge from u to v
graph[u].insert(v);
graph[v].insert(u);

结果是程序消耗了太多内存并且速度太慢而无法通过测试。使用 push_back 函数的 std::list 也发生了同样的事情,这是一个恒定时间操作。然后,当我更改为 std::vector 时,内存消耗变得最小,我在 3 秒内通过了测试,而 std::set 和 std::list 即使在 20 秒内也无法通过测试。

我的问题是它与释放内部集合和列表的空间有关,但是向量的行为如何不同呢?

所以我的问题是,是否有人可以解释为什么会发生这种情况,以便我可以更好地理解 stl 容器在诸如您在另一个容器中有一个容器的情况下如何表现。

编辑:一些额外的信息:节点数约为 N=3000,测试次数超过 1000。这意味着我必须创建 1000 多个图表,这些图表都保存在变量“图表”中。我也知道 set 在 O(lgn) 时间内插入,而 vector 和 list 在 O(1) 中,所以我理解为什么 set只需要比向量长一点的时间。但是为什么 std::list 也失败了?还要让我提一下,set 和 list 以 100Mb 的内存使用完成,而 vector 以 3Mb 完成

好的最终编辑,这是我的代码,以准确显示我如何使用图表(列表版本)。程序中的其他任何地方都不会发生任何内存释放或更改图形数据。

vector< list<int> > graph;
list<int> tmpList;
int T; //num of test cases
int N; //num of nodes
int M; //num of edges
int main ()
{
    int u,v;
    scanf("%d",&T);//Read test cases
    for(int i=0;i<T;i++){

        scanf("%d %d",&N,&M);//Read number of nodes and number of edges
        for(int j=0;j<N;j++)
            graph.push_back(tmpList);

        for(int j=0;j<M;j++){
            scanf("%d %d",&u,&v);//Read edge from u to v
            graph[u].push_back(v);
            graph[v].push_back(u);
        }
        dfs();
        graph.clear();
    }
}
4

3 回答 3

2

当您使用std::set保存相邻节点编号时,您插入并以对数时间获取一个很慢的元素。但是当您使用std::vector insert(push_back) 并获取一个元素时,是在恒定时间内完成的,因此存在时间差异。所以std::vector当你不需要在集合中找到某个元素时应该使用,std::set否则使用。

std::list 和之间的区别std::vector可能是因为clear功能。因为list它是线性的,但vector它是原子化的常数。

于 2013-04-10T12:03:56.283 回答
1

订购了一套。根据您提供的函子,它保证保持特定的顺序。无论您添加或删除什么元素(除非您添加重复项,这在集合中是不允许的),它始终是有序的。

向量具有完全且仅具有您明确给出的顺序。向量中的项目是您放置它们的位置。如果你把它们乱序,那么它们就是乱序的;您现在需要对容器进行分类以将它们按顺序放回原处。

诚然,set 的用途相对有限。通过适当的纪律,可以将项目插入向量并保持有序。但是,如果您不断地在容器中插入和删除项目,vector 会遇到很多问题。它将进行大量元素的复制/移动等,因为它实际上只是一个数组。

将项目插入向量所需的时间与向量中已有项目的数量成正比。将一个项目插入一个集合所花费的时间与项目数量的对数成正比。如果项目的数量很大,那将是一个巨大的差异。对数(100,000)为 5;这是一个重大的速度改进。移除也是如此。

但是,如果您在初始化时一次完成所有插入,则没有问题。您可以将所有内容插入向量中,对其进行排序(支付一次费用),然后使用标准算法对已排序的向量进行查找元素并遍历已排序的列表。虽然对集合元素的迭代并不是很慢,但对向量的迭代更快。

因此,在某些情况下,已排序的向量胜过一组。话虽如此,除非您知道有必要,否则您真的不应该为这种优化的费用而烦恼。因此,除非您对正在编写的系统有经验(因此知道您需要那种性能)或手头有分析数据告诉您需要向量而不是集合,否则请使用集合。

于 2013-04-10T12:09:46.627 回答
0

像往常一样,性能问题的最佳答案是为您的用例分析两种实现,看看哪个更快。

一般来说,如果您在数据结构中插入(除了末尾之外),那么向量可能会更慢,否则在大多数情况下,如果仅针对数据局部性问题,向量预计会比列表执行得更好,这意味着如果两个元素在数据集中是相邻的 在内存中是相邻的,那么下一个元素将已经在处理器的缓存中,并且不必将内存页面错误地放入缓存中。

还要记住,向量的空间开销是恒定的(3 个指针),而列表的空间开销是为每个元素支付的,这也减少了可以驻留在缓存中的完整元素的数量(数据加上开销)任何时候。

于 2013-04-10T12:06:30.517 回答