0.基础知识总结
常见概念
- 强连通图:有向图中存在一条经过所有结点的回路
- 单向连通图:有向图中存在一条经过所有结点的路
- 弱连通图:有向图的基图是连通图
- 有向完全图:有向图的任意顶点到其余各点均有一条弧
- 无向完全图:有向图的任意两个顶点均有一条边
经典算法总结
算法 | 时间复杂度 |
---|---|
基于邻接表的 DFS / BFS / topological | O ( n + e ) O(n+e) O(n+e) |
基于邻接矩阵的 DFS / BFS / topological | O ( n 2 ) O(n^2) O(n2) |
Prim | O ( n 2 ) O(n^2) O(n2) |
Kruskal | O ( e l o g 2 e ) O(elog2e) O(elog2e) |
Dijkstra | O ( n 2 ) O(n^2) O(n2) |
Floyd | O ( n 3 ) O(n^3) O(n3) |
- Prim:首先任选一结点作为起点,每次迭代在满足一端已选,一端未选的边选择一条最小边
- Kruskal:每次迭代选择一条最小边,该边与已选边不构成回路
- Dijkstra:每次迭代先更新距离表,然后从未选结点中选择距离起点最短的结点
- Floyd: A i j ( k ) = A i k ( k − 1 ) + A k j ( k − 1 ) A_{ij}^{(k)} = A_{ik}^{(k-1)} + A_{kj}^{(k-1)} Aij(k)=Aik(k−1)+Akj(k−1)
图的邻接矩阵存储
- 无向图的邻接矩阵一定是对称矩阵,有向图的邻接矩阵不一定是对称的
- 有向图的邻接矩阵的每行表示某个结点的出度,每列表示入度
图的邻接表存储
#include <iostream>
#include <vector>
template <typename T> struct Vertex {
T data;
struct Edge<T>* firstEdge;
Vertex() {}
Vertex(const T& data, Edge<T>* edge = NULL)
: data(data), firstEdge(edge) {}
};
template <typename T> struct Edge {
T data;
int weight;
struct Edge<T>* nextEdge;
Edge() {}
Edge(const T& data, int weight = 0, Edge<T>* edge = NULL)
: data(data), weight(weight), nextEdge(edge) {}
};
template <typename T> class Graph {
private:
std::vector<Vertex<T>*> vertices;
std::vector<bool> visited;
public:
int firstadj(int v) {}
int nextadj(int v, int w) {}
void dfs(int v) {}
void bfs(int v) {}
};
DFS算法
- 基于邻接表的深度遍历
void Graph::dfs(int v) {
visited[v] = true;
int w = firstadj(v);
while(w != 0) {
if (visited[w] == false)
dfs(w);
w = nextadj(v, w);
}
}
- 基于邻接矩阵的深度遍历
void Graph::dfs(int v) {
visited[v] = true;
for(int w = 1; w <= n; w++) {
if (matrix[v][w] != 0 && visited[w] == false)
dfs(w);
}
}
1.图的统计问题
无向图的顶点数
void dfs(int v, int& vertex) {
vertex++;
visited[v] = true;
int w = firstadj(v);
while(w != 0) {
if (visited[w] == false)
dfs(w, vertex);
w = nextadj(v, w);
}
}
int vertexCount() {
int count = 0;
for (int i = 1; i <= n; i++)
visited[i] = false;
for (int i = 1; i <= n; i++) {
if (visited[i] == false) {
dfs(i, count);
}
}
return count;
}
无向图的边数
void dfs(int v, int& edge) {
visited[v] = true;
int w = firstadj(v);
while(w != 0) {
edge++;
if (visited[w] == false)
dfs(w, edge);
w = nextadj(v, w);
}
}
int edgeCount() {
int count = 0;
for (int i = 1; i <= n; i++)
visited[i] = false;
for (int i = 1; i <= n; i++) {
if (visited[i] == false) {
dfs(i, count);
}
}
return count / 2;
}
无向图的连通分量数
int segmentCount() {
int count = 0;
for (int i = 1; i <= n; i++)
visited[i] = false;
for (int i = 1; i <= n; i++) {
// 每遇到一个未访问结点,统计结果加1
if (visited[i] == false) {
count++;
dfs(i);
}
}
return count;
}
2.图的连通性问题
判断是否连通
- 判断无向图G是否连通
- 等价问题:判断有向图G中顶点 v 0 v_0 v0 到每个顶点是否都有路径
bool isConnected() {
for (int i = 1; i <= n; i++)
visited[i] = false;
dfs(1);
for (int i = 1; i <= n; i++) {
if (visited[i] == false)
return false;
}
return true;
}
判断是否有路径
判断无向图的结点 v i v_i vi 到 v j v_j vj 是否有路径
bool hasPath(int vi, int vj) {
for (int i = 1; i <= n; i++)
visited[i] = false;
dfs(vi);
return visited[vj];
}
判断关节点 ★
- 在无向图G中,删除结点 v 0 v_0 v0 后,图是否被分割成2个以上的连通分量
- 镜像问题:在无向图G中,删除结点 v 0 v_0 v0 后,图是否仍然连通
bool isJoint(int v0) {
for (int i = 1; i <= n; i++) {
visited[i] = false;
}
visited[v0] = true; // 先设置v0已被访问过,把路堵住
int w = firstadj(v0);
dfs(w); // 再从v0的某一个邻接点w开始深度遍历
w = nextadj(v0, w);
while (w != 0) {
if (visited[w] == false) // 如果v0有邻接点未被访问,说明是关节点
return true;
w = nextadj(v0, w);
}
return false;
}
3.图是否为树
判断无向图是否为一棵树
- 图用邻接表存储:从任意一点开始 dfs,若能访问到 n 个结点和 n-1 条边,则为树
void dfs(int v, int& vn, int& en) {
vn++;
visited[v] = true;
int w = firstadj(v);
while(w != 0) {
en++;
if (visited[w] == false)
dfs(w, vn, en);
w = nextadj(v, w);
}
}
bool isTree() {
int vn = 0, en = 0, n = nodes(G);
dfs(1, vn, en);
return vn == n && en == 2 * (n - 1);
}
判断有向图是否为以v0为根的有向树
- 图用邻接表存储:从 v 0 v_0 v0 开始 bfs,比较访问的结点数和图的结点数:如果等于,说明是有向树;如果大于,说明存在环;如果小于,说明存在多个连通分量
bool isDirectedTree(int v0) {
int count = 1;
Queue queue; queue.enqueue(v0);
while(!queue.isEmpty()) {
int v = queue.dequeue();
int w = firstadj(v);
while(w != 0) { // 不再判断结点是否被访问过,只要搜素到就直接入队
queue.enqueue(w);
count++;
w = nextadj(v, w);
}
}
return count == nodes(G);
}
- 图用邻接矩阵存储:如果 v 0 v_0 v0 的入度为0,其余点入度均为1,则为树
bool isDirectedTree(int v0) {
for (int i = 1; i <= n; i++) {
if (matrix[i][v0] != 0)
return false;
}
for (int i = 1; i <= n && i != v0; i++) {
int indegree = 0;
for (int j = 1; j <= n; j++) {
if (matrix[j][i] != 0)
indegree++;
}
if (indegree > 1)
return false;
}
return true;
}
4.结点距离问题
求距离v0最远的一个结点
- 从 v 0 v_0 v0 开始 bfs,返回最后一个出队的结点
int farthestNode(int v0) {
for (int i = 1; i <= n; i++) {
visited[i] = false;
}
visited[v0] = true;
Queue queue; queue.enqueue(v0);
while(!queue.isEmpty()) {
int v = queue.dequeue();
int w = firstadj(v);
while(w != 0) {
if (visited[w] == false) {
visited[w] = true;
queue.enqueue(w);
}
w = nextadj(v, w);
}
}
return v;
}
判断vi到vj是否存在长度为 k 的路径
bool hasPath(int vi, int vj, int k) {
for (int i = 1; i <= n; i++) {
visited[i] = false;
}
visited[vi] = true;
Queue queue; queue.enqueue(vi, 1);
while(!queue.isEmpty()) {
int v = queue.dequeue();
int w = firstadj(v, length);
while(w != 0) {
if (w == vj && length == k)
return true;
if (visited[w] == false) {
visited[w] = true;
queue.enqueue(w, length + 1);
}
w = nextadj(v, w);
}
}
return false;
}