C++面试:熟悉图论算法(dijkstra算法、最小生成树、深度优先搜索等)

        熟悉图论算法是对于准备C++后台开发岗位面试非常重要的一部分。我将为你概述Dijkstra算法、最小生成树算法以及深度优先搜索(DFS),这些都是图论中常用的算法。

目录

1. Dijkstra算法

代码解释

运行示例

2. 最小生成树算法

    1. Kruskal算法

2. Prim算法

代码解释

3. 深度优先搜索(DFS)

代码解释

4. 广度优先搜索(BFS)

代码解释

运行示例

5. A* 搜索算法

代码解释

运行示例

6. Floyd-Warshall 算法

代码解释

运行示例

7. Tarjan算法

代码解释

运行示例

8. Bellman-Ford 算法

代码解释

运行示例

9. 拓扑排序

10. 网络流算法

代码解释

运行示例

代码解释

运行示例

总结


1. Dijkstra算法

  • 用途: 用于在加权图中找到一个顶点到图中所有其他顶点的最短路径。
  • 原理: 算法采用了贪心的策略,每次找到距离起始点最近的一个顶点,然后以该顶点为中介,更新其余所有顶点的最短路径。
  • 特点: 仅适用于边权重非负的图。

        以下是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;elsecout << "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结构体包含了目标顶点和边的权重。
  • 优先队列: 用于存储待访问的顶点及其从起点到该点的路径长度。
  • Dijkstra算法实现:
    • 使用dist数组来跟踪从起点到每个顶点的最短距离。
    • 使用visited数组来标记每个顶点是否已被处理。
    • 通过优先队列优化选择下一个要处理的顶点的过程。
    • 遍历每个顶点的所有邻接边,更新其最短路径。
运行示例
  • 这段代码定义了一个有5个顶点的图,并为其添加了一些边和权重。
  • 应用Dijkstra算法计算从顶点0到所有其他顶点的最短路径,并打印结果。

2. 最小生成树算法

  • 用途: 在一个加权连通图中选取一部分边,构成一棵包含所有顶点的树,并且使边的总权重尽可能小。
  • 常用算法:
    • Kruskal算法: 按照边的权重从小到大的顺序选择边,确保选择的边不会与已选择的边形成环。
    • Prim算法: 从一个顶点开始,每一步添加一条连接已选顶点集合和未选顶点集合的最小权重边。
    1. Kruskal算法

        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;elseparent[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);}}// 打印MSTfor (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;
}

2. Prim算法

        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;}}}// 打印构造的MSTfor (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算法:
    • 使用并查集来维护不同集合,并确保添加的边不会形成环。
    • 将所有的边按照权重进行排序,然后按顺序加入边,如果加入该边不会形成环,则该边是MST的一部分。
  • Prim算法:
    • 使用优先队列来存储和选择当前最小权重的边。
    • 从一个顶点开始,重复选择连接已选择顶点和未选择顶点的最小权重边,直到覆盖所有顶点。

        这两种算法在不同情况下都很有用。Kruskal算法适合边比较稀疏的图,而Prim算法适合边比较密集的图。在准备面试时,理解这两种算法的适用场景和优缺点是很重要的。

