Retranslate the complete Japanese online edition from Chinese (#2008)

* Retranslate the complete Japanese online edition from Chinese

* Fit Japanese homepage labels and localize heading permalinks

* Point Japanese contribution instructions to the Japanese preview

* Replace locale conditionals with multilingual UI tables
This commit is contained in:
Yudong Jin
2026-10-30 02:12:26 +08:00
committed by GitHub
parent 455e8c1281
commit 7257641e0e
1987 changed files with 23531 additions and 23261 deletions

View File

@@ -1,20 +1,20 @@
# 演習
# 練習問題
## 確認問題
## 知識の確認
### 同じグラフを 2 つの方法で表す
### 同じグラフを二つの方法で表す
無向グラフに 4 つの頂点 `A、B、C、D` があり、辺は
無向グラフに 4 個の頂点 `A、B、C、D` があり、辺は
`A-B、A-C、B-C、C-D` です。
<!-- numbered-subquestions -->
1. このグラフの隣接リストを書いてください。
2. 0 と 1 だけを使って隣接行列を埋めてください。
3. `A` と `D` が直接つながっているかを調べるとき、1 つの記録を見るだけで済むのは、どちらの表現方法ですか?
4. 頂点が多く、辺が少ないグラフでは、一般にどちらの表現方法が空間を節約できますか?
1. 隣接リストを書いてください。
2. 0 と 1 だけからなる隣接行列を埋めてください。
3. `A` と `D` が直接つながっているかを判定するとき、一つの格納項目を見るだけでよいのはどちらの表現ですか。
4. 頂点が多く、辺が少ないグラフでは、通常どちらが領域を節約できますか。
??? success "解答"
??? success "解答例"
1. 隣接リストは次のとおりです。
@@ -34,70 +34,70 @@
| C | 1 | 1 | 0 | 1 |
| D | 0 | 0 | 1 | 0 |
3. 隣接行列では `A` 行 `D` 列を直接見ればよいため、任意の 2 頂点が直接つながっているかを調べるのに適しています。
3. 隣接行列では、`A` 行 `D` 列を直接見られるため、任意の二頂点が直接つながっているかの判定に適しています。
4. 頂点が多く、辺が少ない場合、隣接リストは実際に存在する辺だけを記録します。そのため、頂点のすべての組に対して領域を用意する隣接行列よりも、一般に空間を節約できます。
4. 頂点が多く辺が少ない場合、隣接リストは実在する辺だけを記録するため、頂点のすべての組に領域を用意する隣接行列より、通常は領域を節約できます。
### 幅優先走査と深さ優先走査の訪問順序
### 幅優先と深さ優先の訪問順序
無向グラフの頂点は `A、B、C、D、E`、辺は
無向グラフの頂点は `A、B、C、D、E` で、辺は
`A-B、A-C、B-D、C-D、D-E` です。
A から開始し、未訪問の隣接頂点が複数ある場合はアルファベット順に選ぶものとします。
A から出発し、未訪問の隣接頂点が複数ある場合はアルファベット順に選ぶものとします。
<!-- numbered-subquestions -->
1. 幅優先走査(BFS)の訪問順序を書いてください。
2. 再帰による深さ優先走査(DFS)の訪問順序を書いてください。
3. どちらの走査でも、訪問済みの頂点を記録する必要があるのはなぜですか?
1. 幅優先走査(BFS)の訪問順を書いてください。
2. 再帰による深さ優先走査(DFS)の訪問順を書いてください。
3. どちらの走査でも、訪問済みの頂点を記録する必要があるのはなぜですか。
??? success "解答"
??? success "解答例"
1. BFS の訪問順序は `A, B, C, D, E` です。まず A から辺 1 本で到達できる B、C を訪問し、
次により遠い D、E を訪問します。
1. BFS の訪問順は `A, B, C, D, E` です。まず A から辺 1 本の距離にある B、C を訪問し、
その後、より遠い D、E を訪問します。
2. DFS の訪問順序は `A, B, D, C, E` です。現在の頂点から未訪問の隣接頂点へ順に進むため、
まず `A → B → D → C` と進みます。C に新しい隣接頂点がなければ D に戻り、次に E を訪問します。
2. DFS の訪問順は `A, B, D, C, E` です。現在の頂点の未訪問の隣接頂点へ順に進むため、
まず `A → B → D → C` をたどります。C には新しい隣接頂点がないので D に戻り、E を訪問します。
3. グラフには、たとえば `A-B-D-C-A` のような閉路があります。訪問済みの頂点を記録しないと、
閉路に沿って同じ頂点を何度も訪問し、走査が正常に終了しない可能性があります。
3. グラフには、例えば `A-B-D-C-A` のような環があります。訪問済みの頂点を記録しなければ、
環に沿って同じ頂点群を繰り返し訪問し、正常に終了できない可能性があります。
### 1 回の BFS でグラフ全体を訪問できるか
### 一回の BFS でグラフ全体を訪問できるか
無向グラフに頂点 `A、B、C、D、E、F` があり、辺は
`A-B、B-C、D-E` だけです。
<!-- numbered-subquestions -->
1. A から 1 回 BFS を行うと、どの頂点を訪問できますか?
2. 問い 1 の結果から、この 1 回の BFS でグラフのすべての頂点を訪問できたといえますか?その理由も説明してください。
3. すべての頂点をアルファベット順に調べ、未訪問の頂点に出会うたびに新しい BFS を開始します。
各 BFS の開始頂点は何ですか?このグラフはいくつの互いにつながっていない部分(連結成分)に分かれますか?
1. A から一回 BFS を行うと、どの頂点を訪問できますか。
2. 問い 1 の結果から、この一回の BFS でグラフの全頂点を訪問できたといえますか。理由も説明してください。
3. 全頂点をアルファベット順に調べ、未訪問の頂点に出会うたびに BFS を開始すると、
各 BFS の始点はどこになりますか。このグラフは、互いにつながらない部分(連結成分)にいくつ分かれますか。
??? success "解答"
??? success "解答例"
1. A からは `A、B、C` だけを訪問できます。
1. A から訪問できるのは `A、B、C` だけです。
2. すべての頂点を訪問できていません。`D、E` は別の互いにつながった部分を作り、F は単独の頂点です。
これらと A の間には経路がないため、A から到達できません。
2. 全頂点は訪問できていません。`D、E` は別の連結した部分を作り、F は孤立した頂点です。
いずれも A との間に経路がないため、A からは到達できません。
3. 3 回の BFS の開始頂点は順に `A、D、F` で、それぞれ
`{A, B, C}`、`{D, E}`、`{F}` を訪問します。したがって、このグラフには 3 つの連結成分があります。
3. 三回の BFS の始点は順に `A、D、F` で、それぞれ
`{A, B, C}`、`{D, E}`、`{F}` を訪問します。したがって、このグラフの連結成分は 3 個です。
## プログラミング演習
### 無向グラフに経路が存在するかを判定する
### 無向グラフに経路があるかを判定する
$n$ 個の頂点を持つ無向グラフが与えられ、頂点には $0$ から $n-1$ までの番号が付いています。配列 `edges` の各要素 `[u, v]` は、頂点 `u` と `v` の間に無向辺があることを表します。
$n$ 個の頂点を持つ無向グラフが与えられ、頂点には $0$ から $n-1$ の番号が付いています。配列 `edges` の各項目 `[u, v]` は、頂点 `u` と `v` の間に無向辺があることを表します。
さらに、始点 `source` と終点 `destination` が与えられます。まず `edges` から隣接リストを作り、次に BFS または DFS を使って、
`source` から `destination` への経路が存在するかを判定してください。存在する場合は `true`、存在しない場合は `false` を返します。
グラフには閉路がある場合も、連結していない場合もあります。
`source` から `destination` への経路があるかを判定してください。あれば `true`、なければ `false` を返します。
グラフには環がある場合も、連結でない場合もあります。
??? tip "解法のヒント"
??? tip "解くためのヒント"
1. 各無向辺は、両方の向きで隣接リストへ追加する必要があります
2. グラフには閉路がある可能性があるため、訪問済みのノードを必ず記録します
3. source から開始し、destination に出会ったら true を返します。走査を終えても出会わなければ false を返します
1. 各無向辺は、両方向に追加する必要があります
2. グラフには環がある場合があるため、訪問済みのノードを必ず記録します
3. source から出発し、destination に出会えば true を、走査が終わっても出会わなければ false を返します
[LeetCode](https://leetcode.com/problems/find-if-path-exists-in-graph/){ .rounded-button .exercise-button target="_blank" rel="noopener noreferrer" }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,6 +1,6 @@
# グラフ
<u>グラフ(graph)</u>は、<u>頂点(vertex)</u>と<u>辺(edge)</u>から構成される非線形データ構造です。グラフ $G$ は、頂点集合 $V$ と辺集合 $E$ からなる集合として抽象的に表せます。以下の例は、5 個の頂点と 7 本の辺を含むグラフを示しています。
<u>グラフ(graph)</u>は、<u>頂点(vertex)</u>と<u>辺(edge)</u>からなる非線形データ構造です。グラフ $G$ は、頂点の集合 $V$ と辺の集合 $E$ をまとめたものとして抽象的に表せます。次の例は、5 個の頂点と 7 本の辺を持つグラフです。
$$
\begin{aligned}
@@ -10,74 +10,74 @@ G & = \{ V, E \} \newline
\end{aligned}
$$
頂点をノード、辺を各ノードをつなぐ参照(ポインタ)とみなせば、グラフは連結リストを拡張したデータ構造の一種と捉えられます。次の図に示すように、**線形関係(連結リスト)や分治関係(木)と比べて、ネットワーク関係(グラフ)は自由度が高く**、そのぶん複雑です。
頂点をノード、辺をノード同士をつなぐ参照(ポインタ)と考えると、グラフは連結リストを拡張したデータ構造として捉えられます。下図のように、**線形の関係(連結リスト)や分割統治の関係(木)よりも、ネットワークの関係(グラフ)は自由度が高く**、その分複雑になります。
![連結リスト、木、グラフの関係](graph.assets/linkedlist_tree_graph.png)
## グラフの一般的な種類と用語
## グラフの主な種類と用語
辺が方向性を持つかどうかに応じて、<u>無向グラフ(undirected graph)</u>と<u>有向グラフ(directed graph)</u>に分けられます。次の図のとおりです。
辺に向きがあるかどうかによって、下図のように<u>無向グラフ(undirected graph)</u>と<u>有向グラフ(directed graph)</u>に分けられます。
- 無向グラフでは、辺は 2 つの頂点間の「双方向」の接続関係を表します。例えば WeChat や QQ における「友だち関係」です。
- 有向グラフでは、辺は方向性を持ち、すなわち $A \rightarrow B$ と $A \leftarrow B$ の 2 方向の辺は互いに独立です。例えば Weibo や Douyin における「フォロー」と「フォロワー」の関係です。
- 無向グラフの辺は、2 頂点間の「双方向」のつながりを表します。WeChat や QQ の「友達関係」がその例です。
- 有向グラフの辺には向きがあります。つまり、$A \rightarrow B$ と $A \leftarrow B$ の二つの向きの辺は互いに独立しています。Weibo や Douyin の「フォローする」「フォローされる」という関係がその例です。
![有向グラフと無向グラフ](graph.assets/directed_graph.png)
すべての頂点が連結しているかどうかに応じて、<u>連結グラフ(connected graph)</u>と<u>非連結グラフ(disconnected graph)</u>に分けられます。次の図のとおりです。
すべての頂点がつながっているかどうかによって、下図のように<u>連結グラフ(connected graph)</u>と<u>非連結グラフ(disconnected graph)</u>に分けられます。
- 連結グラフでは、ある頂点から出発すると、ほかの任意の頂点に到達できます。
- 非連結グラフでは、ある頂点から出発すると、少なくとも 1 つの頂点には到達できません。
- 連結グラフでは、ある頂点から出発して、ほかのどの頂点にも到達できます。
- 非連結グラフでは、ある頂点から出発したとき、到達できない頂点が少なくとも一つあります。
![連結グラフと非連結グラフ](graph.assets/connected_graph.png)
辺に「重み」の変数を追加すると、次の図に示すような<u>重み付きグラフ(weighted graph)</u>が得られます。例えば『Honor of Kings』のようなモバイルゲームでは、システムが共にプレイした時間に基づいてプレイヤー間の「親密度」を計算します。この親密度ネットワークは重み付きグラフで表せます。
辺に「重み」という変数を加えると、下図の<u>重み付きグラフ(weighted graph)</u>になります。たとえば『王者荣耀』などのモバイルゲームでは、一緒に遊んだ時間からプレイヤー間の「親密度」を計算します。この親密度のネットワークは、重み付きグラフで表せます。
![重み付きグラフと重みなしグラフ](graph.assets/weighted_graph.png)
グラフというデータ構造には、次のような基本用語があります。
グラフでは、次の用語をよく使います。
- <u>隣接(adjacency)</u>:2 つの頂点の間に辺が存在するとき、この 2 つの頂点は「隣接している」といいます。上図では、頂点 1 に隣接する頂点は 2、3、5 です。
- <u>経路(path)</u>:頂点 A から頂点 B までに通過する辺で構成された列を、A から B への「経路」と呼びます。上図では、辺の列 1-5-2-4 は頂点 1 から頂点 4 への 1 本の経路です。
- <u>次数(degree)</u>:ある頂点が持つ辺の本数です。有向グラフでは、<u>入次数(in-degree)</u>はその頂点に向かう辺の本数を表し、<u>出次数(out-degree)</u>はその頂点から出る辺の本数を表します。
- <u>隣接(adjacency)</u>:2 頂点の間に辺があるとき、これらの頂点は「隣接している」といいます。上図では、頂点 1 に隣接するのは頂点 2、3、5 です。
- <u>経路(path)</u>:頂点 A から頂点 B までに通る辺の列を、A から B への「経路」といいます。上図の辺の列 1-5-2-4 は、頂点 1 から頂点 4 への経路の一つです。
- <u>次数(degree)</u>:ある頂点が持つ辺の本数です。有向グラフでは、<u>入次数(in-degree)</u>はその頂点に入る辺の本数、<u>出次数(out-degree)</u>はその頂点から出る辺の本数を表します。
## グラフの表現
グラフの一般的な表現方法には「隣接行列」と「隣接リスト」があります。以下では無向グラフを例に説明します。
グラフの代表的な表現方法には、「隣接行列」と「隣接リスト」があります。以下では無向グラフを例に説明します。
### 隣接行列
グラフの頂点数を $n$ とすると、<u>隣接行列(adjacency matrix)</u>は $n \times n$ の行列を用いてグラフを表します。各行(列)は 1 つの頂点を表し、行列要素は辺を表します。$1$ または $0$ を用いて、2 つの頂点の間に辺があるかどうかを示します。
グラフの頂点数を $n$ とします。<u>隣接行列(adjacency matrix)</u>は、$n \times n$ の行列でグラフを表します。各行(列)が一つの頂点に対応し、行列の要素が辺を表します。2 頂点間に辺があるかどうかを $1$ または $0$ で示します。
次の図のように、隣接行列を $M$、頂点リストを $V$ とすると、行列要素 $M[i, j] = 1$ は頂点 $V[i]$ から頂点 $V[j]$ への辺が存在することを表し、逆に $M[i, j] = 0$ は 2 つの頂点の間に辺がないことを表します。
下図のように、隣接行列を $M$、頂点のリストを $V$ とすると、行列の要素 $M[i, j] = 1$ は頂点 $V[i]$ から頂点 $V[j]$ への辺があることを表し、$M[i, j] = 0$ は辺がないことを表します。
![グラフの隣接行列による表現](graph.assets/adjacency_matrix.png)
![隣接行列によるグラフの表現](graph.assets/adjacency_matrix.png)
隣接行列には次の特徴があります。
隣接行列には、次の特徴があります。
- 単純グラフでは、頂点は自分自身とは接続できないため、このとき隣接行列の主対角線上の要素には意味がありません。
- 無向グラフでは、2 方向の辺は等価であるため、このとき隣接行列は主対角線に関して対称です。
- 隣接行列の要素を $1$ と $0$ から重みに置き換えると、重み付きグラフを表せます。
- 単純グラフでは頂点を自分自身につなげられないため、隣接行列の主対角線上の要素は意味を持ちません。
- 無向グラフでは両方向の辺が同じものを表すため、隣接行列は主対角線について対称です。
- 隣接行列の要素 $1$ と $0$ を重みに置き換えると、重み付きグラフを表せます。
隣接行列でグラフを表す場合、行列要素に直接アクセスして辺を取得できるため、追加・削除・検索・更新の操作効率は高く、時間計算量はいずれも $O(1)$ です。しかし、行列の空間計算量は $O(n^2)$ であり、メモリ使用量は多くなります。
隣接行列でグラフを表すと、行列の要素に直接アクセスして辺を取得できるため、辺の追加・削除・検索・更新は効率が高く、時間計算量はいずれも $O(1)$ です。ただし、行列の空間計算量は $O(n^2)$ で、メモリを多く使います。
### 隣接リスト
<u>隣接リスト(adjacency list)</u>は、$n$ 本の連結リストを使ってグラフを表します。連結リストのノードは頂点を表します。第 $i$ 本の連結リストは頂点 $i$ に対応し、その頂点に隣接するすべての頂点(その頂点と接続された頂点)を格納します。次の図は、隣接リストで保存したグラフの例です。
<u>隣接リスト(adjacency list)</u>は、$n$ 個の連結リストでグラフを表し、連結リストのノードが頂点を表します。$i$ 番目の連結リストは頂点 $i$ に対応し、その頂点に隣接するすべての頂点(その頂点につながる頂点)を格納します。下図に、隣接リストで格納したグラフの例を示します。
![グラフの隣接リストによる表現](graph.assets/adjacency_list.png)
![隣接リストによるグラフの表現](graph.assets/adjacency_list.png)
隣接リストは実際に存在する辺だけを格納し、辺の総数は通常 $n^2$ よりはるかに小さいため、より省スペースです。しかし、隣接リストでは辺を見つけるために連結リストを走査する必要があるため、時間効率は隣接行列に及びません。
隣接リストは実際に存在する辺だけを保存します。通常、辺の総数は $n^2$ よりはるかに少ないため、空間を節約できます。ただし、辺を探すには連結リストを走査する必要があり、時間効率は隣接行列より低くなります。
上図を見ると、**隣接リストの構造はハッシュテーブルにおける「連鎖アドレス法」と非常によく似ているため、同様の方法で効率を最適化できます**。例えば、連結リストが長い場合は AVL 木や赤黒木に変換して時間効率を $O(n)$ から $O(\log n)$ に改善できます。さらに、連結リストをハッシュテーブルに変換すれば、時間計算量を $O(1)$ まで下げられます。
上図を見ると、**隣接リストの構造はハッシュテーブルの「チェイン法」によく似ているため、同じような方法で効率を改善できます**。たとえば、連結リストが長い場合は AVL 木や赤黒木に変換し、時間計算量を $O(n)$ から $O(\log n)$ に改善できます。ハッシュテーブルに変換すれば、時間計算量を $O(1)$ にできます。
## グラフの一般的な応用
## グラフの主な用途
次の表のように、多くの現実のシステムはグラフでモデル化でき、対応する問題もグラフ計算の問題に帰着できます。
下表のように、多くの現実のシステムをグラフでモデル化でき、それらに関する問題をグラフ上の計算問題に帰着できます。
<p align="center"> 表 <id> &nbsp; 現実世界でよく見られるグラフ </p>
<p align="center"> 表 <id> &nbsp; 日常生活に見られるグラフ </p>
| | 頂点 | 辺 | グラフ計算問題 |
| | 頂点 | 辺 | グラフ上の計算問題 |
| -------- | ---- | -------------------- | ------------ |
| ソーシャルネットワーク | ユーザー | 友だち関係 | 潜在的な友だちの推薦 |
| 地下鉄路線 | 駅 | 駅間の接続性 | 最短経路の推薦 |
| 太陽系 | 天体 | 天体間の万有引力作用 | 惑星軌道の計算 |
| ソーシャルネットワーク | ユーザー | 友達関係 | 友達候補の推薦 |
| 地下鉄の路線 | 駅 | 駅同士の接続 | 最短経路の推薦 |
| 太陽系 | 天体 | 天体間の万有引力 | 惑星の軌道計算 |

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,68 +1,68 @@
# グラフの基本操作
グラフの基本操作は、「辺」に対する操作と「頂点」に対する操作に分けられます。「隣接行列」と「隣接リスト」の 2 つの表現方法では、実装方法が異なります。
グラフの基本操作には、「辺」に対する操作と「頂点」に対する操作があります。「隣接行列」と「隣接リスト」では、実装方法が異なります。
## 隣接行列に基づく実装
## 隣接行列による実装
頂点数が $n$ の無向グラフを与えると、各種操作の実装方法は次図のとおりです。
頂点数 $n$ の無向グラフに対する各種操作は、下図のように実装できます。
- **辺の追加または削除**:隣接行列で指定した辺を直接変更すればよく、$O(1)$ 時間です。無向グラフであるため、2 方向の辺を同時に更新する必要があります。
- **頂点の追加**:隣接行列の末尾に 1 行 1 列を追加し、すべてを $0$ で埋めればよく、$O(n)$ 時間です。
- **頂点の削除**:隣接行列から 1 行 1 列を削除します。先頭行と先頭列を削除する場合が最悪で、$(n-1)^2$ 個の要素を「左上へ移動」させる必要があるため、$O(n^2)$ 時間です。
- **初期化**:$n$ 個の頂点を受け取り、長さ $n$ の頂点リスト `vertices` を初期化するのに $O(n)$ 時間、サイズ $n \times n$ の隣接行列 `adjMat` を初期化するのに $O(n^2)$ 時間かかります。
- **辺の追加・削除**:隣接行列の指定した辺を直接変更するだけなので、$O(1)$ 時間です。無向グラフのため、両方向の辺を同時に更新する必要があります。
- **頂点の追加**:隣接行列の末尾に行と列を一つずつ追加し、すべて $0$ で埋めます。$O(n)$ 時間かかります。
- **頂点の削除**:隣接行列から行と列を一つずつ削除します。最悪の場合は先頭の行と列を削除するときで、$(n-1)^2$ 個の要素を「左上に移動」するため、$O(n^2)$ 時間かかります。
- **初期化**:$n$ 個の頂点を受け取り、長さ $n$ の頂点リスト `vertices` を $O(n)$ 時間で初期化します。$n \times n$ の隣接行列 `adjMat` の初期化には $O(n^2)$ 時間かかります。
=== "<1>"
![隣接行列の初期化、辺の追加と削除、頂点の追加と削除](graph_operations.assets/adjacency_matrix_step1_initialization.png)
![隣接行列の初期化、辺の追加・削除、頂点の追加・削除](graph_operations.assets/adjacency_matrix_step1_initialization.png)
=== "<2>"
![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_step2_add_edge.png)
![隣接行列で辺を追加](graph_operations.assets/adjacency_matrix_step2_add_edge.png)
=== "<3>"
![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_step3_remove_edge.png)
![隣接行列で辺を削除](graph_operations.assets/adjacency_matrix_step3_remove_edge.png)
=== "<4>"
![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_step4_add_vertex.png)
![隣接行列で頂点を追加](graph_operations.assets/adjacency_matrix_step4_add_vertex.png)
=== "<5>"
![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png)
![隣接行列で頂点を削除](graph_operations.assets/adjacency_matrix_step5_remove_vertex.png)
以下は、隣接行列でグラフを表した実装コードです:
隣接行列によるグラフの実装を次に示します。
```src
[file]{graph_adjacency_matrix}-[class]{graph_adj_mat}-[func]{}
```
## 隣接リストに基づく実装
## 隣接リストによる実装
無向グラフの頂点総数を $n$、辺総数を $m$ とすると、各種操作は次図の方法で実装できます。
無向グラフの頂点数を $n$、辺の総数を $m$ とすると、下図の方法で各種操作を実装できます。
- **辺の追加**:頂点に対応する連結リストの末尾に辺を追加すればよく、$O(1)$ 時間です。無向グラフなので、2 方向の辺を同時に追加する必要があります。
- **辺の削除**:頂点に対応する連結リストから指定した辺を探して削除するため、$O(m)$ 時間です。無向グラフでは、2 方向の辺を同時に削除する必要があります。
- **頂点の追加**:隣接リストに 1 つの連結リストを追加し、新しい頂点をその連結リストの先頭ノードとするため、$O(1)$ 時間です。
- **頂点の削除**:隣接リスト全体を走査し、指定した頂点を含むすべての辺を削除する必要があるため、$O(n + m)$ 時間です。
- **初期化**:隣接リストに $n$ 個の頂点と $2m$ 本の辺を作成するため、$O(n + m)$ 時間です。
- **辺の追加**:頂点に対応する連結リストの末尾に辺を追加するだけなので、$O(1)$ 時間です。無向グラフのため、両方向の辺を同時に追加する必要があります。
- **辺の削除**:頂点に対応する連結リストで指定した辺を探して削除するため、$O(m)$ 時間かかります。無向グラフでは、両方向の辺を同時に削除する必要があります。
- **頂点の追加**:隣接リストに連結リストを一つ追加し、新しい頂点をその先頭ノードにします。$O(1)$ 時間です。
- **頂点の削除**:隣接リスト全体を走査し、指定した頂点を含むすべての辺を削除するため、$O(n + m)$ 時間かかります。
- **初期化**:隣接リストに $n$ 個の頂点と $2m$ 本の辺を作るため、$O(n + m)$ 時間かかります。
=== "<1>"
![隣接リストの初期化、辺の追加と削除、頂点の追加と削除](graph_operations.assets/adjacency_list_step1_initialization.png)
![隣接リストの初期化、辺の追加・削除、頂点の追加・削除](graph_operations.assets/adjacency_list_step1_initialization.png)
=== "<2>"
![adjacency_list_add_edge](graph_operations.assets/adjacency_list_step2_add_edge.png)
![隣接リストで辺を追加](graph_operations.assets/adjacency_list_step2_add_edge.png)
=== "<3>"
![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_step3_remove_edge.png)
![隣接リストで辺を削除](graph_operations.assets/adjacency_list_step3_remove_edge.png)
=== "<4>"
![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_step4_add_vertex.png)
![隣接リストで頂点を追加](graph_operations.assets/adjacency_list_step4_add_vertex.png)
=== "<5>"
![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_step5_remove_vertex.png)
![隣接リストで頂点を削除](graph_operations.assets/adjacency_list_step5_remove_vertex.png)
以下は隣接リストのコード実装です。上図と比べると、実際のコードには次の違いがあります。
隣接リストの実装を次に示します。実際のコードは、上図と次の点が異なります。
- 頂点の追加と削除を容易にし、コードを簡潔にするため、連結リストの代わりにリスト(動的配列)を使用しています。
- ハッシュテーブルを用いて隣接リストを格納しており、`key` は頂点インスタンス、`value` はその頂点に隣接する頂点のリスト(連結リスト)です。
- 頂点の追加と削除をしやすくし、コードを簡潔にするため、連結リストの代わりにリスト(動的配列)を使います。
- 隣接リストをハッシュテーブルに格納します。`key` は頂点のインスタンス、`value` はその頂点に隣接する頂点のリスト(連結リスト)です。
また、隣接リストでは頂点を表すために `Vertex` クラスを使用しています。その理由は、もし隣接行列と同様にリストのインデックスで異なる頂点を区別すると、インデックス $i$ の頂点を削除する場合、隣接リスト全体を走査して、$i$ より大きいすべてのインデックスを $1$ 減らす必要があり、効率が非常に低いためです。これに対して、各頂点が一意な `Vertex` インスタンスであれば、ある頂点を削除しても他の頂点を変更する必要はありません。
また、隣接リストでは `Vertex` クラスで頂点を表します。隣接行列と同じようにリストのインデックスで頂点を区別すると、インデックス $i$ の頂点を削除する際に、隣接リスト全体を走査し、$i$ より大きいインデックスをすべて $1$ ずつ減らす必要があり、非効率だからです。各頂点を固有の `Vertex` インスタンスにすれば、ある頂点を削除しても、ほかの頂点を変更する必要がありません。
```src
[file]{graph_adjacency_list}-[class]{graph_adj_list}-[func]{}
@@ -70,17 +70,17 @@
## 効率の比較
グラフに $n$ 個の頂点と $m$ 本の辺があるとすると、次の表は隣接行列と隣接リストの時間効率および空間効率を比較したものです。なお、隣接リスト(連結リスト)は本記事の実装に対応し、隣接リスト(ハッシュテーブル)はすべての連結リストをハッシュテーブルに置き換えた実装を指します。
グラフに $n$ 個の頂点と $m$ 本の辺があるとします。下表で、隣接行列と隣接リストの時間効率と空間効率を比較します。隣接リスト(連結リスト)は本節の実装に対応し、隣接リスト(ハッシュテーブル)は、すべての連結リストをハッシュテーブルに置き換えた実装を指します。
<p align="center"> 表 <id> &nbsp; 隣接行列と隣接リストの比較 </p>
| | 隣接行列 | 隣接リスト(連結リスト) | 隣接リスト(ハッシュテーブル) |
| ------------ | -------- | -------------- | ---------------- |
| 隣接判定 | $O(1)$ | $O(n)$ | $O(1)$ |
| 辺の追加 | $O(1)$ | $O(1)$ | $O(1)$ |
| 辺の削除 | $O(1)$ | $O(n)$ | $O(1)$ |
| 頂点の追加 | $O(n)$ | $O(1)$ | $O(1)$ |
| 頂点の削除 | $O(n^2)$ | $O(n + m)$ | $O(n)$ |
| 隣接しているかの判定 | $O(1)$ | $O(n)$ | $O(1)$ |
| 辺の追加 | $O(1)$ | $O(1)$ | $O(1)$ |
| 辺の削除 | $O(1)$ | $O(n)$ | $O(1)$ |
| 頂点の追加 | $O(n)$ | $O(1)$ | $O(1)$ |
| 頂点の削除 | $O(n^2)$ | $O(n + m)$ | $O(n)$ |
| メモリ使用量 | $O(n^2)$ | $O(n + m)$ | $O(n + m)$ |
上表を見ると、隣接リスト(ハッシュテーブル)の時間効率と空間効率が最も優れているように見えます。しかし実際には、隣接行列のほうが辺の操作効率は高く、必要なのは 1 回の配列アクセスまたは代入だけです。総合的に見ると、隣接行列は「空間を時間と引き換えにする」原則を体現し、隣接リストは「時間を空間と引き換えにする」原則を体現しています。
上表だけを見ると、隣接リスト(ハッシュテーブル)が時間効率・空間効率ともに最も優れているように思えます。しかし実際には、隣接行列なら配列へのアクセスまたは代入が 1 回で済むため、辺の操作はより高速です。全体として、隣接行列は「空間を使って時間を節約する」考え方を、隣接リストは「時間を使って空間を節約する」考え方を表しています。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1,140 +1,140 @@
# グラフの走査
木は「一対多」の関係を表すのに対し、グラフはより高い自由度を持ち、任意の「多対多」の関係を表現できます。したがって、木はグラフの一種の特殊な場合とみなせます。明らかに、**木の走査操作もグラフの走査操作の一種の特殊な場合です**。
木は「一対多」の関係を表しますが、グラフは自由度が高く、任意の「多対多」の関係を表せます。そのため、木はグラフの特殊な場合と考えられます。当然、**木の走査も、グラフの走査の特殊な場合です**。
グラフと木はいずれも、走査操作を実現するために探索アルゴリズムを用いる必要があります。グラフの走査方法も、<u>幅優先走査</u>と<u>深さ優先走査</u>の 2 種類に分けられます。
グラフも木も、走査には探索アルゴリズムを使います。グラフの走査方法も、<u>幅優先走査</u>と<u>深さ優先走査</u>の二つに分けられます。
## 幅優先走査
**幅優先走査は、近いところから遠いところへ向かう走査方法であり、ある頂点から出発して、常に最も近い頂点を優先して訪問し、層ごとに外側へ広がっていきます**。以下の図に示すように、左上の頂点から出発し、まずその頂点のすべての隣接頂点を走査し、次に次の頂点のすべての隣接頂点を走査し、これを繰り返して、すべての頂点を訪問するまで続けます。
**幅優先走査は、あるノードから始め、距離の近い頂点から優先して訪問し、層ごとに外側へ広がっていく走査方法です**。下図のように、左上の頂点から始めて、その頂点に隣接するすべての頂点を訪問します。続いて次の頂点に隣接するすべての頂点を訪問し、これをすべての頂点に到達するまで繰り返します。
![グラフの幅優先走査](graph_traversal.assets/graph_bfs.png)
### アルゴリズムの実装
BFS は通常キューを用いて実装され、コードは以下のとおりです。キューは「先入れ先出し」という性質を持ち、これは BFS の「近いところから遠いところへ」という考え方と本質的に一致しています。
BFS は通常、次のコードのようにキューで実装します。キューの「先入れ先出し」という性質は、BFS の「近いところから遠いところへ」という考え方に合っています。
1. 走査の開始頂点 `startVet` をキューに追加し、ループを開始します。
2. ループの各反復で、キュー先頭の頂点を取り出して訪問を記録し、その後その頂点のすべての隣接頂点をキューの末尾に追加します。
3. 手順 `2.` を繰り返し、すべての頂点が訪問されると終了します。
1. 走査を始める頂点 `startVet` をキューに入れ、ループを開始します。
2. 各反復で、キューの先頭の頂点を取り出して訪問を記録し、その頂点に隣接するすべての頂点をキューの末尾に入れます。
3. 手順 `2.` を繰り返し、すべての頂点を訪問したら終了します。
頂点の重複走査を防ぐために、どの頂点が訪問済みかを記録するハッシュ集合 `visited` を用います。
同じ頂点を繰り返し訪問しないよう、ハッシュ集合 `visited` に訪問済みのノードを記録します。
!!! tip
ハッシュ集合は、`value` を持たず `key` だけを格納するハッシュテーブルとみなせます。これは $O(1)$ の時間計算量で `key` の追加・削除・検索・更新を行えます。`key` の一意性にもとづき、ハッシュ集合は通常、データの重複排除などの場面で用いられます。
ハッシュ集合は、`key` だけを保存し、`value` を保存しないハッシュテーブルと考えられます。`key` の追加・削除・検索・更新を時間計算量 $O(1)$ で行えます。`key` が重複しない性質を活かし、データの重複除去などによく使われます。
```src
[file]{graph_bfs}-[class]{}-[func]{graph_bfs}
```
コードはやや抽象的なので、以下の図と照らし合わせて理解を深めることを勧めます。
コードだけでは抽象的なので、下図と照らし合わせると理解が深まります。
=== "<1>"
![グラフの幅優先走査の手順](graph_traversal.assets/graph_bfs_step1.png)
=== "<2>"
![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png)
![グラフの幅優先走査:手順 2](graph_traversal.assets/graph_bfs_step2.png)
=== "<3>"
![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png)
![グラフの幅優先走査:手順 3](graph_traversal.assets/graph_bfs_step3.png)
=== "<4>"
![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png)
![グラフの幅優先走査:手順 4](graph_traversal.assets/graph_bfs_step4.png)
=== "<5>"
![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png)
![グラフの幅優先走査:手順 5](graph_traversal.assets/graph_bfs_step5.png)
=== "<6>"
![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png)
![グラフの幅優先走査:手順 6](graph_traversal.assets/graph_bfs_step6.png)
=== "<7>"
![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png)
![グラフの幅優先走査:手順 7](graph_traversal.assets/graph_bfs_step7.png)
=== "<8>"
![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png)
![グラフの幅優先走査:手順 8](graph_traversal.assets/graph_bfs_step8.png)
=== "<9>"
![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png)
![グラフの幅優先走査:手順 9](graph_traversal.assets/graph_bfs_step9.png)
=== "<10>"
![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png)
![グラフの幅優先走査:手順 10](graph_traversal.assets/graph_bfs_step10.png)
=== "<11>"
![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png)
![グラフの幅優先走査:手順 11](graph_traversal.assets/graph_bfs_step11.png)
!!! question "幅優先走査の順序列は一意ですか?"
!!! question "幅優先走査の順序は一意ですか?"
一意ではありません。幅優先走査は「近いところから遠いところへ」の順で走査することだけを要求し、**同じ距離にある複数の頂点の走査順は任意に入れ替えて構いません**。上図を例にすると、頂点 $1$ と $3$ の訪問順は交換でき、頂点 $2$、$4$、$6$ の訪問順も任意に入れ替えられます。
一意ではありません。幅優先走査で必要なのは「近いところから遠いところへ」という順序だけで、**同じ距離にある複数の頂点は、どの順に訪問してもかまいません**。上図では、頂点 $1$ と $3$ の訪問順を入れ替えられますし、頂点 $2$、$4$、$6$ も任意の順に訪問できます。
### 計算量の分析
### 計算量の解析
**時間計算量**:すべての頂点は 1 回ずつキューに入り、1 回ずつキューから出るため、$O(|V|)$ 時間です。隣接頂点を走査する過程では、無向グラフであるため、すべての辺が $2$ 回訪問され、$O(2|E|)$ 時間です。したがって全体では $O(|V| + |E|)$ 時間です。
**時間計算量**:すべての頂点を 1 回ずつキューに入れて取り出すため、$O(|V|)$ 時間かかります。隣接する頂点を走査する際は、無向グラフなので、すべての辺を $2$ 回ずつ調べ、$O(2|E|)$ 時間かかります。全体では $O(|V| + |E|)$ 時間です。
**空間計算量**:リスト `res`、ハッシュ集合 `visited`、キュー `que` に含まれる頂点数は最大で $|V|$ であるため、$O(|V|)$ 空間です。
**空間計算量**:リスト `res`、ハッシュ集合 `visited`、キュー `que` が保持する頂点数は最大 $|V|$ なので、$O(|V|)$ の空間を使います。
## 深さ優先走査
**深さ優先走査は、まず行けるところまで進み、進めなくなったら戻る走査方法です**。以下の図に示すように、左上の頂点から出発し、現在の頂点のある隣接頂点を訪問して、行き止まりに達するまで進んだら戻り、再び別の方向へ進んで行き止まりまで進んで戻る、ということを繰り返し、すべての頂点の走査が完了するまで続けます。
**深さ優先走査は、まず進めるところまで進み、行き止まりになったら引き返す走査方法です**。下図のように、左上の頂点から出発し、現在の頂点に隣接する頂点の一つを訪問して、行き止まりまで進んだら戻ります。別の方向でも同じように進んで戻る操作を繰り返し、すべての頂点を走査します。
![グラフの深さ優先走査](graph_traversal.assets/graph_dfs.png)
### アルゴリズムの実装
この「行き止まりまで進んでから戻る」アルゴリズムのパターンは、通常再帰にもとづいて実装されます。幅優先走査と同様に、深さ優先走査でも、頂点の重複訪問を避けるために、訪問済みの頂点を記録するハッシュ集合 `visited` を用います。
この「行き止まりまで進んでから戻る」というアルゴリズムの形は、通常、再帰で実装します。幅優先走査と同様に、深さ優先走査でもハッシュ集合 `visited` に訪問済みの頂点を記録し、重複して訪問するのを防ぎます。
```src
[file]{graph_dfs}-[class]{}-[func]{graph_dfs}
```
深さ優先走査のアルゴリズムの流れは以下の図のとおりです。
深さ優先走査のアルゴリズムの流れを下図に示します。
- **直線の破線は下向きの再帰呼び出し**を表し、新しい頂点を訪問するために新たな再帰メソッドが開始されたことを意味します。
- **曲線の破線は上向きのバックトラック**を表し、この再帰メソッドがすでに戻って、呼び出し元の位置までたどり着いたことを意味します。
- **直線の破線は、下へ進む再帰呼び出しを表し**、新しい再帰メソッドを呼び出して、新しい頂点を訪問することを示します。
- **曲線の破線は、上へ戻る動きを表し**、再帰メソッドから戻り、そのメソッドを呼び出した位置に復帰することを示します。
理解を深めるために、以下の図とコードを結びつけて、DFS 全体の過程を頭の中でシミュレーションする(あるいは紙に書き出す)ことを勧めます。各再帰メソッドがいつ開始し、いつ戻るかも含めて追ってみてください。
理解を深めるため、下図とコードを照らし合わせ、各再帰メソッドがいつ呼ばれ、いつ戻るかを含めた DFS 全体の流れを、頭の中でたどるか、紙に描いてみてください。
=== "<1>"
![グラフの深さ優先走査の手順](graph_traversal.assets/graph_dfs_step1.png)
=== "<2>"
![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png)
![グラフの深さ優先走査:手順 2](graph_traversal.assets/graph_dfs_step2.png)
=== "<3>"
![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png)
![グラフの深さ優先走査:手順 3](graph_traversal.assets/graph_dfs_step3.png)
=== "<4>"
![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png)
![グラフの深さ優先走査:手順 4](graph_traversal.assets/graph_dfs_step4.png)
=== "<5>"
![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png)
![グラフの深さ優先走査:手順 5](graph_traversal.assets/graph_dfs_step5.png)
=== "<6>"
![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png)
![グラフの深さ優先走査:手順 6](graph_traversal.assets/graph_dfs_step6.png)
=== "<7>"
![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png)
![グラフの深さ優先走査:手順 7](graph_traversal.assets/graph_dfs_step7.png)
=== "<8>"
![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png)
![グラフの深さ優先走査:手順 8](graph_traversal.assets/graph_dfs_step8.png)
=== "<9>"
![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png)
![グラフの深さ優先走査:手順 9](graph_traversal.assets/graph_dfs_step9.png)
=== "<10>"
![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png)
![グラフの深さ優先走査:手順 10](graph_traversal.assets/graph_dfs_step10.png)
=== "<11>"
![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png)
![グラフの深さ優先走査:手順 11](graph_traversal.assets/graph_dfs_step11.png)
!!! question "深さ優先走査の順序列は一意ですか?"
!!! question "深さ優先走査の順序は一意ですか?"
幅優先走査と同様に、深さ優先走査の順序列も一意ではありません。ある頂点が与えられたとき、どの方向を先に探索してもよく、つまり隣接頂点の順序は任意に入れ替えられ、それでも深さ優先走査になります。
幅優先走査と同様に、深さ優先走査の順序も一意ではありません。ある頂点からどの方向を先に探索してもよく、隣接する頂点の順序を任意に入れ替えても、深さ優先走査であることは変わりません。
木の走査を例にすると、「根 $\rightarrow$ 左 $\rightarrow$ 右」「左 $\rightarrow$ 根 $\rightarrow$ 右」「左 $\rightarrow$ 右 $\rightarrow$ 根」は、それぞれ先行順、中間順、後行順走査に対応します。これらは 3 種類の走査優先順位を示していますが、いずれも深さ優先走査に属します。
木の走査を例にすると、「根 $\rightarrow$ 左 $\rightarrow$ 右」「左 $\rightarrow$ 根 $\rightarrow$ 右」「左 $\rightarrow$ 右 $\rightarrow$ 根」は、それぞれ行きがけ順、通りがけ順、帰りがけ順の走査です。訪問の優先順位は異なりますが、いずれも深さ優先走査です。
### 計算量の分析
### 計算量の解析
**時間計算量**:すべての頂点は $1$ 回ずつ訪問されるため、$O(|V|)$ 時間です。すべての辺は $2$ 回ずつ訪問されるため、$O(2|E|)$ 時間です。したがって全体では $O(|V| + |E|)$ 時間です。
**時間計算量**:すべての頂点を $1$ 回ずつ訪問するのに $O(|V|)$ 時間、すべての辺を $2$ 回ずつ調べるのに $O(2|E|)$ 時間かかります。全体では $O(|V| + |E|)$ 時間です。
**空間計算量**:リスト `res` とハッシュ集合 `visited` に含まれる頂点数は最大で $|V|$ であり、再帰の深さも最大で $|V|$ であるため、$O(|V|)$ 空間です。
**空間計算量**:リスト `res` とハッシュ集合 `visited` の頂点数は最大 $|V|$、再帰の深さも最大 $|V|$ なので、$O(|V|)$ の空間を使います。

View File

@@ -4,6 +4,6 @@
!!! abstract
人生の旅路において、私たちはそれぞれ一つひとつのノードのようなものであり、無数の見えない辺によって結ばれています。
人生の旅路で、私たちは一つひとつのノードのように、無数の見えない辺で結ばれています。
出会いと別れのたびに、この巨大なネットワークグラフの中に固有の足跡が刻まれます。
一つひとつの出会いと別れが、この巨大なネットワークにそれぞれの足跡を残します。

View File

@@ -1,31 +1,31 @@
# まとめ
### 重要なポイントの振り返り
### 要点の振り返り
- グラフは頂点と辺から構成され、一組の頂点と一組の辺からなる集合として表せます。
- 線形関係(連結リスト)や分治関係(木)と比べて、ネットワーク関係(グラフ)は自由度が高く、そのぶん複雑です。
- 有向グラフの辺は方向性を持ち、連結グラフでは任意の頂点に到達でき、重み付きグラフの各辺は重み変数を含みます。
- 隣接行列は行列を用いてグラフを表し、各行(列)が 1 つの頂点を表し、行列要素が辺を表します。$1$ または $0$ を用いて、2 つの頂点の間に辺があるかないかを示します。隣接行列は追加・削除・検索・更新の操作効率が高い一方で、より多くの空間を消費します。
- 隣接リストは複数の連結リストを使ってグラフを表し、第 $i$ 個の連結リストが頂点 $i$ に対応し、その頂点に隣接するすべての頂点を格納します。隣接リストは隣接行列よりも省スペースですが、辺を探すために連結リストを走査する必要があるため、時間効率は低くなります。
- 隣接リスト内の連結リストが長くなりすぎた場合は、赤黒木やハッシュテーブルに変換することで、検索効率を高められます。
- アルゴリズムの考え方という観点では、隣接行列は「空間を時間と引き換えにする」ことを体現し、隣接リストは「時間を空間と引き換えにする」ことを体現します。
- グラフは、ソーシャルネットワークや地下鉄路線など、さまざまな現実のシステムをモデル化するために使えます。
- 木はグラフの特殊な一例であり、木の走査もグラフ走査の特殊な一例です。
- グラフの幅優先探索は、近いところから遠いところへ、層ごとに広がっていく探索方法であり、通常はキューを使って実装します。
- グラフの深さ優先探索は、まず行けるところまで進み、進めなくなったらバックトラックする探索方法であり、通常は再帰に基づいて実装します。
- グラフは頂点と辺で構成され、頂点の集合と辺の集合をまとめたものとして表せます。
- 線形の関係(連結リスト)や分割統治の関係(木)に比べ、ネットワークの関係(グラフ)は自由度が高く、その分複雑です。
- 有向グラフの辺には向きがあります。連結グラフではどの頂点にも到達でき、重み付きグラフでは各辺に重みの変数があります。
- 隣接行列は行列でグラフを表します。各行(列)が一つの頂点、行列の要素が辺に対応し、$1$ または $0$ で 2 頂点間に辺があるかどうかを示します。辺の追加・削除・検索・更新は効率的ですが、多くの空間を使います。
- 隣接リストは複数の連結リストでグラフを表します。$i$ 番目の連結リストは頂点 $i$ に対応し、その頂点に隣接するすべての頂点を格納します。隣接行列より空間を節約できますが、辺を探すには連結リストを走査する必要があるため、時間効率は低くなります。
- 隣接リスト内の連結リストが長くなりすぎた場合は、赤黒木やハッシュテーブルに変換して探索効率を高められます。
- アルゴリズムの考え方として、隣接行列は「空間を使って時間を節約する」、隣接リストは「時間を使って空間を節約する」方法です。
- グラフは、ソーシャルネットワークや地下鉄の路線など、さまざまな現実のシステムのモデル化に使えます。
- 木はグラフの特殊な場合であり、木の走査もグラフの走査の特殊な場合です。
- グラフの幅優先走査は、近いところから遠いところへ、層ごとに広がる探索方法で、通常はキューを使って実装します。
- グラフの深さ優先走査は、まず進めるところまで進み、進めなくなったら引き返す探索方法で、通常は再帰で実装します。
### Q & A
**Q**:経路の定義は頂点列ですか、それとも辺列ですか?
**Q**:経路は頂点の列ですか、それとも辺の列ですか。
Wikipedia では言語版ごとに定義が一致していません。英語版では「経路は辺の列」であり、中国語版では「経路は頂点の列」です。以下は英語版の原文です:In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices.
Wikipedia では言語版によって定義が異なります。英語版では「経路は辺の列」、中国語版では「経路は頂点の列」とされています。英語版の原文は次のとおりです。In graph theory, a path in a graph is a finite or infinite sequence of edges which joins a sequence of vertices.
本書では、経路を頂点列ではなく辺列とみなします。これは、2 つの頂点の間に複数の辺が存在する可能性があり、その場合は各辺がそれぞれ 1 本の経路に対応するためです。
本書では、経路を頂点の列ではなく、辺の列として扱います。2 頂点間を複数の辺が結ぶことがあり、その場合はそれぞれの辺が一つの経路に対応するためです。
**Q**:非連結グラフには到達できない頂点がありますか?
**Q**:非連結グラフには、走査で到達できない頂点がありますか。
非連結グラフでは、ある頂点から出発すると、少なくとも 1 つの頂点には到達できません。非連結グラフ全体を走査するには、グラフ内のすべての連結成分をたどれるように複数の始点を設定する必要があります。
非連結グラフでは、ある頂点から出発したとき、到達できない頂点が少なくとも一つあります。すべての連結成分を走査するには、複数の開始点を設定する必要があります。
**Q**:隣接リストにおいて、「その頂点に接続されたすべての頂点」の順序に決まりはありますか?
**Q**:隣接リストにある「その頂点につながるすべての頂点」の順序には、決まりがありますか。
順序は任意でかまいません。ただし実際の応用では、頂点を追加した順序や頂点値の大小順など、特定の規則に従って並べ替える必要がある場合があります。そうすることで、「ある種の極値を持つ」頂点をすばやく見つけやすくなります。
順序は任意です。ただし、実際の用途では、頂点を追加した順や頂点の値の大小順など、特定の規則に従って並べることがあります。これにより、「何らかの値が最大または最小である頂点」をすばやく探しやすくなります。