一般的なアルゴリズム手法 - グラフ アルゴリズム
簡単な紹介
グラフ アルゴリズムは、グラフ理論の問題を解決するアルゴリズムの一種で、主にグラフの走査、経路探索、最小全域木などの問題に使用されます。グラフは、ノード(頂点)とノードを接続するエッジで構成されるデータ構造です。
アルゴリズムの分類
以下では、いくつかの一般的なグラフ アルゴリズムを紹介し、C++ で実装されたサンプル プログラムを提供します。
深さ優先検索 (DFS)
深さ優先検索はグラフ走査のアルゴリズムであり、再帰またはスタックを通じてグラフの深さ優先の走査を実行します。深さ優先検索は開始ノードから開始し、続行できなくなるまでパスに沿ってできるだけ深いグラフのノードを訪問し、その後、前のノードに戻って他のパスの探索を続けます。以下は、深さ優先検索を使用してグラフ内にパスが存在するかどうかを検索する例です。
#include <iostream>
#include <vector>
bool dfs(std::vector<std::vector<int>>& graph, int start, int target, std::vector<bool>& visited) {
if (start == target) {
return true; // 找到目标节点,存在一条路径
}
visited[start] = true; // 标记当前节点为已访问
for (int neighbor : graph[start]) {
if (!visited[neighbor]) {
if (dfs(graph, neighbor, target, visited)) {
return true; // 从相邻节点出发能够找到目标节点
}
}
}
return false; // 从当前节点无法找到目标节点
}
int main() {
std::vector<std::vector<int>> graph = {
{
1, 2}, {
2, 3}, {
3}, {
}};
int start = 0;
int target = 3;
int n = graph.size();
std::vector<bool> visited(n, false); // 记录节点的访问状态
bool result = dfs(graph, start, target, visited);
std::cout << std::boolalpha << result << std::endl; // 输出:true
return 0;
}
幅優先検索 (BFS)
幅優先検索は、グラフ走査のアルゴリズムです。開始ノードから開始して、グラフのノードをレイヤーごとに走査します。最初は開始ノードに最も近いノードを訪問し、次に距離が増加するノードを徐々に訪問します。幅優先検索では、キューを使用してアクセスするノードを保存し、ノードが階層順に走査されるようにします。次に、幅優先検索を使用してグラフ内にパスが存在するかどうかを検索する例を示します。
#include <iostream>
#include <vector>
#include <queue>
bool bfs(std::vector<std::vector<int>>& graph, int start, int target) {
int n = graph.size();
std::vector<bool> visited(n, false); // 记录节点的访问状态
std::queue<int> q; // 创建队列用于广度优先遍历
q.push(start); // 将起始节点放入队列
visited[start] = true;
while (!q.empty()) {
int curr = q.front();
q.pop();
if (curr == target) {
return true; // 找到目标节点,存在一条路径
}
for (int neighbor : graph[curr]) {
if (!visited[neighbor]) {
q.push(neighbor);
visited[neighbor] = true;
}
}
}
return false; // 无法找到目标节点
}
int main() {
std::vector<std::vector<int>> graph = {
{
1, 2}, {
2, 3}, {
3}, {
}};
int start = 0;
int target = 3;
bool result = bfs(graph, start, target);
std::cout << std::boolalpha << result << std::endl; // 输出:true
return 0;
}
最短経路アルゴリズム
ダイクストラアルゴリズムとベルマンフォードアルゴリズム
ダイクストラのアルゴリズム
ダイクストラのアルゴリズムは、単一ソース最短パス問題、つまり固定開始ノードから他のすべてのノードまでの最短パスを解決するために使用されます。貪欲な戦略を使用して、開始ノードから各ノードまでの最短パス長を徐々に更新します。
#include <iostream>
#include <vector>
#include <queue>
#include <limits>
#define INF std::numeric_limits<int>::max()
void dijkstra(std::vector<std::vector<std::pair<int, int>>>& graph, int start, std::vector<int>& dist) {
int n = graph.size();
std::vector<bool> visited(n, false);
std::priority_queue<std::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<std::pair<int, int>>> pq;
dist[start] = 0;
pq.push(std::make_pair(0, start));
while (!pq.empty()) {
int u = pq.top().second;
pq.pop();
if (visited[u]) {
continue;
}
visited[u] = true;
for (const auto& neighbor : graph[u]) {
int v = neighbor.first;
int weight = neighbor.second;
if (dist[u] != INF && dist[u] + weight < dist[v]) {
dist[v] = dist[u] + weight;
pq.push(std::make_pair(dist[v], v));
}
}
}
}
int main() {
int n = 6; // 节点数
std::vector<std::vector<std::pair<int, int>>> graph(n); // 邻接表表示的图
// 添加边
graph[0].push_back(std::make_pair(1, 2));
graph[0].push_back(std::make_pair(2, 4));
graph[1].push_back(std::make_pair(2, 1));
graph[1].push_back(std::make_pair(3, 7));
graph[2].push_back(std::make_pair(4, 3));
graph[3].push_back(std::make_pair(4, 2));
graph[3].push_back(std::make_pair(5, 1));
graph[4].push_back(std::make_pair(5, 5));
int start = 0; // 起始节点
std::vector<int> dist(n, INF); // 保存起始节点到各个节点的最短路径长度
dijkstra(graph, start, dist);
std::cout << "Shortest distances from node " << start << ":\n";
for (int i = 0; i < n; i++) {
std::cout << "Node " << i << "'s shortest distance: " << dist[i] << std::endl;
}
return 0;
}
ベルマン・フォードアルゴリズム
Bellman-Ford アルゴリズムは、負の重み付けされたエッジを含むグラフの単一ソース最短経路問題を解くために使用されます。すべてのエッジに対して緩和操作を実行することで、開始ノードから各ノードまでの最短パス長を徐々に更新します。
#include <iostream>
#include <vector>
#include <limits>
#define INF std::numeric_limits<int>::max()
struct Edge {
int src, dest, weight;
};
void bellmanFord(std::vector<Edge>& edges, int n, int start, std::vector<int>& dist) {
dist[start] = 0;
for (int i = 1; i <= n - 1; i++) {
for (const auto& edge : edges) {
int u = edge.src;
int v = edge.dest;
int weight = edge.weight;
if (dist[u] != INF && dist[u] + weight < dist[v]) {
dist[v] = dist[u] + weight;
}
}
}
// 检查是否存在负权环
for (const auto& edge : edges) {
int u = edge.src;
int v = edge.dest;
int weight = edge.weight;
if (dist[u] != INF && dist[u] + weight < dist[v]) {
std::cout << "Graph contains a negative-weight cycle" << std::endl;
return;
}
}
}
int main() {
int n = 5; // 节点数
std::vector<Edge> edges = {
{
0, 1, -1},
{
0, 2, 4},
{
1, 2, 3},
{
1, 3, 2},
{
1, 4, 2},
{
3, 2, 5},
{
3, 1, 1},
{
4, 3, -3},
};
int start = 0; // 起始节点
std::vector<int> dist(n, INF); // 保存起始节点到各个节点的最短路径长度
bellmanFord(edges, n, start, dist);
std::cout << "Shortest distances from node " << start << ":\n";
for (int i = 0; i < n; i++) {
std::cout << "Node " << i << "'s shortest distance: " << dist[i] << std::endl;
}
return 0;
}
最小スパニング ツリー アルゴリズム
プリムのアルゴリズムとクラスカルのアルゴリズム
プリムのアルゴリズム
プリムのアルゴリズムは、重み付き無向グラフの最小スパニング ツリーを解くために使用されるアルゴリズムです。これは開始ノードから開始し、すべてのノードが含まれるまで最小スパニング ツリーのエッジを徐々に拡張します。このアルゴリズムの中心的な考え方は、現在の最小スパニング ツリーから選択されたノードと選択されていないノードを接続する最小重みエッジを毎回選択する貪欲な戦略です。
#include <iostream>
#include <vector>
#include <queue>
#define INF std::numeric_limits<int>::max()
struct Edge {
int src, dest, weight;
};
struct Compare {
bool operator()(const Edge& e1, const Edge& e2) {
return e1.weight > e2.weight;
}
};
std::vector<Edge> primMST(std::vector<std::vector<std::pair<int, int>>>& graph) {
int n = graph.size();
std::vector<Edge> mst; // 保存最小生成树的边
std::vector<bool> visited(n, false);
std::priority_queue<Edge, std::vector<Edge>, Compare> pq;
// 从节点0开始
visited[0] = true;
for (const auto& neighbor : graph[0]) {
int v = neighbor.first;
int weight = neighbor.second;
pq.push({
0, v, weight});
}
while (!pq.empty()) {
Edge e = pq.top();
pq.pop();
int src = e.src;
int dest = e.dest;
int weight = e.weight;
if (visited[dest]) {
continue;
}
visited[dest] = true;
mst.push_back({
src, dest, weight});
for (const auto& neighbor : graph[dest]) {
int v = neighbor.first;
int w = neighbor.second;
if (!visited[v]) {
pq.push({
dest, v, w});
}
}
}
return mst;
}
int main() {
int n = 5; // 节点数
std::vector<std::vector<std::pair<int, int>>> graph(n); // 邻接表表示的图
// 添加边
graph[0].push_back(std::make_pair(1, 2));
graph[1].push_back(std::make_pair(0, 2));
graph[0].push_back(std::make_pair(2, 3));
graph[2].push_back(std::make_pair(0, 3));
graph[1].push_back(std::make_pair(2, 4));
graph[2].push_back(std::make_pair(1, 4));
graph[1].push_back(std::make_pair(3, 3));
graph[3].push_back(std::make_pair(1, 3));
graph[1].push_back(std::make_pair(4, 1));
graph[4].push_back(std::make_pair(1, 1));
graph[2].push_back(std::make_pair(3, 2));
graph[3].push_back(std::make_pair(2, 2));
graph[3].push_back(std::make_pair(4, 5));
graph[4].push_back(std::make_pair(3, 5));
std::vector<Edge> mst = primMST(graph);
std::cout << "Minimum Spanning Tree:\n";
for (const auto& edge : mst) {
std::cout << edge.src << " - " << edge.dest << " : " << edge.weight << std::endl;
}
return 0;
}
クラスカルアルゴリズム
クラスカルのアルゴリズムは、重み付き無向グラフの最小スパニング ツリーを解くためのアルゴリズムです。エッジの重みが小さい順にエッジを選択し、サイクルが形成されるかどうかを判定し、サイクルが形成されない場合には最小全域木にエッジを追加します。このアルゴリズムの基本的な考え方は、すべてのノードがスパニング ツリーに含まれるまで、毎回最小の重みを持つエッジを選択する貪欲な戦略です。
#include <iostream>
#include <vector>
#include <algorithm>
struct Edge {
int src, dest, weight;
};
struct UnionFind {
std::vector<int> parent;
std::vector<int> rank;
UnionFind(int n) {
parent.resize(n);
rank.resize(n, 0);
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void unite(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
}
};
bool compareEdges(const Edge& e1, const Edge& e2) {
return e1.weight < e2.weight;
}
std::vector<Edge> kruskalMST(std::vector<Edge>& edges, int n) {
std::vector<Edge> mst; // 保存最小生成树的边
UnionFind uf(n);
std::sort(edges.begin(), edges.end(), compareEdges);
for (const auto& edge : edges) {
int src = edge.src;
int dest = edge.dest;
if (uf.find(src) != uf.find(dest)) {
mst.push_back(edge);
uf.unite(src, dest);
}
}
return mst;
}
int main() {
int n = 5; // 节点数
std::vector<Edge> edges = {
{
0, 1, 2},
{
0, 2, 3},
{
1, 2, 4},
{
1, 3, 3},
{
1, 4, 1},
{
2, 3, 2},
{
3, 4, 5},
};
std::vector<Edge> mst = kruskalMST(edges, n);
std::cout << "Minimum Spanning Tree:\n";
for (const auto& edge : mst) {
std::cout << edge.src << " - " << edge.dest << " : " << edge.weight << std::endl;
}
return 0;
}
記事の概要
これらのアルゴリズムは、グラフ理論の問題を解くときに非常に役立ち、問題の要件とグラフの特性に基づいて適切なアルゴリズムを選択できます。実際のアプリケーションでは、問題のニーズを満たすために、特定の問題に応じてアルゴリズムを適切に調整および最適化する必要がある場合があることに注意してください。