3. 深度优先搜索(DFS)

  • 用途: 用于遍历或搜索树或图的结构。深度优先搜索沿着树的深度遍历树的节点,尽可能深地搜索树的分支。
  • 原理: 从一个顶点开始,沿当前顶点的边走到未访问的顶点,当没有未访问的顶点时,回溯到上一个顶点,继续搜索直到所有顶点都被访问。
  • 应用: 解决迷宫问题、路径查找、拓扑排序等。

        深度优先搜索(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实现
    • DFS函数初始化一个布尔类型的数组来跟踪访问过的顶点。
    • DFSUtil是一个递归函数,用于实际进行DFS遍历。它访问一个顶点,然后对于每一个邻接的未访问顶点,递归调用自身。

        请注意,DFS的特点是它可能不会访问图中的所有顶点,尤其是在图不是完全连通的情况下。要遍历所有顶点,可能需要从不同的顶点分别启动DFS。此外,DFS在实际应用中有多种变体,例如用于路径查找、拓扑排序或解决迷宫问题。在准备面试时,理解这些变体并知道如何实现它们是很有帮助的。

4. 广度优先搜索(BFS)

  • 用途: 主要用于在图中进行层级搜索,适用于找到最短路径问题。
  • 原理: 从一个顶点开始,先访问所有邻接的顶点,再从这些邻接顶点出发,访问它们的邻接顶点,以此类推。

        广度优先搜索(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);// 创建一个队列用于BFSqueue<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。先将起始顶点加入队列,然后循环直到队列为空。在循环中,从队列中取出一个顶点,访问它的所有未访问的邻接顶点,并将这些邻接顶点加入队列。
运行示例
  • 这个程序创建了一个包含四个顶点的图,并添加了一些边。
  • 然后,它从顶点2开始进行BFS遍历,并打印访问的顶点顺序。

        BFS通常用于找到从一个顶点到另一个顶点的最短路径,尤其是在未加权图中。在准备面试时,理解BFS的工作原理并能够根据需要修改和应用它是很重要的。

5. A* 搜索算法

  • 用途: 用于图中的路径寻找和图遍历,特别是在有成本最小化要求的情况下。
  • 原理: 结合了Dijkstra算法和启发式方法(如曼哈顿距离、欧几里得距离)来找到从起始点到目标点的最低成本路径。

        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 + hNode *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;
}
代码解释
  • 启发式函数:这里使用的是欧几里得距离,你也可以使用曼哈顿距离或其他合适的启发式函数。
  • Node结构:每个节点包含其在网格中的坐标、从起点到该点的实际成本(g)、从该点到终点的估计成本(h)以及两者之和(f)。
  • 开放列表和关闭列表:开放列表(openSet)存储待访问的节点,关闭列表(closedSet)用于跟踪已访问的节点。
  • 邻居节点:算法检查当前节点的所有邻居,然后计算每个邻居的成本,将其添加到开放列表中。
运行示例
  • 这个程序创建了一个简单的网格地图,并设置了起点和终点。
  • 然后,它使用A*搜索算法找到从起点到终点的路径,并打印路径。

        请注意,这个示例是A算法的一个基本实现。在复杂的实际应用中,比如大型地图或动态变化的环境中,可能需要对算法进行优化和调整。在准备面试时,理解A算法的原理和实现方式非常重要。

6. Floyd-Warshall 算法

  • 用途: 解决所有顶点对的最短路径问题。
  • 原理: 通过考虑所有可能的路径中间点,系统地减少最短路径的估计。

        Floyd-Warshall算法是一种计算图中所有顶点对之间最短路径的算法。它能够处理包含负权边的图(但不允许负权回路)。该算法通过动态规划逐步构建每对顶点间的最短路径。以下是Floyd-Warshall算法的C++实现,包含详细注释:

#include <iostream>
#include <vector>
#include <climits>using namespace std;#define INF INT_MAXvoid 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 ";elsecout << 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数组初始化为图的邻接矩阵。
  • 动态规划:通过三重循环逐渐更新每对顶点之间的最短路径。对于每个顶点k,算法尝试通过顶点k更新所有顶点对i和j之间的最短路径。
  • 输出结果:打印出最终的最短路径矩阵,其中dist[i][j]表示顶点i到顶点j的最短路径长度。
运行示例
  • 这个程序创建了一个含有4个顶点的图,并以邻接矩阵形式表示。
  • 然后,它使用Floyd-Warshall算法计算图中所有顶点对之间的最短路径,并打印结果。

        在准备面试时,了解Floyd-Warshall算法的原理和应用是很重要的,尤其是在处理复杂图论问题时。这个算法虽然简单,但对于理解动态规划和图的最短路径问题非常有帮助。

