Graph 在内存中存储图形的三种方法及其优缺点

Graph 在内存中存储图形的三种方法及其优缺点,graph,Graph,在内存中存储图形有三种方法: 节点作为对象,边作为指针 包含编号节点x和节点y之间所有边权重的矩阵 编号节点之间的边列表 我知道如何写这三本书,但我不确定我是否考虑过每本书的优点和缺点 将图形存储在内存中的每种方法的优缺点是什么?分析这些方法的一种方法是根据内存和时间复杂性(这取决于您希望如何访问图形) 将节点存储为具有指向彼此的指针的对象 这种方法的内存复杂性为O(n),因为对象的数量与节点的数量相同。所需的(指向节点的)指针数最多为O(n^2),因为每个节点对象可能包含最多n个节点的指针

在内存中存储图形有三种方法:

  • 节点作为对象,边作为指针
  • 包含编号节点x和节点y之间所有边权重的矩阵
  • 编号节点之间的边列表
  • 我知道如何写这三本书,但我不确定我是否考虑过每本书的优点和缺点


    将图形存储在内存中的每种方法的优缺点是什么?

    分析这些方法的一种方法是根据内存和时间复杂性(这取决于您希望如何访问图形)

    将节点存储为具有指向彼此的指针的对象

    • 这种方法的内存复杂性为O(n),因为对象的数量与节点的数量相同。所需的(指向节点的)指针数最多为O(n^2),因为每个节点对象可能包含最多n个节点的指针
    • 对于访问任何给定节点,该数据结构的时间复杂度为O(n)
    存储边权重矩阵

    • 对于矩阵,这将是O(n^2)的内存复杂度
    • 这种数据结构的优点是,访问任何给定节点的时间复杂度为O(1)

    根据在图上运行的算法以及有多少节点,您必须选择合适的表示形式

    我认为您的第一个示例有点模棱两可——节点作为对象,边作为指针。您可以通过只存储指向某个根节点的指针来跟踪这些节点,在这种情况下,访问给定节点可能效率低下(例如,您需要节点4-如果没有提供节点对象,您可能需要搜索它)。在这种情况下,还将丢失从根节点无法访问的部分图形。我认为这就是f64 rainbow假设的情况,他说访问给定节点的时间复杂度是O(n)

    否则,还可以保留一个数组(或hashmap),其中包含指向每个节点的指针。这允许O(1)访问给定节点,但会稍微增加内存使用量。如果n是节点数,e是边数,则该方法的空间复杂度为O(n+e)

    矩阵方法的空间复杂度将沿O(n^2)(假设边是单向的)。如果你的图是稀疏的,你的矩阵中会有很多空单元格。但是,如果您的图是完全连通的(e=n^2),则与第一种方法相比,这是有利的。正如RG所说,如果您将矩阵分配为一块内存,那么使用这种方法也可能会有更少的缓存未命中,这可以使跟踪图形周围的许多边更快


    第三种方法在大多数情况下可能是最节省空间的方法—O(e)—但会使查找给定节点的所有边成为一项O(e)杂务。我想不出这在什么情况下会非常有用。

    好吧,如果边没有权重,矩阵可以是一个二进制数组,在这种情况下,使用二进制运算符可以使事情进行得非常非常快

    如果图形是稀疏的,则对象/指针方法似乎效率更高。将对象/指针保存在数据结构中,以便将它们引导到单个内存块中,这可能是一个很好的计划,也可能是使它们保持在一起的任何其他方法

    邻接列表——简单的连接节点列表——似乎是迄今为止内存效率最高的,但也可能是最慢的


    使用矩阵表示法反转有向图很容易,使用邻接列表也很容易,但使用对象/指针表示法反转有向图就不那么容易了。

    还有几件事需要考虑:

  • 通过将权重存储在矩阵中,矩阵模型更容易生成具有加权边的图。对象/指针模型需要在并行数组中存储边权重,这需要与指针数组同步

  • 与无向图相比,对象/指针模型更适合于有向图,因为指针需要成对维护,这可能会变得不同步


  • 正如一些人指出的,objects和pointers方法存在搜索困难,但对于构建二叉搜索树(其中有很多额外的结构)这样的事情来说是非常自然的


    我个人喜欢邻接矩阵,因为它们使用代数图论中的工具使各种问题变得更容易。(例如,邻接矩阵的第k次幂给出了从顶点i到顶点j的长度为k的路径数。在取第k次幂之前添加一个单位矩阵,以获得长度为的路径数还有另一个选项:节点作为对象,边也作为对象,每条边同时在两个双链接列表中:所有路径的列表来自同一节点的边以及进入同一节点的所有边的列表

    struct Node {
        ... node payload ...
        Edge *first_in;    // All incoming edges
        Edge *first_out;   // All outgoing edges
    };
    
    struct Edge {
        ... edge payload ...
        Node *from, *to;
        Edge *prev_in_from, *next_in_from; // dlist of same "from"
        Edge *prev_in_to, *next_in_to;     // dlist of same "to"
    };
    
    内存开销很大(每个节点2个指针,每个边6个指针),但是

    • O(1)节点插入
    • O(1)边插入(给定指向“从”和“到”节点的指针)
    • O(1)边缘删除(给定指针)
    • O(deg(n))节点删除(给定指针)
    • O(deg(n))查找节点的邻居
    该结构还可以表示一个相当通用的图:带循环的定向多重图(即,在相同的两个节点之间可以有多条不同的边,包括多条不同的循环-从x到x的边)


    更详细地解释了这种方法。

    < P>在维基百科上查看。它很好地理解了何时使用图的每一种表示。

    只有当图是非常连通的或非常小的时候,才会考虑矩阵。对于稀疏连通的图,对象/指针或列表的边缘方法将是BOT。h可以更好地使用内存。我很好奇,除了我忽略的存储之外,还有什么。;)它们在时间复杂度上也不同,矩阵是O(1),并且