【问题标题】:Graph representation with vectors vs lists in C++C++ 中向量与列表的图形表示
【发布时间】:2013-04-10 11:52:09
【问题描述】:

所以我遇到了一个编程竞赛问题,涉及在不同的图表上运行大量 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 也发生了同样的事情,这是一个 constant 时间操作。然后,当我更改为 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();
    }
}

【问题讨论】:

  • 当您删除测试逻辑完全并简单地加载您的容器,然后释放它们时,表现出的行为是什么?提供展示您观察到的行为的 SSCCE 的可能性很小吗?您是否考虑过“清除”的交换/清除成语?再说一次,在您的“测试”期间容器是如何访问/枚举的?如果您想要一个不是纯粹猜测/推测的具体答案,可能需要提供更多信息。

标签: c++ list vector stl


【解决方案1】:

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

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

【讨论】:

  • 我会理解如果是这种情况,但同样的事情发生在 std::list 上,它在 O(1) 时间内提供 push_back。
【解决方案2】:

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

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

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

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

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

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

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-23
      • 1970-01-01
      • 2014-02-08
      • 2013-11-08
      • 1970-01-01
      相关资源
      最近更新 更多