图的应用(最小生成树、最短路径、拓扑排序、关键路径)

1.最小生成树

一个连通图的生成树是指一个极小连通子图,它含有图中的全部顶点,但只有足以构成一棵树的 n-1 条边。在一个连通网的所有生成树中,各边代价之和最小的那棵生成树称为该连通网 最小代价生成树(MST),简称为 最小生成树。利用普里姆(Prim)算法和克鲁斯卡尔(Kruscal)算法可以生成一个连通网的最小代价生成树,它们都是基于贪心算法策略。

1.1. 普里姆(Prim)算法

(1).基本步骤

假设N=(V,{E})是连通网,TE为最小代价生成树中边的集合。
① 初始U={u0}(u0∈V),TE=空集;
② 在所有u∈U,v∈V-U的边中选一条代价最小的边(u0,v0)并入集合TE,同时将v0并入U;
③ 重复②,直到U=V为止,此时必然存在n-1条边。

通俗点说就是:从一个顶点出发,在保证不形成回路的前提下,每找到并添加一条最短的边,就把当前形成的连通分量当做一个整体或者一个点看待,然后重复“找最短的边并添加”的操作。

这里有兄弟问过我为什么不需要判断环,哥们,咱动动脑子想想,形成环要什么条件?这里是两个集合,要形成环,两个顶点必须在一个集合里面,Prim算法显然不是的。

示例:在这里插入图片描述

(2).代码实现+注释

#include<iostream>
#include<vector>
using namespace std;
#define inf 0x7fffffff

typedef char VertexType;//图的顶点类型
typedef int ArcType;//边的权值类型

typedef struct {
	vector<VertexType> vex;//顶点表
	vector<vector<ArcType>> arc;//邻接矩阵 边表
	int vexNum, arcNum;//当前顶点数 边数
}MGraph;

//构造无向网:无向图权值为0或1即可
//			  有向网只赋值G[i][j]
void CreateUDG(MGraph& G) {
	cin >> G.vexNum >> G.arcNum;//图的顶点数和边数

	//顶点信息
	G.vex.resize(G.vexNum);
	for (int i = 0; i < G.vexNum; i++) cin >> G.vex[i];

	//初始化边信息
	vector<vector<ArcType>> arc(G.vexNum, vector<ArcType>(G.vexNum, inf));//赋值为无穷大
	G.arc = arc;

	//边信息
	VertexType vex1, vex2;
	int weight;
	for (int k = 0; k < G.arcNum; k++) {
		cin >> vex1 >> vex2 >> weight;//两顶点 权值
		int i = find(G.vex.begin(), G.vex.end(), vex1) - G.vex.begin();//vex1 vex2在图中的位置
		int j = find(G.vex.begin(), G.vex.end(), vex2) - G.vex.begin();
		G.arc[i][j] = G.arc[j][i] = weight;//建立对称弧
	}

}

//Prim算法生成最小生成树
//时间复杂度:O(n*n)即O(v*v)
void MiniSpanTree_Prim(MGraph G){
	int k, n = G.vexNum-1, mincost;//k用来记录下标 n用来记录循环遍历 mincost记录最小权值
	//adjvex保存相关顶点下标 这里从v0开始 即下标零开始 解释:adjvex[i] = j——> 第i个顶点与第j个顶点的弧 这里首先初始化所有点与顶点0的弧
	vector<int> adjvex(G.vexNum, 0);
	//lowcost保存顶点间边的权值 这里从v0开始 即下标零开始 初始化arc[0](将v0顶点与之组成的边的权值存入数组)lowcost[i] = j, adjvex[i] = k 
	//即顶点i和顶点k之间弧权值为j
	//就是保存已加入树的点 距离此点的最小权值
	vector<int> lowcost(G.arc[0]);
	//初始化第一个权值为0,即v0加入生成树
	//lowcost的值为0,在这里就是此下标的顶点已经加入生成树,也相当于一个标记
	lowcost[0] = 0;
	//这里也可以写一个标记数组标记节点是否已经加入生成树中
	//如vector<int> isJoin(G.vexNum, false);
	while (n--) {
		mincost = inf;//初始化最小权值为inf(无穷大)
		for (int i = 1; i < G.vexNum; i++) {
			if (lowcost[i]!=0&&lowcost[i] < mincost) {//如果权值不为0且权值小于mincost 找除了已经存入树的顶点的最小权值
				mincost = lowcost[i];
				k = i;//记录当前下标
			}
		}
        cout<<k<<" ";
		cout << G.vex[adjvex[k]] << " " << G.vex[k] << endl;//打印当前顶点边中权值最小边的两顶点,即新加入生成树的一条边
		lowcost[k] = 0;//将当前顶点权值设置为0 表示此顶点已经加入生成树

		//再次循环遍历,找到未加入的节点以及与下标为k的结点相连的结点的权值与前面的比较,更小则进行覆盖
		for (int i = 1; i < G.vexNum; i++) {//若下标为k顶点各边权值小于此前这些顶点未被加入生成树的边权值则覆盖原本lowcost并且建立弧关系
			//lowcost[i] = j, adjvex[i] = k 
			//即顶点i和顶点k之间弧权值为j
			//就是保存已加入树的点 距离此点的最小权值
			if (lowcost[i] != 0&&G.arc[k][i] < lowcost[i]) {//这里lowcost[i]!=0没有实际意义 我这里加上就是提醒大家这里第i个顶点已经被加入 不会形成环 它是往外面找的
				lowcost[i] = G.arc[k][i];//将较小权值覆盖上一次权值 存入lowcost
				adjvex[i] = k;//弧建立关系 即下标为k的顶点与顶点i联系
			}
		}

	}
}

