赞
踩
熟悉图论算法是对于准备C++后台开发岗位面试非常重要的一部分。我将为你概述Dijkstra算法、最小生成树算法以及深度优先搜索(DFS),这些都是图论中常用的算法。
目录
以下是Dijkstra算法的一个C++实现示例,我将包含详细的注释来解释每个关键部分。这个实现使用了优先队列来优化效率,它适用于边权重非负的图。
- #include <iostream>
- #include <vector>
- #include <queue>
- #include <climits>
-
- using namespace std;
-
- // 定义边的结构
- struct Edge {
- int to; // 边指向的顶点
- int weight; // 边的权重
- Edge(int t, int w) : to(t), weight(w) {}
- };
-
- // 定义用于优先队列的比较函数
- class Compare {
- public:
- bool operator() (pair<int, int>& p1, pair<int, int>& p2) {
- return p1.second > p2.second;
- }
- };
-
- void dijkstra(const vector<vector<Edge>>& graph, int start) {
- int n = graph.size();
- vector<int> dist(n, INT_MAX); // 存储起始点到所有点的最短距离
- vector<bool> visited(n, false); // 标记顶点是否访问过
-
- priority_queue<pair<int, int>, vector<pair<int, int>>, Compare> pq;
-
- // 从起始点开始
- pq.push({start, 0});
- dist[start] = 0;
-
- while (!pq.empty()) {
- int current = pq.top().first;
- pq.pop();
-
- if (visited[current]) continue;
- visited[current] = true;
-
- // 遍历当前顶点的所有邻接边
- for (const Edge& edge : graph[current]) {
- int next = edge.to;
- int nextDist = dist[current] + edge.weight;
-
- // 如果找到更短的路径,则更新
- if (nextDist < dist[next]) {
- dist[next] = nextDist;
- pq.push({next, nextDist});
- }
- }
- }
-
- // 打印最短路径结果
- for (int i = 0; i < n; ++i) {
- if (dist[i] == INT_MAX)
- cout << "Vertex " << i << ": unreachable" << endl;
- else
- cout << "Vertex " << i << ": " << dist[i] << endl;
- }
- }
-
- int main() {
- // 创建一个示例图
- int n = 5; // 图中顶点数
- vector<vector<Edge>> graph(n);
-
- // 添加边
- graph[0].emplace_back(1, 2);
- graph[0].emplace_back(3, 6);
- graph[1].emplace_back(2, 3);
- graph[1].emplace_back(3, 8);
- graph[1].emplace_back(4, 5);
- graph[2].emplace_back(4, 7);
- graph[3].emplace_back(4, 9);
-
- // 从顶点0开始应用Dijkstra算法
- dijkstra(graph, 0);
-
- return 0;
- }
vector<vector<Edge>>
来表示图,每个顶点都有一个边的列表。Edge
结构体包含了目标顶点和边的权重。dist
数组来跟踪从起点到每个顶点的最短距离。visited
数组来标记每个顶点是否已被处理。Kruskal算法使用并查集(Union-Find)结构来避免环的形成。
- #include <iostream>
- #include <vector>
- #include <algorithm>
-
- using namespace std;
-
- struct Edge {
- int src, dest, weight;
- bool operator<(Edge const& other) {
- return weight < other.weight;
- }
- };
-
- struct DisjointSets {
- vector<int> parent, rank;
- int n;
-
- DisjointSets(int n) {
- this->n = n;
- parent.resize(n);
- rank.resize(n);
-
- for (int i = 0; i < n; i++)
- parent[i] = i;
- }
-
- int find(int i) {
- if (parent[i] != i)
- parent[i] = find(parent[i]);
- return parent[i];
- }
-
- void merge(int x, int y) {
- x = find(x), y = find(y);
-
- if (rank[x] > rank[y])
- parent[y] = x;
- else
- parent[x] = y;
-
- if (rank[x] == rank[y])
- rank[y]++;
- }
- };
-
- void KruskalMST(vector<Edge>& edges, int V) {
- sort(edges.begin(), edges.end());
- DisjointSets ds(V);
-
- vector<Edge> result;
-
- for (auto edge : edges) {
- int x = ds.find(edge.src);
- int y = ds.find(edge.dest);
-
- if (x != y) {
- result.push_back(edge);
- ds.merge(x, y);
- }
- }
-
- // 打印MST
- for (auto e : result)
- cout << e.src << " - " << e.dest << " : " << e.weight << endl;
- }
-
- int main() {
- int V = 4; // 顶点数量
- vector<Edge> edges = {
- {0, 1, 10},
- {0, 2, 6},
- {0, 3, 5},
- {1, 3, 15},
- {2, 3, 4}
- };
-
- KruskalMST(edges, V);
- return 0;
- }
Prim算法使用优先队列来选择最小权重的边。
- #include <iostream>
- #include <vector>
- #include <queue>
- #include <climits>
-
- using namespace std;
-
- typedef pair<int, int> iPair; // 用于表示权重和顶点的对
-
- void PrimMST(vector<vector<iPair>>& graph, int V) {
- priority_queue<iPair, vector<iPair>, greater<iPair>> pq;
- int src = 0; // 可以从任何顶点开始
-
- vector<int> key(V, INT_MAX);
- vector<bool> inMST(V, false);
- vector<int> parent(V, -1);
-
- pq.push(make_pair(0, src));
- key[src] = 0;
-
- while (!pq.empty()) {
- int u = pq.top().second;
- pq.pop();
-
- inMST[u] = true;
-
- for (auto i : graph[u]) {
- int v = i.first;
- int weight = i.second;
-
- if (!inMST[v] && key[v] > weight) {
- key[v] = weight;
- pq.push(make_pair(key[v], v));
- parent[v] = u;
- }
- }
- }
-
- // 打印构造的MST
- for (int i = 1; i < V; ++i)
- cout << parent[i] << " - " << i << " : " << key[i] << endl;
- }
-
- int main() {
- int V = 4;
- vector<vector<iPair>> graph(V);
-
- // 构造图
- graph[0].push_back(make_pair(1, 10));
- graph[0].push_back(make_pair(2, 6));
- graph[0].push_back(make_pair(3, 5));
- graph[1].push_back(make_pair(3, 15));
- graph[2].push_back(make_pair(3, 4));
-
- PrimMST(graph, V);
-
- return 0;
- }
这两种算法在不同情况下都很有用。Kruskal算法适合边比较稀疏的图,而Prim算法适合边比较密集的图。在准备面试时,理解这两种算法的适用场景和优缺点是很重要的。
深度优先搜索(DFS)是一种用于遍历或搜索树或图的算法。它的基本思想是从一个顶点开始,沿着边到达新的顶点,并沿此路径尽可能深地搜索,直到找不到新的未访问顶点为止,然后回溯并继续搜索。以下是DFS算法的C++实现示例,包含详细注释。
- #include <iostream>
- #include <list>
- #include <vector>
-
- using namespace std;
-
- class Graph {
- int V; // 顶点的数量
- list<int> *adj; // 邻接表
-
- // DFS递归辅助函数
- void DFSUtil(int v, vector<bool>& visited) {
- // 标记当前节点为已访问
- visited[v] = true;
- cout << v << " ";
-
- // 递归访问所有未访问的邻接顶点
- for (auto i = adj[v].begin(); i != adj[v].end(); ++i) {
- if (!visited[*i]) {
- DFSUtil(*i, visited);
- }
- }
- }
-
- public:
- Graph(int V) {
- this->V = V;
- adj = new list<int>[V];
- }
-
- // 添加边到图中
- void addEdge(int v, int w) {
- adj[v].push_back(w); // 添加w到v的列表中
- }
-
- // DFS遍历函数
- void DFS(int v) {
- // 初始化所有顶点为未访问
- vector<bool> visited(V, false);
-
- // 从顶点v开始DFS遍历
- DFSUtil(v, visited);
- }
- };
-
- int main() {
- // 创建一个图
- Graph g(4);
-
- // 添加边
- g.addEdge(0, 1);
- g.addEdge(0, 2);
- g.addEdge(1, 2);
- g.addEdge(2, 0);
- g.addEdge(2, 3);
- g.addEdge(3, 3);
-
- // 从顶点2开始DFS遍历
- cout << "Starting DFS from vertex 2" << endl;
- g.DFS(2);
-
- return 0;
- }
Graph
类中的adj
是一个指向整数列表的数组,表示图的邻接表。addEdge
函数用于向图中添加边。DFS
函数初始化一个布尔类型的数组来跟踪访问过的顶点。DFSUtil
是一个递归函数,用于实际进行DFS遍历。它访问一个顶点,然后对于每一个邻接的未访问顶点,递归调用自身。请注意,DFS的特点是它可能不会访问图中的所有顶点,尤其是在图不是完全连通的情况下。要遍历所有顶点,可能需要从不同的顶点分别启动DFS。此外,DFS在实际应用中有多种变体,例如用于路径查找、拓扑排序或解决迷宫问题。在准备面试时,理解这些变体并知道如何实现它们是很有帮助的。
广度优先搜索(BFS)是图和树的一种遍历算法,它从一个起始顶点开始,先访问所有相邻的顶点,然后再逐层访问更远的顶点。它特别适用于找到从源点到其他顶点的最短路径。以下是广度优先搜索的C++实现,包含详细注释:
- #include <iostream>
- #include <vector>
- #include <queue>
- #include <list>
-
- using namespace std;
-
- class Graph {
- int V; // 顶点的数量
- list<int> *adj; // 邻接表
-
- public:
- Graph(int V) {
- this->V = V;
- adj = new list<int>[V];
- }
-
- // 添加边到图中
- void addEdge(int v, int w) {
- adj[v].push_back(w); // 在v的列表中添加w
- }
-
- // BFS遍历函数
- void BFS(int s) {
- // 初始化所有顶点为未访问
- vector<bool> visited(V, false);
-
- // 创建一个队列用于BFS
- queue<int> queue;
-
- // 标记当前节点为已访问并入队
- visited[s] = true;
- queue.push(s);
-
- while (!queue.empty()) {
- // 出队一个顶点并打印
- s = queue.front();
- cout << s << " ";
- queue.pop();
-
- // 获取所有邻接的顶点
- for (auto i = adj[s].begin(); i != adj[s].end(); ++i) {
- if (!visited[*i]) {
- visited[*i] = true;
- queue.push(*i);
- }
- }
- }
- }
- };
-
- int main() {
- // 创建一个图
- Graph g(4);
-
- // 添加边
- g.addEdge(0, 1);
- g.addEdge(0, 2);
- g.addEdge(1, 2);
- g.addEdge(2, 0);
- g.addEdge(2, 3);
- g.addEdge(3, 3);
-
- // 从顶点2开始BFS遍历
- cout << "Starting BFS from vertex 2" << endl;
- g.BFS(2);
-
- return 0;
- }
Graph
类中的adj
是一个指向整数列表的数组,表示图的邻接表。addEdge
函数向图中添加边。BFS
函数初始化一个布尔类型的数组来跟踪访问过的顶点。BFS通常用于找到从一个顶点到另一个顶点的最短路径,尤其是在未加权图中。在准备面试时,理解BFS的工作原理并能够根据需要修改和应用它是很重要的。
A搜索算法是一种有效的路径查找和图遍历算法,它结合了Dijkstra算法的确保最短路径的优点和启发式搜索(如贪心算法)的高效性。A算法使用启发式函数来估算从当前节点到目标节点的最佳路径成本,通常用于求解复杂的路径规划问题。以下是A*算法的C++实现示例,包含详细注释:
- #include <iostream>
- #include <vector>
- #include <queue>
- #include <cmath>
- #include <unordered_map>
-
- using namespace std;
-
- // 定义坐标点
- struct Point {
- int x, y;
- Point(int x, int y) : x(x), y(y) {}
- };
-
- // 计算两点之间的欧几里得距离,用作启发式函数
- double heuristic(Point a, Point b) {
- return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2));
- }
-
- // 定义节点,用于A*搜索
- struct Node {
- Point point; // 节点的坐标
- double f, g, h; // f = g + h
- Node *parent; // 指向父节点的指针
-
- Node(Point pt, double g, double h, Node *parent = nullptr)
- : point(pt), g(g), h(h), f(g + h), parent(parent) {}
-
- // 重载<运算符,用于优先队列
- bool operator<(const Node& other) const {
- return f > other.f;
- }
- };
-
- // A*搜索算法实现
- vector<Point> AStarSearch(vector<vector<int>>& grid, Point start, Point end) {
- priority_queue<Node> openSet; // 开放列表
- vector<vector<bool>> closedSet(grid.size(), vector<bool>(grid[0].size(), false)); // 关闭列表
-
- openSet.push(Node(start, 0, heuristic(start, end)));
-
- while (!openSet.empty()) {
- Node current = openSet.top();
- openSet.pop();
-
- // 检查是否到达终点
- if (current.point.x == end.x && current.point.y == end.y) {
- vector<Point> path;
- while (current.parent != nullptr) {
- path.push_back(current.point);
- current = *current.parent;
- }
- reverse(path.begin(), path.end());
- return path;
- }
-
- closedSet[current.point.x][current.point.y] = true;
-
- // 检查邻居
- vector<Point> neighbors = {/* 上下左右四个方向的邻居坐标 */};
- for (Point& neighbor : neighbors) {
- if (closedSet[neighbor.x][neighbor.y] || grid[neighbor.x][neighbor.y] == 0) {
- continue; // 跳过障碍物或已经在关闭列表中的节点
- }
-
- double tentative_g = current.g + heuristic(current.point, neighbor);
-
- // 添加到开放列表
- if (!closedSet[neighbor.x][neighbor.y]) {
- openSet.push(Node(neighbor, tentative_g, heuristic(neighbor, end), new Node(current)));
- }
- }
- }
-
- return vector<Point>(); // 如果找不到路径,返回空路径
- }
-
- int main() {
- // 创建网格地图,1表示可通过,0表示障碍物
- vector<vector<int>> grid = {
- {1, 1, 1, 1},
- {1, 0, 1, 1},
- {1, 1, 1, 1},
- {1, 1, 1, 1}
- };
-
- // 设置起点和终点
- Point start(0, 0), end(3, 3);
-
- // 执行A*搜索
- vector<Point> path = AStarSearch(grid, start, end);
-
- // 打印路径
- for (Point p : path) {
- cout << "(" << p.x << ", " << p.y << ") ";
- }
-
- return 0;
- }
请注意,这个示例是A算法的一个基本实现。在复杂的实际应用中,比如大型地图或动态变化的环境中,可能需要对算法进行优化和调整。在准备面试时,理解A算法的原理和实现方式非常重要。
Floyd-Warshall算法是一种计算图中所有顶点对之间最短路径的算法。它能够处理包含负权边的图(但不允许负权回路)。该算法通过动态规划逐步构建每对顶点间的最短路径。以下是Floyd-Warshall算法的C++实现,包含详细注释:
- #include <iostream>
- #include <vector>
- #include <climits>
-
- using namespace std;
-
- #define INF INT_MAX
-
- void FloydWarshall(vector<vector<int>>& graph) {
- int V = graph.size();
- vector<vector<int>> dist = graph;
-
- // 通过每个顶点k,尝试更新每对顶点i和j之间的最短路径
- for (int k = 0; k < V; k++) {
- for (int i = 0; i < V; i++) {
- for (int j = 0; j < V; j++) {
- // 如果i到k和k到j的路径都存在
- if (dist[i][k] != INF && dist[k][j] != INF) {
- // 更新i到j的最短路径
- dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
- }
- }
- }
- }
-
- // 打印最终的最短路径矩阵
- for (int i = 0; i < V; i++) {
- for (int j = 0; j < V; j++) {
- if (dist[i][j] == INF)
- cout << "INF ";
- else
- cout << dist[i][j] << " ";
- }
- cout << endl;
- }
- }
-
- int main() {
- // 创建一个图的邻接矩阵表示
- vector<vector<int>> graph = {
- {0, 5, INF, 10},
- {INF, 0, 3, INF},
- {INF, INF, 0, 1},
- {INF, INF, INF, 0}
- };
-
- // 执行Floyd-Warshall算法
- FloydWarshall(graph);
-
- return 0;
- }
graph[i][j]
表示顶点i到顶点j的边的权重,如果i和j之间没有直接的边,则为INF
(无穷大)。dist
数组初始化为图的邻接矩阵。dist[i][j]
表示顶点i到顶点j的最短路径长度。在准备面试时,了解Floyd-Warshall算法的原理和应用是很重要的,尤其是在处理复杂图论问题时。这个算法虽然简单,但对于理解动态规划和图的最短路径问题非常有帮助。
Tarjan算法是图论中的一个重要算法,它不仅用于识别强连通分量,还可以应用于其他问题,如桥的检测和割点的识别。在准备面试时,理解这个算法的工作原理并能够实现它对于展示你的算法和数据结构知识非常有帮助。
Tarjan算法是一种基于深度优先搜索(DFS)的图算法,用于找出有向图中的所有强连通分量。算法维护一个堆栈来追踪已访问的顶点,并使用两个数组来记录每个顶点的访问顺序和可回溯到的最早顶点。以下是Tarjan算法的C++实现,包括详细注释:
- #include <iostream>
- #include <vector>
- #include <stack>
- #include <list>
-
- using namespace std;
-
- class Graph {
- int V; // 顶点数
- list<int> *adj; // 邻接表
-
- void tarjanUtil(int u, vector<int>& disc, vector<int>& low, stack<int>& st, vector<bool>& stackMember) {
- static int time = 0;
-
- // 初始化当前节点
- disc[u] = low[u] = ++time;
- st.push(u);
- stackMember[u] = true;
-
- // 遍历所有邻接顶点
- for (int v : adj[u]) {
- // 如果v未被访问,递归调用
- if (disc[v] == -1) {
- tarjanUtil(v, disc, low, st, stackMember);
-
- // 检查子树中是否有回路
- low[u] = min(low[u], low[v]);
- }
- // 如果v在栈中,更新u的low值
- else if (stackMember[v] == true) {
- low[u] = min(low[u], disc[v]);
- }
- }
-
- // 如果u是强连通分量的根,提取整个分量
- int w = 0; // To store stack extracted vertices
- if (low[u] == disc[u]) {
- while (st.top() != u) {
- w = (int) st.top();
- cout << w << " ";
- stackMember[w] = false;
- st.pop();
- }
- w = (int) st.top();
- cout << w << "\n";
- stackMember[w] = false;
- st.pop();
- }
- }
-
- public:
- Graph(int V) {
- this->V = V;
- adj = new list<int>[V];
- }
-
- void addEdge(int v, int w) { adj[v].push_back(w); }
-
- // The function to find SCCs
- void tarjanSCC() {
- vector<int> disc(V, -1), low(V, -1);
- vector<bool> stackMember(V, false);
- stack<int> st;
-
- // Call the recursive helper function to find SCCs
- for (int i = 0; i < V; i++)
adj
是一个指向整数列表的数组。tarjanUtil
函数实现了Tarjan算法的主要逻辑。它使用时间戳来标记每个顶点的访问时间,用low
数组来跟踪每个节点可以回溯到的最早访问节点。low[u] == disc[u]
时,从堆栈中提取出一个完整的强连通分量。Bellman-Ford 算法是一种在带权图中计算单源最短路径的算法,尤其适用于含有负权边的图。该算法通过重复地松弛所有边,来逐步减小从源点到所有其他顶点的距离估计,直到这些估计不再改变。以下是Bellman-Ford 算法的 C++ 实现,包括详细注释:
- #include <iostream>
- #include <vector>
- #include <climits>
-
- using namespace std;
-
- // 定义边的结构
- struct Edge {
- int src, dest, weight;
- };
-
- class Graph {
- int V, E; // 图中的顶点数和边数
- vector<Edge> edges; // 边的列表
-
- public:
- Graph(int V, int E) {
- this->V = V;
- this->E = E;
- }
-
- // 向图中添加边
- void addEdge(int u, int v, int w) {
- edges.push_back({u, v, w});
- }
-
- // Bellman-Ford算法实现
- void BellmanFord(int src) {
- vector<int> dist(V, INT_MAX);
- dist[src] = 0;
-
- // 对每条边进行V-1次松弛操作
- for (int i = 1; i <= V - 1; i++) {
- for (int j = 0; j < E; j++) {
- int u = edges[j].src;
- int v = edges[j].dest;
- int weight = edges[j].weight;
- if (dist[u] != INT_MAX && dist[u] + weight < dist[v])
- dist[v] = dist[u] + weight;
- }
- }
-
- // 检查负权回路
- for (int i = 0; i < E; i++) {
- int u = edges[i].src;
- int v = edges[i].dest;
- int weight = edges[i].weight;
- if (dist[u] != INT_MAX && dist[u] + weight < dist[v]) {
- cout << "Graph contains negative weight cycle" << endl;
- return;
- }
- }
-
- // 打印最短路径
- for (int i = 0; i < V; i++)
- cout << "Distance from " << src << " to " << i << " is " << dist[i] << endl;
- }
- };
-
- int main() {
- int V = 5; // 顶点数
- int E = 8; // 边数
- Graph g(V, E);
-
- // 向图中添加边
- g.addEdge(0, 1, -1);
- g.addEdge(0, 2, 4);
- g.addEdge(1, 2, 3);
- g.addEdge(1, 3, 2);
- g.addEdge(1, 4, 2);
- g.addEdge(3, 2, 5);
- g.addEdge(3, 1, 1);
- g.addEdge(4, 3, -3);
-
- // 从顶点 0 执行Bellman-Ford算法
- g.BellmanFord(0);
-
- return 0;
- }
Bellman-Ford 算法是理解动态规划在图论中应用的一个很好的例子。尽管它的时间复杂度高于 Dijkstra 算法,但它能处理包含负权边的图。在准备面试时,理解和能够实现这个算法是很重要的。
网络流问题,特别是最大流问题,在图论中是一个重要的主题。Ford-Fulkerson 算法是解决这类问题的一种常用方法,它通过不断查找增广路径来增加流量,直到达到最大流。Edmonds-Karp 算法是 Ford-Fulkerson 算法的一个特定实现,它使用广度优先搜索(BFS)来查找增广路径,保证了多项式时间复杂度。以下是 Edmonds-Karp 算法的 C++ 实现,包含详细注释:
- #include <iostream>
- #include <vector>
- #include <queue>
- #include <climits>
- #include <cstring>
-
- using namespace std;
-
- // 寻找从 s 到 t 的路径,并更新残留网络
- bool bfs(vector<vector<int>>& rGraph, int s, int t, vector<int>& parent) {
- int V = rGraph.size();
- vector<bool> visited(V, false);
- queue<int> q;
- q.push(s);
- visited[s] = true;
- parent[s] = -1;
-
- // 标准的 BFS 循环
- while (!q.empty()) {
- int u = q.front();
- q.pop();
-
- for (int v = 0; v < V; v++) {
- if (visited[v] == false && rGraph[u][v] > 0) {
- // 如果我们找到了一个连接到汇点的路径,则终止 BFS
- if (v == t) {
- parent[v] = u;
- return true;
- }
- q.push(v);
- parent[v] = u;
- visited[v] = true;
- }
- }
- }
-
- return false;
- }
-
- // 使用 Ford-Fulkerson 算法的 Edmonds-Karp 实现来返回最大流
- int edmondsKarp(vector<vector<int>>& graph, int s, int t) {
- int u, v;
-
- // 创建残留网络并用给定的容量填充
- int V = graph.size();
- vector<vector<int>> rGraph(V, vector<int>(V)); // 残留网络
- for (u = 0; u < V; u++)
- for (v = 0; v < V; v++)
- rGraph[u][v] = graph[u][v];
-
- vector<int> parent(V); // 用于存储路径
- int max_flow = 0; // 存储最大流量
-
- // 增广路径循环
- while (bfs(rGraph, s, t, parent)) {
- // 找到最小的残留容量边
- int path_flow = INT_MAX;
- for (v = t; v != s; v = parent[v]) {
- u = parent[v];
- path_flow = min(path_flow, rGraph[u][v]);
- }
-
- // 更新残留网络的容量和反向边
- for (v = t; v != s; v = parent[v]) {
- u = parent[v];
- rGraph[u][v] -= path_flow;
- rGraph[v][u] += path_flow;
- }
-
- // 添加路径流量到总流量
- max_flow += path_flow;
- }
-
- return max_flow;
- }
-
- int main() {
- // 创建图:顶点数为 4
- vector<vector<int>> graph = { {0, 10, 0, 10},
- {0, 0, 4, 2},
- {0, 0, 0, 8},
- {0, 0, 0, 0} };
-
- // 设定源点为 0,汇点为 3
- cout << "The maximum possible flow is " << edmondsKarp(graph, 0, 3) << endl;
-
- return 0;
- }
rGraph
初始时与原始图相同,表示流量的可能性。bfs
函数用于在残留网络中查找从源点 s
到汇点 t
的路径,同时更新 parent
数组来记录路径。Edmonds-Karp 算法是网络流问题的一种有效解法,特别是在求解最大流问题时。在准备面试时,了解此算法的原理和实现对于展示你的图论知识非常有帮助。
Ford-Fulkerson 算法是解决网络流问题中的最大流问题的一种经典方法。它通过不断寻找从源点到汇点的增广路径,并沿着这些路径增加流量,直到无法再增加为止。以下是 Ford-Fulkerson 算法的 C++ 实现,我将包括详细的注释来解释关键部分:
- #include <iostream>
- #include <vector>
- #include <climits>
- #include <queue>
- #include <cstring>
-
- using namespace std;
-
- // 使用邻接矩阵来表示图
- class Graph {
- int V; // 顶点数
- vector<vector<int>> rGraph; // 残留网络
-
- public:
- Graph(int V, vector<vector<int>> graph) : V(V), rGraph(graph) {}
-
- // 使用 BFS 查找从 s 到 t 的路径
- bool bfs(int s, int t, vector<int>& parent) {
- vector<bool> visited(V, false);
- queue<int> q;
- q.push(s);
- visited[s] = true;
- parent[s] = -1;
-
- while (!q.empty()) {
- int u = q.front();
- q.pop();
-
- for (int v = 0; v < V; v++) {
- if (visited[v] == false && rGraph[u][v] > 0) {
- if (v == t) {
- parent[v] = u;
- return true;
- }
- q.push(v);
- parent[v] = u;
- visited[v] = true;
- }
- }
- }
- return false;
- }
-
- // 主函数实现 Ford-Fulkerson 算法
- int FordFulkerson(int s, int t) {
- int max_flow = 0; // 最大流初始化为 0
- vector<int> parent(V); // 用于存储路径
-
- // 增广路径循环
- while (bfs(s, t, parent)) {
- int path_flow = INT_MAX;
-
- // 计算增广路径的最小残留容量
- for (int v = t; v != s; v = parent[v]) {
- int u = parent[v];
- path_flow = min(path_flow, rGraph[u][v]);
- }
-
- // 更新残留网络的边和反向边
- for (int v = t; v != s; v = parent[v]) {
- int u = parent[v];
- rGraph[u][v] -= path_flow;
- rGraph[v][u] += path_flow;
- }
-
- // 将路径流添加到总流量
- max_flow += path_flow;
- }
-
- return max_flow;
- }
- };
-
- int main() {
- // 创建图:顶点数为 4
- vector<vector<int>> graph = { {0, 16, 13, 0},
- {0, 0, 10, 12},
- {0, 4, 0, 0},
- {0, 0, 9, 0} };
-
- Graph g(4, graph);
-
- // 计算从顶点 0(源点)到顶点 3(汇点)的最大流
- cout << "The maximum possible flow is " << g.FordFulkerson(0, 3) << endl;
-
- return 0;
- }
rGraph[u][v]
表示从顶点 u
到顶点 v
的流量。s
到汇点 t
的路径,并更新 parent
数组以记录路径。Ford-Fulkerson 算法是理解网络流问题的基础,并且在求解实际问题时非常有用。在准备面试时,了解并能够实现此算法对于展示你的图论和算法能力非常重要。
以上算法涵盖了图论中的一些关键概念和问题,包括寻找最短路径、探索图的结构,以及解决网络流问题。Dijkstra 和 Bellman-Ford 算法关注于单源最短路径问题,适用于不同类型的图(无负权边和有负权边)。而 Floyd-Warshall 和 A* 算法则扩展到所有顶点对的最短路径和特定场景下的最短路径搜索。Tarjan 算法用于探索图的深层结构,如强连通分量,而 DFS 和 BFS 提供了基础的图遍历方法。最后,Ford-Fulkerson 和 Edmonds-Karp 算法解决网络流和最大流问题。这些算法是计算机科学中图论应用的基石,对于处理复杂的数据结构和网络问题至关重要。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。