データ構造における重要な概念として、グラフは世界を繋ぐリンクの役割を果たします。ツリーやバイナリ ツリーと比較して、グラフはより柔軟で多様性があり、ソーシャル ネットワークにおける個人間のつながり、都市交通におけるルート計画、電子ネットワークにおける通信経路など、さまざまな実際的な問題における複雑な関係を記述することができます。
あなたが初心者であろうと上級者であろうと、この記事は、データ構造とアルゴリズムにおけるグラフの重要性をよりよく理解し、それによってデータ構造とアルゴリズムの能力を向上させるのに役立つ、シンプルで理解しやすい、実践的で実行可能な知識ポイントを提供します。問題を解決するためのアルゴリズム。次に、データ構造とアルゴリズムの素晴らしい旅に乗り出しましょう。
目次
グラフの基本概念
グラフは、一連の頂点 (ノード) とこれらの頂点を接続するエッジ (関係) で構成される非線形データ構造です。グラフは要素間の関係を表し、ノードとエッジを介してグラフ内の要素間の接続を記述するために使用されます。
グラフの定義には次の要素が含まれます
頂点: グラフ内のノードはエンティティまたはオブジェクトを表し、通常は円またはボックスで表されます。各ノードには、名前、属性などの関連情報を含めることができます。
エッジ: グラフ内のエッジは頂点間の関係を表し、異なる頂点を接続するために使用されます。エッジは、有向 (方向を示す矢印) または無向 (矢印なし) にすることができ、また重み付けする (エッジの重みを表す) こともできます。
隣接ポイント: 頂点の場合、それに直接接続されている頂点は隣接ポイントと呼ばれます。隣接する点はエッジによって接続されます。
パス: パスとは、頂点間を通過する一連のエッジを指します。パスの長さは、パスが通過するエッジの数によって計算できます。
円: パスの始点と終点が同じ頂点で、少なくとも 1 つのエッジを通過する場合、円が形成されます。円はループとも呼ばれます。
有向グラフと無向グラフ:
顶点的度:
無向グラフの場合、頂点 v の次数は、TD( v) として表される、頂点に接続されているエッジの数を指します。
有向グラフについて説明したいのは、頂点の入次数と出次数です。
頂点 v の次数は、入次数と出次数の合計、つまり TD(v) = ID(v) + OD(v) に等しくなります。
Entry は頂点 v で終わる有向辺の数であり、ID(V) として記録されます。出次数 は、頂点 v から始まる有向エッジの数であり、OD(v) として記録されます。
頂点間の関係の説明:
接続グラフと強接続グラフ:
サブ図: (図の一部)
有向グラフの場合、サブグラフと生成されたサブグラフの概念は同じです。
连通分量:
無向グラフ内の最大連結部分グラフは連結成分と呼ばれます。
有向グラフ内の最大の強連結部分グラフは、有向グラフの強連結成分と呼ばれます。
生成树:
接続されたグラフのスパニング ツリーは、グラフ内のすべての頂点を含む最小の接続されたサブグラフです。
生成森林:
切断されたグラフでは、接続されたコンポーネントのスパニング ツリーが、切断されたグラフのスパニング フォレストを構成します。
いくつかの特殊な形式の画像:
要点を整理すると、主な内容は次のようになります。
グラフのストレージ表現
グラフはさまざまな方法で保存および表現できます。主な方法としては、隣接行列と隣接リストの 2 つがあります。
隣接行列法:
隣接行列は、グラフ内のノード間の接続関係を表すために使用される 2 次元配列です。 n 個のノードを持つグラフの場合、隣接行列はサイズ n×n の正方行列になります。ノード i とノード j の間にエッジがある場合、隣接行列の行 i と列 j の要素は 1 になり、それ以外の場合は 0 になります。隣接行列の利点は、2 つのノード間にエッジがあるかどうかを迅速に判断できることですが、欠点は、グラフがまばらな場合に多くのスペースを占有することです。
隣接行列法を使用して重み付きグラフ (ネットワーク) を保存すると、次のようになります。
邻接表法:
有向接続と無向接続に基づいて、現在のノードに接続されている次のノードのインデックスを決定します。
要点を整理すると、主な内容は次のようになります。
クロス リンク リスト方式で有向グラフを保存する:
隣接する複数のテーブルには無向グラフが格納されます:
ノードとエッジを削除すると、対応する結果は次のようになります。
要点を整理すると、主な内容は次のようになります。
グラフの基本操作
2 つのノード間にエッジがあるかどうかを知りたい場合は、隣接行列または隣接リストを使用できます。
グラフに頂点 x を挿入したい場合は、次の方法で行うことができます。
グラフ内の特定の頂点 x を削除したい場合は、次の方法で実行できます。
グラフ内の頂点 x の最初の隣接点を知りたい場合は、頂点番号を返します。x に隣接点がない場合、または x がグラフに存在しない場合は、-1 を返します。
グラフの走査と接続性
グラフ トラバーサルとは、グラフ内のすべてのノードを訪問するプロセスを指します。トラバーサルを通じて、グラフの構造とノード間の関係を包括的に理解できます。一般的に使用されるグラフ走査アルゴリズムには、幅優先検索 (BFS) と深さ優先検索
幅優先探索: グラフの幅優先検索 (BFS) は開始ノードから開始され、最初に開始点に最も近いノードを訪問し、その後、連続して戦略を実行します。レイヤーの外側へのアクセスを拡張します。これにより、開始ノードに近いノードが最初に訪問され、次にわずかに離れたノードが訪問されるようになります。
シーケンスの幅優先トラバーサルの例は次のとおりです。
以下は、C でグラフの幅優先走査を実装するための基本的なコード例です。
#include <stdio.h>
#define MAX_NODES 100
typedef struct {
int neighbor[MAX_NODES];
int numNeighbors;
int visited;
} Node;
void bfs(Node graph[], int start, int numNodes) {
int queue[MAX_NODES];
int front = 0, rear = 0;
// 将起始节点加入队列并标记为已访问
queue[rear++] = start;
graph[start].visited = 1;
while (front != rear) {
int current_node = queue[front++]; // 队首节点出队
printf("%d ", current_node); // 访问当前节点
// 遍历当前节点的邻居节点
for (int i = 0; i < graph[current_node].numNeighbors; i++) {
int neighbor = graph[current_node].neighbor[i];
if (!graph[neighbor].visited) { // 如果邻居节点未被访问过
queue[rear++] = neighbor; // 将邻居节点入队
graph[neighbor].visited = 1; // 标记邻居节点为已访问
}
}
}
}
int main() {
// 创建一个示例图
Node graph[MAX_NODES];
// 初始化图中的节点和边
for (int i = 0; i < MAX_NODES; i++) {
graph[i].numNeighbors = 0;
graph[i].visited = 0;
}
// 添加边关系
graph[0].neighbor[graph[0].numNeighbors++] = 1;
graph[0].neighbor[graph[0].numNeighbors++] = 2;
graph[1].neighbor[graph[1].numNeighbors++] = 3;
graph[2].neighbor[graph[2].numNeighbors++] = 4;
graph[2].neighbor[graph[2].numNeighbors++] = 5;
graph[3].neighbor[graph[3].numNeighbors++] = 6;
graph[4].neighbor[graph[4].numNeighbors++] = 7;
// 进行广度优先遍历
bfs(graph, 0, 8); // 假设图中有 8 个节点
return 0;
}
幅優先スパニング ツリー は、無向グラフまたは有向グラフの特定の開始ノードから開始して幅優先方式でツリー構造を生成するために使用されます。ツリーには最短のものが含まれます。開始ノードから到達可能なすべてのノードまでのパス。
要点を整理すると、主な内容は次のようになります。
深さ優先検索: 深さ優先検索 (DFS) は、最初に可能な限り遠くまで進む戦略です。つまり、現在のパスに沿って最後まで進みます。すべてのノードにアクセスするまで、前のノードに戻って他のパスの探索を続けることはできなくなります。
深さ優先スパニング ツリー は、深さ優先検索アルゴリズムによって生成されたツリー状の構造を指します。ツリーには、指定された開始ノードから始まる到達可能なすべてのノードが含まれます。最短パス。
C での深さ優先トラバーサルの基本的なコード例を次に示します。
#include <stdio.h>
#define MAX_NODES 100
typedef struct {
int neighbor[MAX_NODES];
int numNeighbors;
int visited;
} Node;
void dfs(Node graph[], int current_node) {
printf("%d ", current_node); // 先访问当前节点
graph[current_node].visited = 1; // 标记当前节点为已访问
// 遍历当前节点的邻居节点,递归调用 dfs 函数
for (int i = 0; i < graph[current_node].numNeighbors; i++) {
int neighbor = graph[current_node].neighbor[i];
if (!graph[neighbor].visited) { // 如果邻居节点未被访问过
dfs(graph, neighbor); // 递归访问邻居节点
}
}
}
int main() {
// 创建一个示例图
Node graph[MAX_NODES];
// 初始化图中的节点和边
for (int i = 0; i < MAX_NODES; i++) {
graph[i].numNeighbors = 0;
graph[i].visited = 0;
}
// 添加边关系
graph[0].neighbor[graph[0].numNeighbors++] = 1;
graph[0].neighbor[graph[0].numNeighbors++] = 2;
graph[1].neighbor[graph[1].numNeighbors++] = 3;
graph[2].neighbor[graph[2].numNeighbors++] = 4;
graph[2].neighbor[graph[2].numNeighbors++] = 5;
graph[3].neighbor[graph[3].numNeighbors++] = 6;
graph[4].neighbor[graph[4].numNeighbors++] = 7;
// 进行深度优先遍历
dfs(graph, 0); // 假设从节点 0 开始遍历
return 0;
}
接続されたコンポーネント
連結成分は、無向グラフ内の最大の連結部分グラフを指します。接続されたコンポーネントは、複数の頂点とそれらの間のエッジで構成され、各頂点は他の頂点へのパスを通じて相互に到達できます。
無向グラフでは、連結成分は、すべての頂点が互いに到達できるグラフ内の領域として非常に直観的に理解できます。各連結成分はグラフの一部であり、グラフには複数の連結成分を含めることができます。
有向グラフでは、接続コンポーネントの定義は比較的複雑です。有向グラフの接続性には、頂点間の有向パスを考慮する必要があります。有向グラフの連結コンポーネントは、グラフ内の各頂点が、他のすべての頂点に到達するための有向パスを少なくとも 1 つ持っていることを意味します。
要点を整理すると、主な内容は次のようになります。
最小スパニングツリー
最小スパニング ツリーとは、無向重み付き接続グラフ内のすべての頂点を含み、ツリーのすべてのエッジの重みの合計が最小となるスパニング ツリーを見つけることを指します。最小スパニング ツリーには次の特徴があります。
複数の最小スパニング ツリーが存在する場合があります。最小スパニング ツリーのエッジの数は、頂点の数から 1 を引いたものに等しくなります。最小スパニング ツリーにはサイクルは存在しません。
最小スパニング ツリー問題を解くための一般的なアルゴリズムには、プリムのアルゴリズムとクラスカルのアルゴリズムがあります。 >。
Prim のアルゴリズム(Prim): 特定の頂点からスパニング ツリーの構築を開始します。すべての頂点が含まれるまで、最小コストの新しい頂点がスパニング ツリーに含まれるたびに。
ここではルート ノードとして都市 p を選択し、接続間のコストが最小のノードを見つけるために外側に拡張し、最終的に最小コストを取得します。
クラスカル アルゴリズム (クラスカル): 毎回最小の重みを持つエッジを選択して、このエッジの 2 つの端を接続します (すでに接続されているエッジは選択されません)。すべてのノードが接続されています。
2 つのアルゴリズムの比較:
最短経路の問題
グラフの最短経路問題とは、重み付き有向グラフまたは無向グラフの 2 つの頂点間の最短経路を見つける問題を指します。最短パスはエッジの重みの合計によって測定できます。最短経路問題は主に次の 2 つの状況に分けられます。
単一ソースの最短パス: グラフ内の特定の開始ノードから他のすべてのノードまでの最短パス。これは、固定開始点から他のノードまでの最短経路問題を解くために使用できます。
BFS アルゴリズム(重み付けされていないグラフ):
ダイクストラ アルゴリズム (加重グラフ、加重なしグラフ):
頂点間の最短パス: グラフ内の任意の 2 つのノード間の最短パスを計算します。これを使用して、ノードの任意のペア間の最短パスの問題を解決できます。
フロイド アルゴリズム(加重グラフ、加重なしグラフ):
要点を整理すると、主な内容は次のようになります。
クリティカルパスの問題
クリティカル パスとは、プロジェクト計画において遅延が許されない最長のパスを指し、プロジェクト全体の最短完了時間を決定します。クリティカル パスの問題は、プロジェクト管理やエンジニアリング計画でよく使用されます。
AOE ネットワークでは、次の概念を理解する必要があります。
上記の関連する例を引用した後、次に対応する値の計算を開始します。
すべてのイベントの最も早い発生時間を検索する: (最も遅いイベントが完了するまで待ちます)
すべてのイベントのうち最新の発生イベントを検索する: (シンク ポイントの最新の発生時刻は、最も早い発生時刻と一致します。シンク ポイントをトリガー ポイントとして使用して、トポロジカル ソートを実行します。つまり、対応するパスで発生したイベントへの Go を減らして、ポイントの最新の発生時刻を取得します):
すべてのアクティビティの最も早い発生時間を検索する: (イベントの最も早い発生時間は、前回の計算でのイベントの最も早い発生時間から計算されます):
すべてのアクティビティの最新の発生時刻を検索します: (前回の計算でのイベントの最新の発生時刻から、対応するアクティビティに必要な時間を減算することで取得されます)
すべてのアクティビティの余裕時間の検索: (アクティビティの最も遅い発生時刻からアクティビティの最も早い発生時刻を引いたもの)
注: 以下は、対応するクリティカル アクティビティとクリティカル パスの特性です。
要点を整理すると、主な内容は次のようになります。