int main() {
	MGraph G;
	CreateUDG(G);
	MiniSpanTree_Prim(G);
	return 0;
}
6 10
A B C D E F
A B 6
C D 6
B C 3
D E 2
A E 5
A F 1
B F 5
C F 6
D F 4
E F 5
A F
F D
D E
F B
B C

1.2.克鲁斯卡尔(Kruscal)算法

(1).基本步骤

假设N=(V,{E})是连通网,将N中的边按权值从小到大的顺序排列。
① 将n个顶点看成n个集合。
② 按权值从小到大的顺序选择边,所选边应满足两个顶点不在同一个顶点集合内,将该边放到生成树边的集合中,同时将该边的两个顶点所在的顶点集合合并。
③ 重复②直到所有的顶点都在同一顶点集合内。
示例:
在这里插入图片描述

(2).代码实现+注释

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

typedef char VexType;

//边存储结构
typedef struct Edge {
	int begin;//起点序号
	int end;//尾点序号
	int weight;//权值
}Edge;

//图存储结构
typedef struct EdgeGraph {
	vector<VexType> vex;//顶点信息
	vector<Edge> arc;//边信息
	int vexnum, arcnum;//顶点数 边数

}EdgrGraph;

//权值升序
bool cmp(Edge e1,Edge e2) {
	return e1.weight < e2.weight;
}

//创建网
void Create_EdgeGraph(EdgeGraph& G) {
	cin >> G.vexnum >> G.arcnum;//输入顶点数和边数
	G.vex.resize(G.vexnum);//初始化顶点数组大小
	G.arc.resize(G.arcnum);//初始化边数组大小
	for (int k = 0; k < G.vexnum; k++) {
		cin >> G.vex[k];//输入顶点信息
	}
	VexType vex1, vex2;//两顶点
	int weight;//权值
	for (int k = 0; k < G.arcnum; k++) {//保存边信息
		cin >> vex1 >> vex2 >> weight;//输入边信息:两顶点 + 权值
		G.arc[k].begin = find(G.vex.begin(), G.vex.end(), vex1) - G.vex.begin();
		G.arc[k].end = find(G.vex.begin(), G.vex.end(), vex2) - G.vex.begin();
		G.arc[k].weight = weight;
	}
}

//Kruskar算法生成最小生成树
void MiniSpanTree_Kruskal(EdgeGraph G) {
	sort(G.arc.begin(), G.arc.end(), cmp);//按照权值升序
	vector<bool> vexset(G.vexnum, false);//节点是否已经存入树中
	for (int k = 0; k < G.arcnum; k++) {
		int i = G.arc[k].begin;
		int j = G.arc[k].end;
		if (!(vexset[i]&&vexset[j])) {//两顶点不在同一集合 不会形成环
			cout << G.vex[i] << " " << G.vex[j] << endl;
			vexset[i] = vexset[j] = true;
		}
	}
}

int main() {
	EdgeGraph G;
	Create_EdgeGraph(G);
	MiniSpanTree_Kruskal(G);
	return 0;
}
6 10
A B C D E F
A B 16
B C 5
B D 6
C D 6
D E 18
A E 19
A F 21
D F 14
E F 33
B F 11
B C
B D
B F
A B
D E

2.最短路径

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值