首先,关于图的存储方式有2种,一种是邻接矩阵,一种是邻接表,而邻接表适用于1个点对到其他所有点的批处理,实际程序中经常使用。 邻接表会给每一个顶点建立一个单链表,即使那个顶点没有度(无向图),or没有任何出度(有向图)。在程序中,我们并不是使用单链表来存储,而是一个向量数组来表示一个图。 并查集用于实现不相交集合的查找Find和合并Union两种操作。 查找:确定元素属于哪个集合。这种方法的步骤是:不断向上查找,直到找到它的根结点,之后根据根结点是否相同来判断两个元素是否属于同一集合。 合并:将两个子集合并成同一个集合。这种方法的步骤是:将一棵树作为另外一棵树的子树,从而使得两棵树变成一颗更大的树。 无论是查找,还是合并首先都要找到子集的根结点,这个操作的耗时与树高h有关,所以应尽可能保持较低的树高,为此要在查找和合并操作中加入一点约束和优化。 查找-路径压缩:在查找某个特定结点的根结点的同时,将将除了根结点之外的所有先辈结点直接指向根结点。 合并-矮树合并大树:为此在 并查集用于判定是否是连通图非常方便,因为只要是连通图,必定只有一个连通分量,如果多于1个连通分量,那么就不是连通图。 即无向连通图中的极小连通子图,可能不只一棵,但最小生成树的边权和的值是一样的。**如何求解一个连通图的最小生成树,最常见的有Kruskal算法和Prim算法。 Dijkstra算法用于解决单源最短路径问题。 Dijkstra算法在运行过程中将顶点集合V分成两个集合S和T。 (1)S:已确定的顶点集合,起始顶点到当前顶点最短的距离已确定。 (2)T=T - S:尚未确定的顶点集合。 初始所有顶点都在T集合中,然后设置起始顶点到各个顶点的距离值,到自己肯定是0,到其他顶点先设置理论上的最大值 松弛操作举例,假设现在把点B加入集合S,对所有从点B发出的边进行松弛操作为:1.图的存储方式
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int MAXN = 1000;
vector<int> graph[MAXN]; //给每个顶点都分配一个向量
2.并查集
father[MAXN]
数组的基础上,要增设height[MAXN]
,初始化为0,并且当且仅当两棵树的树高相同时,合并后的集合树高会加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算法,以边为依据逐步加入一个集合当中-并查集 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.最短路径
INF
。算法反复从集合T中选择当前到源点s最近的顶点u,将u加入集合S,然后对所有从u发出的边进行松弛操作。
\(D(u)= min{D(u), D(B) + (B, 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;
}