1.最小生成树
- 在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边,而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集且为无循环图,使得 w(T) 最小,则此 T 为 G 的最小生成树。
最小生成树其实是最小权重生成树的简称。 - 生成树和最小生成树有许多重要的应用。
例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。 - 使用例
用最小的权值连接所有顶点 - 图的实现类
JAVA实现数据结构:图
1.1普利姆(Prim)算法
- Prim算法简述
1).初始化:Vnew= {x},其中x为集合V中的任一节点(起始点),Enew= {},为空;
2).重复下列操作,直到Vnew= V:
a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条具有相同权值的边,则可任意选取其中之一);
b.将v加入集合Vnew中,将<u, v>边加入集合Enew中; - 时间复杂度为O(n^2)
- 邻接矩阵实现
package 数据结构;
import java.util.Scanner;
public class Main {
static void MiniSpanTree_Prin(Graph map) {
int min, weights=0, i, j, k;
int[] saveVex = new int[map.vexnum];// 保存弧尾下标
int[] saveWeights = new int[map.vexnum];// 保存相关顶点间的权值
saveWeights[0] = 0;// 值为0表示该下标顶点加入生成树
saveVex[0] = 0;// 第一个顶点下标为0
for (i = 1; i < map.vexnum; i++) {// 遍历v0外所有顶点
saveWeights[i] = map.arc[0][i];// 将v0的每边权存入,没有直接相连的点之间边权为∞
saveVex[i] = 0;// 全部初始化为v0下标,即弧尾都是v0,弧头是各个顶点
}
for (i = 1; i < map.vexnum; i++) {// 遍历v0外所有顶点
min = Integer.MAX_VALUE;// 初始化最小权值为∞,因为可能有0权负权,所以设置不可能值为初始
j = 1;
k = 0;
while (j < map.vexnum) {
if (saveWeights[j] != 0 && saveWeights[j] < min) {
// 如果未加入生产树且权值小于min
min = saveWeights[j];// 让当前权成为最小权
k = j;// 将当前最小权下标存入k
}
j++;
}
//计入最后权值
weights+=min;
// 打印当前顶点的最小权边
System.out.printf("(%s,%s)", map.vexs[saveVex[k]], map.vexs[k]);
//将当前顶点加入生成树
saveWeights[k] = 0;
for (j = 1; j < map.vexnum; j++) {
if (saveWeights[j] != 0 && map.arc[k][j] < saveWeights[j]) {
//从vk出发到各顶点的权比从v0出发到各顶点的权小,且该顶点没有加入生成树
//即v0->vk->v < v0->v
saveWeights[j] = map.arc[k][j];//把较小边权存入
saveVex[j] = k;//对应弧尾改为vk
}
}
}
System.out.println();
System.out.print("最小权:"+weights);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入图的顶点数和边数");
int vexnum = sc.nextInt();
int arcnum = sc.nextInt();
Graph map = new Graph(vexnum, arcnum);
for (int i = 0; i < vexnum; i++) {
for (int j = 0; j < vexnum; j++) {
map.arc[i][j] = Integer.MAX_VALUE;
}
}
map.CreateMGraph();
MiniSpanTree_Prin(map);
}
}
1.2卡鲁斯卡尔(Kruskal)算法
-
基本思想
先构造一个只含 n 个顶点、而边集为空的子图,把子图中各个顶点看成各棵树上的根结点,之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,即把两棵树合成一棵树,反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直到森林中只有一棵树,也即子图中含有 n-1 条边为止。 -
步骤
1)新建图G,G中拥有原图中相同的节点,但没有边;
2)将原图中所有的边按权值从小到大排序;
3)从权值最小的边开始,如果这条边连接的两个节点于图G中不在同一个连通分量中,则添加这条边到图G中;
4)重复3,直至图G中所有的节点都在同一个连通分量中。 -
证明
1)这样的步骤保证了选取的每条边都是桥,因此图G构成一个树。
2)为什么这一定是最小生成树呢?关键还是步骤3中对边的选取。算法中总共选取了n-1条边,每条边在选取的当时,都是连接两个不同的连通分量的权值最小的边
3)要证明这条边一定属于最小生成树,可以用反证法:如果这条边不在最小生成树中,它连接的两个连通分量最终还是要连起来的,通过其他的连法,那么另一种连法与这条边一定构成了环,而环中一定有一条权值大于这条边的边,用这条边将其替换掉,图仍旧保持连通,但总权值减小了。也就是说,如果不选取这条边,最后构成的生成树的总权值一定不会是最小的。 -
时间复杂度为O(eloge)
-
边集数组实现
package 数据结构;
import java.util.Scanner;
public class Main {
//算法部分需要放在边集数组实现的图类中,因为用了内部类边类
public void MiniSpanTree_Krukal() {
int i, n, m, weights = 0;
edge[] e = sequence();// 将边集数组按权排序
int[] parent = new int[vexnum];// 判断边与边是否形成环路
for (i = 0; i < arcnum; i++) {//遍历每一条边
n = Find(parent, e[i].begin);
m = Find(parent, e[i].end);
if (n != m) {//不等则此边没有与现有生成树形成环路
parent[n] = m;//将此边的结尾顶点放入下标为起点的parent中,表示此顶点已经在生成树集合中
System.out.printf("(%s,%s)", vexs[e[i].begin], vexs[e[i].end]);
weights += e[i].weight;
}
}
System.out.println("最小权:" + weights);
}
public edge[] sequence() {//用冒泡排序法按权值排序
edge[] e = Arrays.copyOf(arc, arcnum);//不改变原边集
edge temp;
for (int i = 0; i < arcnum; i++) {
for (int j = i + 1; j < arcnum; j++) {
if (e[i].weight > e[j].weight) {
temp = e[j];
e[j] = e[i];
e[i] = temp;
}
}
}
return e;
}
private int Find(int[] parent, int f) {//查找连线顶点的尾部下标
while (parent[f] > 0) {
f = parent[f];
}
return f;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入图的顶点数和边数");
int vexnum = sc.nextInt();
int arcnum = sc.nextInt();
Graph map = new Graph(vexnum, arcnum);
map.CreateMGraph();
map.MiniSpanTree_Krukal();
}
}
2.最短路径
2.1迪杰斯特拉(Dijikstra)算法
2.2弗洛伊德(Floyd)算法
3.拓扑排序
4.关键路径
来源:CSDN
作者:miku的肯定
链接:https://blog.csdn.net/qq_44467578/article/details/104181362