7. Tarjan算法

  • 用途: 用于寻找图中的强连通分量。
  • 原理: 基于深度优先搜索,通过管理一个索引堆栈,能够有效地识别出强连通分量。

        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 verticesif (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 SCCsvoid tarjanSCC() {vector<int> disc(V, -1), low(V, -1);vector<bool> stackMember(V, false);stack<int> st;// Call the recursive helper function to find SCCsfor (int i = 0; i < V; i++)
代码解释
  • 图的表示:图使用邻接表表示,其中adj是一个指向整数列表的数组。
  • Tarjan算法的核心tarjanUtil函数实现了Tarjan算法的主要逻辑。它使用时间戳来标记每个顶点的访问时间,用low数组来跟踪每个节点可以回溯到的最早访问节点。
  • 强连通分量的提取:当low[u] == disc[u]时,从堆栈中提取出一个完整的强连通分量。
运行示例
  • 这个程序创建了一个含有5个顶点的图,并添加了一些边。
  • 然后,它使用Tarjan算法找到并打印图中的所有强连通分量。

8. Bellman-Ford 算法

  • 用途: 在带权图中计算单源最短路径,特别是处理图中包含负权边的情况。
  • 原理: 通过松弛操作重复地减小从源点到所有其他顶点的距离估计。

        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 算法实现
    • 初始化所有顶点的距离为无穷大,除了源顶点的距离为0。
    • 对所有边执行 V-1 次松弛操作,更新每个顶点的距离估计。
    • 再次检查所有边,确认是否存在负权回路。
  • 打印结果:打印从源顶点到所有其他顶点的最短路径。
运行示例
  • 这个程序创建了一个含有5个顶点和8条边的图。
  • 然后,它使用 Bellman-Ford 算法从顶点0开始计算最短路径,并打印结果。

      Bellman-Ford 算法是理解动态规划在图论中应用的一个很好的例子。尽管它的时间复杂度高于 Dijkstra 算法,但它能处理包含负权边的图。在准备面试时,理解和能够实现这个算法是很重要的。

9. 拓扑排序

  • 用途: 对有向无环图(DAG)进行排序,以线性顺序表示图中所有顶点的前后关系。
  • 原理: 基于深度优先搜索或入度表(每个节点入度的计数)。

10. 网络流算法

  • 用途: 用于求解网络流问题,如最大流问题。
  • 常用算法: Ford-Fulkerson 算法、Edmonds-Karp 算法等。

        网络流问题,特别是最大流问题,在图论中是一个重要的主题。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) {// 如果我们找到了一个连接到汇点的路径,则终止 BFSif (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() {// 创建图:顶点数为 4vector<vector<int>> graph = { {0, 10, 0, 10},{0, 0, 4, 2},{0, 0, 0, 8},{0, 0, 0, 0} };// 设定源点为 0,汇点为 3cout << "The maximum possible flow is " << edmondsKarp(graph, 0, 3) << endl;return 0;
}
代码解释
  • 创建残留网络:残留网络 rGraph 初始时与原始图相同,表示流量的可能性。
  • 广度优先搜索(BFS)bfs 函数用于在残留网络中查找从源点 s 到汇点 t 的路径,同时更新 parent 数组来记录路径。
  • 计算最大流:在找到增广路径之后,计算该路径上的最小残留容量,然后更新残留网络,增加流量。
  • 更新残留网络:减少正向边的容量,增加反向边的容量,表示流量的重新分配。
运行示例
  • 程序创建了一个包含 4 个顶点的图,并设置了每条边的容量。
  • 然后,它使用 Edmonds-Karp 算法计算从顶点 0(源点)到顶点 3(汇点)的最大流,并打印结果。

        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;  // 最大流初始化为 0vector<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() {// 创建图:顶点数为 4vector<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 的流量。
  • BFS:使用广度优先搜索来查找从源点 s 到汇点 t 的路径,并更新 parent 数组以记录路径。
  • 计算最大流:通过不断查找增广路径并更新残留网络,直到无法找到新的增广路径为止。
  • 更新残留网络:沿着找到的路径减少正向边的流量,并增加反向边的流量。

运行示例

  • 程序创建了一个包含 4 个顶点的图,并设置了每条边的流量。
  • 然后,它使用 Ford-Fulkerson 算法计算从顶点 0(源点)到顶点 3(汇点)的最大流,并打印结果。

        Ford-Fulkerson 算法是理解网络流问题的基础,并且在求解实际问题时非常有用。在准备面试时,了解并能够实现此算法对于展示你的图论和算法能力非常重要。

总结

        以上算法涵盖了图论中的一些关键概念和问题,包括寻找最短路径、探索图的结构,以及解决网络流问题。Dijkstra 和 Bellman-Ford 算法关注于单源最短路径问题,适用于不同类型的图(无负权边和有负权边)。而 Floyd-Warshall 和 A* 算法则扩展到所有顶点对的最短路径和特定场景下的最短路径搜索。Tarjan 算法用于探索图的深层结构,如强连通分量,而 DFS 和 BFS 提供了基础的图遍历方法。最后,Ford-Fulkerson 和 Edmonds-Karp 算法解决网络流和最大流问题。这些算法是计算机科学中图论应用的基石,对于处理复杂的数据结构和网络问题至关重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/653766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot游戏分享网站源码和论文

网络的广泛应用给生活带来了十分的便利。所以把游戏分享管理与现在网络相结合&#xff0c;利用java技术建设游戏分享网站&#xff0c;实现游戏分享的信息化。则对于进一步提高游戏分享管理发展&#xff0c;丰富游戏分享管理经验能起到不少的促进作用。 游戏分享网站能够通过互…

快手社招一面算法原题

快手面试 最近收到一位读者的留言&#xff0c;是跟我吐槽「快手社招面试」的。 但是由于交谈内容太多东西需要脱敏&#xff0c;我打完马赛克之后&#xff0c;发现上下文都不连贯了。 遂罢。 但一转念&#xff0c;我想如果这真的是这个公司的普遍性问题&#xff0c;而非独立个例…

JSON使用详解

JSON&#xff08;JavaScript Object Notation&#xff09;是一种常用的数据交换格式&#xff0c;它具有简单、易读和易于解析的特点。下面是使用JSON的一些常见方法&#xff1a; 1、创建JSON对象&#xff1a; JSON对象由键值对组成&#xff0c;用花括号 {} 包围。键值对之间使…

【极数系列】Flink环境搭建Linux版本 (03)

文章目录 引言01 Linux部署JDK11版本1.下载Linux版本的JDK112.创建目录3.上传并解压4.配置环境变量5.刷新环境变量6.检查jdk安装是否成功 02 Linux部署Flink1.18.0版本1.下载Flink1.18.0版本包2.上传压缩包到服务器3.修改flink-config.yaml配置4.启动服务5.浏览器访问6.停止服务…

解决Ubuntu20.04远程时必须连接显示器

此文档解决Ubuntu20.04远程使用时必须连接服务器的问题。通过使用虚拟服务器&#xff0c;让设备无显示器也可以远程。只需要新建一个文件&#xff0c;将命令粘贴进去即可。 目录 1&#xff09;安装软件 2&#xff09;添加配置文件 3&#xff09;编辑配置文件 4&#xff09…

verdaccio搭建npm私服

一、安装verdaccio 注&#xff1a;加上–unsafe-perm的原因是防止报grywarn权限的错 npm install -g verdaccio --unsafe-perm 二、启动verdaccio verdaccio 三、配置文件 找到config.yml一般情况下都在用户下的这个文件夹下面 注&#xff1a;首次启动后才会生成 C:\Users\h…

Idea上操作Git回退本地版本,怎么样保留已修改的文件,回退本地版本的四种方式代表什么?

Git的基本概念:Git是一个版本控制系统,用于管理代码的变更历史记录。核心概念包括仓库、分支、提交和合并。 1、可以帮助开发者合并开发的代码 2、如果出现冲突代码的合并,会提示后提交合并代码的开发者,让其解决冲突 3、代码文件版本管理 问题描述 当我们使用git提交代码…

深入了解低代码开发:多角度分类

引言 随着数字化时代的到来&#xff0c;应用程序的需求不断增长&#xff0c;企业和开发者们面临着更多的挑战&#xff0c;包括开发周期的压力、技术复杂性的增加以及对高效协作的需求。在这一背景下&#xff0c;低代码开发应运而生&#xff0c;成为解决这些挑战的一种强大工具…

算法基础课-数据结构

单链表 题目链接&#xff1a;826. 单链表 - AcWing题库 思路&#xff1a;AcWing 826. 单链表---图解 - AcWing 需要注意的点在于理解ne[idx] head&#xff0c;idx表示当前的点&#xff0c;意思是将当前的点链到头结点的后面&#xff0c;再将头结点链在当前idx的前面。 #inc…

JVM系列——垃圾收集器

对象存活判断 引用计数法 在对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器值就加一&#xff1b;当引用失效时&#xff0c;计数器值就减一&#xff1b;任何时刻计数器为零的对象就是不可能再被使用的。 可达性分析算法 通过一系列称为“GC …

基于springboot+微信小程序+vue实现的校园二手商城项目源码

介绍 校园二手商城&#xff0c;架构&#xff1a;springboot微信小程序vue 软件架构 软件架构说明 系统截图 技术选型 技术版本说明Spring Boot2.1.6MVC核心框架Spring Security oauth22.1.5认证和授权框架MyBatis3.5.0ORM框架MyBatisPlus3.1.0基于mybatis&#xff0c;使用…

Whatsapp相关(四)

Whatsapp相关(四) 书接上文: whatsapp相关(三)-消息发送 1: 编写脚本 首先我们先通过SQLiteDatabase的insert方法.来打印相关的操作. Java.perform(function() {console.log("insert 0: 可以发消息了")var database Java.use(android.database.sqlite.SQLiteDa…

【蓝桥备赛】妮妮的月饼工厂——二分查找

题目链接 妮妮的月饼工厂 个人思路 通过二分查找&#xff0c;寻找满足条件的高度&#xff0c;判定标准是当我们选择mid高度时&#xff0c;我们可以切出的月饼个数是否满足题目要求的 K 个。 static boolean check(long mid) {if (mid 0) return false;long res 0;for (int …

蓝桥杯备战——8.DS1302时钟芯片

1.分析原理图 由上图可以看到&#xff0c;芯片的时钟引脚SCK接到了P17,数据输出输入引脚IO接到P23,复位引脚RST接到P13。 2.查阅DS1302芯片手册 具体细节还需自行翻阅手册&#xff0c;我只截出重点部分 总结&#xff1a;数据在上升沿写出&#xff0c;下降沿读入&#xff0c;…

QGIS使用地理配准将3857坐标系转成上海城建坐标

控制点格式 如 mapX mapY sourceX sourceY enable dX dY residual -58653 70641 13452659.39 3746386.025 1 0 0 0 -58653 65641 13452693.09 3740477.283 1 0 0 0 ......保存为.points格式 图层预处理 图层投影为3857坐标系 地理配准 1. 打开图层-地理配准 工具 2. 导入…

import org.apache.commons.lang3.ObjectUtils;下的ObjectUtils工具类

目录 依赖 1.ObjectUtils.compare() 1.1比较Integer类型数据大小 1.2比较int类型数据大小 2.ObjectUtils.CONST()&#xff1a;作用是将value的值赋给result 1.boolean类型 依赖 <!-- lang3 可以使用其中的工具类来处理字符串、日期、数组等等常见的功能--><depe…

基于FX构建大型Golang应用

Uber开源的FX可以帮助Go应用解耦依赖&#xff0c;实现更好的代码复用。原文: How to build large Golang applications using FX 构建复杂的Go应用程序可能会引入很多耦合 Golang是一种流行编程语言&#xff0c;功能强大&#xff0c;但人们还是会发现在处理依赖关系的同时组织大…

sql注入第一关

判断注入点的类型 通常 Sql 注入漏洞分为 2 种类型&#xff1a; 数字型字符型 数字型测试 在参数后面加上单引号,比如: http://xxx/abc.php?id1 如果页面返回错误&#xff0c;则存在 Sql 注入。 原因是无论字符型还是整型都会因为单引号个数不匹配而报错。 如果未报错&…

Go语言中的HTTP代理处理机制

在当今的互联网世界&#xff0c;HTTP代理是一种常见的网络通信方式&#xff0c;用于保护用户的隐私、突破网络限制或提高网络访问速度。在Go语言中&#xff0c;代理处理机制的实现可以为开发者提供强大的网络通信能力。本文将深入探讨Go语言中的HTTP代理处理机制。 首先&#…

使用Windows API实现屏幕截图及服务器传输

功能描述 屏幕截图在许多应用程序和系统中都是一个有用的功能。有时&#xff0c;我们需要捕获当前屏幕的图像&#xff0c;并将其发送到服务器以供进一步处理或存储。下面的C代码演示了如何使用Windows API以及GDI库来完成这一任务。 关键代码分析 屏幕信息获取&#xff1a; …