一个有点复杂但提供高效本地操作的表示如下
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))
为节点分配空间。
使用这种方法,节点及其传出链接的所有数据都将位于相邻的内存位置。