用于表示多重图的良好数据结构(C ++)

11
最佳的数据结构是什么来描述一个未定向多重图(优化了速度和内存)?
使用边缘列表不合适,因为在我的代码中经常需要获取顶点的邻居。
邻接表不好,因为我必须保留有关已访问边缘的信息,当访问从1到3的边缘时(假设我正在遍历1的邻居并找到导致三个权重为W的边缘),我必须在3的邻居列表中找到相同的边缘,使其标记为已访问,这是很慢的。
我考虑过邻接矩阵,其中每个单元格都是表示有关是否访问顶点,边缘重量等信息的结构set< Edge >. 但是,当将graph [0] [1] [i]设置为已访问时,我无法在 graph [1] [0] 的边缘中设置相同的边缘,而无需线性搜索。
在表示多重图时,是否有任何好的方法和技术?我不想使用第三方库解决方案,如boost :: AdjacencyList; 我必须自己编写。
编辑:对不起,有误解。 这是大学的练习,我只能使用Standard库来完成它。图形有约束: 0
我有32 MB的内存限制和0.5秒的时间限制(我必须使用DFS遍历)。

好的,关于C++...请不要称呼这个可怜的语言为"Cplusplus",因为这会伤害它和我。 - user529758
3
选择好的表达方式可能取决于图的密度和直径。 - Basile Starynkevitch
2
如果你真的把Boost称为“我不想使用的第三方库”,那么你基本上是在割掉自己的右(左)手。它提供了许多优秀的工具,就像一个好的STL一样重要。除非这个要求来自于经理/任务规范/平台/教育原因等,否则请重新考虑。 - quetzalcoatl
请提供一些反对boost::graph的论据,它有许多不同的表示形式(邻接表、矩阵、压缩稀疏矩阵...),而且它是一个头文件库...唯一的理由可能是极端的性能问题,但如果您有这样的问题,那么您就不会问这样的问题了。如果这是一个练习,请告诉我。 - Tristram Gräbener
2
@TristramGräbener:不要告诉他标记为“homework”,那是一个被禁止的标记。但他应该在问题正文中提到它。 - Ben Voigt
显示剩余4条评论
1个回答

4
一个有一定复杂度但能提供高效本地操作的表示如下:
struct Link;

struct Node
{
    Link *firstIn, *lastIn, *firstOut, *lastOut;
    ... node data ...
};

struct Link
{
    Node *from, *to;
    Link *prevInFrom, *nextInFrom, *prevInTo, *nextInTo;
    ... link data ...
};

基本上每个 Node 都有两个双向链表,一个用于入站链接,另一个用于出站链接。每个 Link 都知道起始和结束的 Node,并且还具有包含它的两个列表中的 prev 和 next 指针(在“from”节点的出站列表和“to”节点的入站列表中的出站列表)。通过使用这种方法,您可以获得 O(1) 的节点和链接创建和销毁,O(inbound_deg(node)) 用于查找到达节点的哪些链接,O(outbound_deg(node)) 用于查找离开节点的哪些链接。该结构还支持同一对节点之间的多个连接以及多个循环。
每个节点和每个链接所需的空间是固定的,但是根据应用程序而言,开销可能会好或不好(每个节点4个指针,每个链接6个指针)。如果使用简单列表而不是双向链表,则开销变为每个节点2个指针,每个链接4个指针,但是链接删除变为 O(outbound_deg(from) + inbound_deg(to)) 并且不再是常量。
还要注意的是,所示结构不适合缓存,并且在现代桌面计算机中,可能更加“暴力”的方法(例如指针向量而不是双向链表)可以根据列表的大小以及您如何频繁地改变图形结构而提供更好的速度。
甚至可能有意义将链接对象拆分为将前向链接数据嵌入“from”节点中,在“to”节点中保留后向指针。
struct Node
{
    struct OutgoingLink
    {
        Node *to;
        int incoming_index;
        ... link data ...
    };

    struct IncomingLink
    {
        Node *from;
        int outgoing_index;
    };

    std::vector<OutgoingLink> outgoing_links;
    std::vector<IncomingLink> incoming_links;

    ... node data ...
};

如果你大部分时间都在前向遍历链接,而且链接不会添加到现有节点中,那么更好的方法是只使用一个内存块来存储节点和出站链接数据,但不幸的是,C++ 不容易支持这种方式。
在 C 中,可以这样实现:
typedef struct TOutgoingLink
{
    struct TNode *to;
    int incoming_index;
    ... link data ...
} OutgoingLink;

typedef struct TIncomingLink
{
    struct TNode *from;
    int outgoing_index;
} IncomingLink;

typedef struct TNode
{
    ... node data ...
    int num_incoming_links;
    int num_outgoing_links;
    IncomingLink *incoming_links;   // separate array
    OutgoingLink outgoing_links[1]; // embedded array starting here
} Node;

使用malloc(sizeof(Node) + (num_outgoing_links-1)*sizeof(OutgoingLink))来为节点分配空间。
采用这种方法,节点及其出链的所有数据都将位于相邻的内存位置。

1
... 除了因为缓存行为而更有效的 vector<Link*> 外。 - Ben Voigt
这真的取决于出站和入站度有多大。使用向量而不是双向链表是另一种具有不同权衡的合理解决方案。 - 6502

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接