chapter11-图论
1.图的存储方式
首先,关于图的存储方式有2种,一种是邻接矩阵,一种是邻接表,而邻接表适用于1个点对到其他所有点的批处理,实际程序中经常使用。
邻接表会给每一个顶点建立一个单链表,即使那个顶点没有度(无向图),or没有任何出度(有向图)。在程序中,我们并不是使用单链表来存储,而是一个向量数组来表示一个图。
#include <iostream> #include <cstdio> #include <vector> using namespace std; const int MAXN = 1000; vector<int> graph[MAXN]; //给每个顶点都分配一个向量
2.并查集
并查集用于实现不相交集合的查找Find和合并Union两种操作。
-
查找:确定元素属于哪个集合。这种方法的步骤是:不断向上查找,直到找到它的根结点,之后根据根结点是否相同来判断两个元素是否属于同一集合。
-
合并:将两个子集合并成同一个集合。这种方法的步骤是:将一棵树作为另外一棵树的子树,从而使得两棵树变成一颗更大的树。
无论是查找,还是合并首先都要找到子集的根结点,这个操作的耗时与树高h有关,所以应尽可能保持较低的树高,为此要在查找和合并操作中加入一点约束和优化。
-
查找-路径压缩:在查找某个特定结点的根结点的同时,将将除了根结点之外的所有先辈结点直接指向根结点。
-
合并-矮树合并大树:为此在
father[MAXN]
数组的基础上,要增设height[MAXN]
,初始化为0,并且当且仅当两棵树的树高相同时,合并后的集合树高会加1,其他情况树高均不会发生变化,从而提高查找效率。
并查集用于判定是否是连通图非常方便,因为只要是连通图,必定只有一个连通分量,如果多于1个连通分量,那么就不是连通图。
判定是否是连通图
//判定连通图-并查集 2024-03-16 #include <iostream> #include <cstdio> using namespace std; const int MAXN = 1000 + 10; int father[MAXN]; int height[MAXN]; void Initial(int n) { for(int i = 0; i <= n; ++i) { father[i] = i; //初始每个顶点都是孤立的集合,自己是自己的根结点 height[i] = 0; } } //查找元素a属于哪个集合,返回根结点编号 int Find(int a) { if(father[a] != a) { father[a] = Find(father[a]); } return father[a]; } //合并元素x和元素y所在的两个子集合 void Union(int x, int y) { x = Find(x); y = Find(y); if(x != y) { if(height[x] < height[y]) { father[x] = y; } else if(height[y] < height[x]) { father[y] = x; } else { father[y] = x; height[x]++; } } } int main() { int n, m; while(scanf("%d%d", &n, &m) != EOF) { Initial(n); while(m--) { int x, y; cin >> x >> y; Union(x, y); } //连通分量是否只有1个 int component = 0; for(int i = 1; i <= n; ++i) { if(father[i] == i) { ++component; } } if(component == 1) { cout << "YES" << endl; } else { cout << "NO" << endl; } } return 0; }
3.最小生成树
即无向连通图中的极小连通子图,可能不只一棵,但最小生成树的边权和的值是一样的。**如何求解一个连通图的最小生成树,最常见的有Kruskal算法和Prim算法。
最小生成树的权值和
//最小生成树-kruskal算法,以边为依据逐步加入一个集合当中-并查集 2024-03-16 #include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int MAXN = 100; struct Edge { int from; int to; int length; bool operator<(const Edge &c) const { return length < c.length; } }; Edge edge[MAXN * MAXN]; int father[MAXN]; int height[MAXN]; void Initial(int n) { for(int i = 1; i <= n; ++i) { father[i] = i; height[i] = 0; } } int Find(int x) { if(x != father[x]) { father[x] = Find(father[x]); } return father[x]; } void Union(int x, int y) { x = Find(x); y = Find(y); if(x != y) { if(height[x] < height[y]) { father[x] = y; } else if(height[y] < height[x]) { father[y] = x; } else { father[y] = x; height[x]++; } } return; } int Kruskal(int n, int edgeNumber) { Initial(n); sort(edge, edge + edgeNumber); int sum = 0; for(int i = 0; i < edgeNumber; ++i) { int x = Find(edge[i].from); int y = Find(edge[i].to); if(x != y) { Union(x, y); sum += edge[i].length; } }//其实这里还缺一个判定连通分量是否为1,即是否是无向连通图 return sum; } int main() { int n; while(cin >> n) { if(n == 0) break; int edgeNumber = n * (n -1) / 2; for(int i = 0; i < edgeNumber; ++i) { scanf("%d%d%d", &edge[i].from, &edge[i].to, &edge[i].length); } int answer = Kruskal(n, edgeNumber); cout << answer << endl; } return 0; }
4.最短路径
Dijkstra算法用于解决单源最短路径问题。
Dijkstra算法在运行过程中将顶点集合V分成两个集合S和T。
(1)S:已确定的顶点集合,起始顶点到当前顶点最短的距离已确定。
(2)T=T - S:尚未确定的顶点集合。
初始所有顶点都在T集合中,然后设置起始顶点到各个顶点的距离值,到自己肯定是0,到其他顶点先设置理论上的最大值INF
。算法反复从集合T中选择当前到源点s最近的顶点u,将u加入集合S,然后对所有从u发出的边进行松弛操作。
松弛操作举例,假设现在把点U加入集合S,对所有从点U发出的边进行松弛操作为:
单源最短路径
//单源最短路径-用优先队列优化每次选择距离起始顶点最短的顶点 2024-03-16 #include <iostream> #include <cstdio> #include <vector> #include <cstring> #include <queue> using namespace std; const int MAXN = 200; const int INF = INT_MAX; struct Edge { int to; int length; Edge(int t, int l): to(t), length(l) {} }; struct Point { int index; int distance; Point(int i, int d): index(i), distance(d) {} bool operator< (const Point &p) const { return distance > p.distance; } }; vector<Edge> graph[MAXN]; int dis[MAXN]; priority_queue<Point> myPriortyQueue; void Dijkstra(int start, int n) { fill(dis, dis + MAXN, INF); dis[start] = 0; myPriortyQueue.push(Point(start, dis[start])); while(!myPriortyQueue.empty()) { int u = myPriortyQueue.top().index; myPriortyQueue.pop(); for(int i = 0; i < graph[u].size(); ++i) { int v = graph[u][i].to; int d = graph[u][i].length; if(dis[u] + d < dis[v]) { dis[v] = dis[u] + d; myPriortyQueue.push(Point(v, dis[v])); //if 更新,就放入优先队列中 } } } } int main() { int n, m; while(scanf("%d%d", &n, &m) != EOF) { memset(graph, 0, sizeof(graph)); while(m--) { int from, to, dis; scanf("%d%d%d", &from, &to, &dis); graph[from].push_back(Edge(to, dis)); graph[to].push_back(Edge(from, dis)); } int start, terminal; scanf("%d%d", &start, &terminal); Dijkstra(start, n); if(dis[terminal] == INF) { dis[terminal] = -1; } cout << dis[terminal] << endl; } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具