图论

南笙酒味 提交于 2020-02-05 19:02:37

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.关键路径

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!