ただし、効率的なローカル操作を提供するやや複雑な表現は次のとおりです。
struct Link;
struct Node
{
Link *firstIn, *lastIn, *firstOut, *lastOut;
... node data ...
};
struct Link
{
Node *from, *to;
Link *prevInFrom, *nextInFrom, *prevInTo, *nextInTo;
... link data ...
};
基本的に、それぞれNode
に 2 つの二重リンク リストがあり、1 つは着信リンク用で、もう 1 つは発信リンク用です。それぞれLink
が開始と終了Node
を認識し、それを含む 2 つのリスト ("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 ...
};
ほとんどの場合、リンクを順方向にトラバースし、リンクが既存のノードに追加されていない場合は、ノードと発信リンク データに 1 つのメモリ チャンクを使用するだけでよいのですが、残念ながらこれは簡単ではありません。 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))
ノードにスペースを割り当てるために使用します。
このアプローチでは、ノードとその発信リンクのすべてのデータが隣接するメモリ ロケーションに配置されます。