一、普里姆(Prim)
1.1 算法思想
对于图中的若干个顶点和若干条边,优先选择加入到已连通图中花费代价最小的点,又称为加点法。最后结果:生成最小生成树,获得最小连通图。
1.2 图例效果如下
图片节选自ZGULZ
1.3 适用题型
1.4 代码
① 结构体声明
重点是想象出辅助数组的作用,以及这个过程中辅助数组需要进行的操作
Closedge是用来记录顶点连接到图中的信息,ajdvex为和图中的哪个顶点相连接,lowcost为连接的最小花费,即无向网中的边的权值。
typedef struct{
char vex[MVNum];
int arc[MVNum][MVNum];
int vexnum,arcnum;
}AMGraph;
typedef struct{
char ajdvex; //指向该点连接图中最短路径的点
int lowcost;
}Closedge;
② 创建无向网
int LocateVex(AMGraph G,char v){ //获取指定顶点的下角标
for(int i = 0;i < G.vexnum;i++){
if(G.vex[i] == v) return i;
}
return -1;
}
void CreateUDN(AMGraph &G){ //创建无向图图
char v1,v2;
int i,j,w;
cin >> G.vexnum >> G.arcnum;
for(i = 0;i < G.vexnum;i++){
cin >> G.vex[i];
}
for(i = 0;i < G.vexnum;i++){
for(int j = 0;j < G.vexnum;j++){
G.arc[i][j] = MAX_INT;
}
}
for(int k = 0;k < G.arcnum;k++){
cin >> v1 >> v2 >> w;
i = LocateVex(G,v1);
j = LocateVex(G,v2);
G.arc[i][j] = G.arc[j][i] = w;
}
}
③ Prim算法实现
Min()用于获取记录的辅助数组中需要花费最小的点,若该顶点已经连接到图中,则将lowcost = 0
int Min(AMGraph G){ //获取剩余顶点中连接到图中的最小代价顶点
int index = -1,min = MAX_INT;
for(int i = 0;i < G.vexnum;i++){
if(min > closedge[i].lowcost && closedge[i].lowcost != 0){
min = closedge[i].lowcost;
index = i;
}
}
return index;
}
void MinSpanTree_Prim(AMGraph G,char v){ //Prim算法实现
int k = LocateVex(G,v);
closedge[k].lowcost = 0;
cout << G.vex[k] << " " << closedge[k].lowcost<< endl;
for(int i = 0;i < G.vexnum;i++){
if(i != k){
closedge[i].ajdvex = G.vex[k];
closedge[i].lowcost = G.arc[k][i];
}
}
for(int i = 1;i < G.vexnum;i++){
k = Min(G);
cout << closedge[k].ajdvex << "->" << G.vex[k] << " " << closedge[k].lowcost << endl;
closedge[k].lowcost = 0;
for(int j = 0;j < G.vexnum;j++){
if(closedge[j].lowcost > G.arc[k][j]){
closedge[j].lowcost = G.arc[k][j];
closedge[j].ajdvex = G.vex[k];
}
}
}
}
④ 全部代码
#include<iostream>
using namespace std;
#define MVNum 100
#define MAX_INT 1000000007
typedef struct{
char vex[MVNum];
int arc[MVNum][MVNum];
int vexnum,arcnum;
}AMGraph;
typedef struct{
char ajdvex; //指向该点连接图中最短路径的点
int lowcost;
}Closedge;
Closedge closedge[MVNum];
int LocateVex(AMGraph G,char v){
for(int i = 0;i < G.vexnum;i++){
if(G.vex[i] == v) return i;
}
return -1;
}
void CreateUDN(AMGraph &G){
char v1,v2;
int i,j,w;
cin >> G.vexnum >> G.arcnum;
for(i = 0;i < G.vexnum;i++){
cin >> G.vex[i];
}
for(i = 0;i < G.vexnum;i++){
for(int j = 0;j < G.vexnum;j++){
G.arc[i][j] = MAX_INT;
}
}
for(int k = 0;k < G.arcnum;k++){
cin >> v1 >> v2 >> w;
i = LocateVex(G,v1);
j = LocateVex(G,v2);
G.arc[i][j] = G.arc[j][i] = w;
}
}
int Min(AMGraph G){
int index = -1,min = MAX_INT;
for(int i = 0;i < G.vexnum;i++){
if(min > closedge[i].lowcost && closedge[i].lowcost != 0){
min = closedge[i].lowcost;
index = i;
}
}
return index;
}
void MinSpanTree_Prim(AMGraph G,char v){
int k = LocateVex(G,v);
closedge[k].lowcost = 0;
cout << G.vex[k] << " " << closedge[k].lowcost<< endl;
for(int i = 0;i < G.vexnum;i++){
if(i != k){
closedge[i].ajdvex = G.vex[k];
closedge[i].lowcost = G.arc[k][i];
}
}
for(int i = 1;i < G.vexnum;i++){
k = Min(G);
cout << closedge[k].ajdvex << "->" << G.vex[k] << " " << closedge[k].lowcost << endl;
closedge[k].lowcost = 0;
for(int j = 0;j < G.vexnum;j++){
if(closedge[j].lowcost > G.arc[k][j]){
closedge[j].lowcost = G.arc[k][j];
closedge[j].ajdvex = G.vex[k];
}
}
}
}
void PrintG(AMGraph G){
for(int i = 0 ;i < G.vexnum;i++){
for(int j = 0;j < G.vexnum;j++){
cout << G.arc[i][j] << " ";
}
cout << endl;
}
}
int main(){
AMGraph G;
char v;
CreateUDN(G);
cout << "请输入开始的结点符号:";
cin >> v;
//PrintG(G);
MinSpanTree_Prim(G,v);
}
1.5 测试数据
6 10
1 2 3 4 5 6
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 4 5
3 5 6
3 6 4
4 6 2
5 6 6
1
二、克鲁斯卡尔(Kruskal)
2.1 算法思想
通过每次最小边相连,同时保证是最小连通子图,不断更新最小连通子图,不断让最小连通子图相连,从而获取最后的最小连通子图,即最小生成树。
这里让不同的最小连通连通子图相连,可以保证不会构成回路。该算法不好想的地方在于最小连通子图如何进行判别,该算法适用一个极为巧妙的方法,起初初始化VexSet数组,并赋不同的值,标记是不同的连通子图。
for(int i = 0;i < G.vexnum;i++){
VexSet[i] = i; //这里赋值下标,赋值什么都行,不同即可
}
如果顶点G.vex[0]和顶点G.vex[1]相连,则让二者的VexSet[0] == VexSet[1]来标记二者属于同一个最小连通子图。
2.2 图例实现过程
2.3 适用题型
最小生成树,花费最小代价连通全部顶点
例题如下:
2.4 代码
① 创建结构体
重点是想象出辅助数组的作用,以及这个过程中辅助数组需要进行的操作
typedef struct{
char vex[MVNum];
int arc[MVNum][MVNum];
int vexnum,arcnum;
}AMGraph;
typedef struct{
char Head; //起点
char Tail; //终点
int lowcost; //边的权值
}Edge;
int VexSet[MVNum]; //用来辨别是否同属一个最小连通子图
Edge edge[MVNum];
② 创建无向网
int LocateVex(AMGraph G,char v){
for(int i = 0;i < G.vexnum;i++){
if(G.vex[i] == v) return i;
}
return -1;
}
void CreateUDN(AMGraph &G){
char v1,v2;
int i,j,w;
cin >> G.vexnum >> G.arcnum;
for(int i = 0;i < G.vexnum;i++){
cin >> G.vex[i];
}
for(int i = 0;i < G.vexnum;i++){
for(int j = 0;j < G.vexnum;j++){
G.arc[i][j] = MAX_INT;
}
}
for(int k = 0;k < G.arcnum;k++){
cin >> v1 >> v2 >> w;
edge[k].Head = v1;
edge[k].Tail = v2;
edge[k].lowcost = w;
i = LocateVex(G,v1);
j = LocateVex(G,v2);
G.arc[i][j] = G.arc[j][i] = w;
}
}
③Kruskal算法实现
精髓在于对于VexSet[]的处置上,详细见注释
这里先进行边权值的排序,如果连接后还是最小连通图则连接,会构成回路则跳过该边,说明该边两边顶点已相连。
void MinSpanTree_Kruskal(AMGraph G){
int i,j,vs1,vs2;
int res = 0; //生成最小生成树的最小花费
sort(edge,edge+G.arcnum,cmp);
for(int i = 0;i < G.vexnum;i++){
VexSet[i] = i;
}
for(int k = 0;k < G.vexnum;k++){
i = LocateVex(G,edge[k].Head);
j = LocateVex(G,edge[k].Tail);
vs1 = VexSet[i];
vs2 = VexSet[j];
if(vs1 != vs2){ //判断是否从属同一个最小连通子图,不是一个图则进入
res += edge[k].lowcost;
for(int m = 0;m < G.vexnum;m++){ //让该图的所有顶点标记都改成要加入的图的标记
if(VexSet[m] == vs2) VexSet[m] = vs1;
}
}
}
cout << "最小花费为:" << res;
}
④ 全部代码
#include<iostream>
#include<algorithm>
using namespace std;
#define MVNum 100
#define MAX_INT 1000000007
//Kruskal适合稀疏边的最小生成树
typedef struct{
char vex[MVNum];
int arc[MVNum][MVNum];
int vexnum,arcnum;
}AMGraph;
typedef struct{
char Head;
char Tail;
int lowcost;
}Edge;
Edge edge[MVNum];
int VexSet[MVNum]; //用来判断最小连通量
bool cmp(Edge x,Edge y){
return x.lowcost < y.lowcost;
}
int LocateVex(AMGraph G,char v){
for(int i = 0;i < G.vexnum;i++){
if(G.vex[i] == v) return i;
}
return -1;
}
void CreateUDN(AMGraph &G){
int i,j,w;
char v1,v2;
cin >> G.vexnum >> G.arcnum;
for(i = 0;i < G.vexnum;i++){
G.vex[i] = i+'1';
}
for(i = 0;i < G.vexnum;i++){
for(j = 0;j < G.vexnum;j++){
G.arc[i][j] = MAX_INT;
}
}
for(int k = 0;k < G.arcnum;k++){
cin >> v1 >> v2 >> w;
i = LocateVex(G,v1);
j = LocateVex(G,v2);
G.arc[i][j] = G.arc[j][i] = w;
edge[k].Head = v1;
edge[k].Tail = v2;
edge[k].lowcost = w;
}
}
//克鲁斯卡尔算法
void MinSpanTree_Kruskal(AMGraph G){
int i,j,vs1,vs2;
int res = 0; //生成最小生成树的最小花费
sort(edge,edge+G.arcnum,cmp);
for(i = 0;i < G.vexnum;i++){
VexSet[i] = i; //初始化辅助数组
}
for(int k = 0;k < G.arcnum;k++){ //依次判断边是否进行连接,
i = LocateVex(G,edge[k].Head);
j = LocateVex(G,edge[k].Tail);
vs1 = VexSet[i];
vs2 = VexSet[j];
if(vs1 != vs2){ //若该边两边顶点已经连接,则该边不进行连接
res += edge[k].lowcost;
for(int m = 0;m < G.vexnum;m++){
if(VexSet[m] == vs2) VexSet[m] = vs1;
}
}
}
cout << "最小花费为:" << res;
}
int main(){
AMGraph G;
CreateUDN(G);
MinSpanTree_Kruskal(G);
}
2.5 测试数据
6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3
测试结果如下:
三、迪杰斯特拉(Dijkstra)
3.1 算法思想
不断更新距离某点的最短路径,每次加入距离指定顶点的距离最短的一个顶点,同时比较新加入的顶点作为中转顶点是否可以使得其他顶点距离指定顶点的距离更近,如果更近,则更新辅助数组中的距离数据
3.2 图例实现过程
3.3 适用题型
用于求最短路径 ,求指定顶点到剩余各顶点的最短路径。
3.4 代码
① 创建结构体
重点是想象出辅助数组的作用,以及这个过程中辅助数组需要进行的操作
需要用到三个辅助数组
S[]: 用来标记该顶点是否已加入
D[]: 用来记录距离指定顶点的最短距离
Path[]: 用来记录前一个顶点的位置
typedef struct{
char vex[MVNum];
int arc[MVNum][MVNum];
int vexnum,arcnum;
}AMGraph;
//辅助数组
bool S[MVNum];
int D[MVNum];
int Path[MVNum];
② 创建有向网
int LocateVex(AMGraph G,char v){
for(int i = 0; i < G.vexnum;i++){
if(G.vex[i] == v) return i;
}
return -1;
}
void CreateDN(AMGraph &G){
int i,j,w;
char v1,v2;
cin >> G.vexnum >> G.arcnum;
for(i = 0;i < G.vexnum;i++){
cin >> G.vex[i];
}
for(i = 0;i < G.vexnum;i++){
for(j = 0;j < G.vexnum;j++){
G.arc[i][j] = MAX_INT;
}
}
for(int k = 0;k < G.arcnum;k++){
cin >> v1 >> v2 >> w;
i = LocateVex(G,v1);
j = LocateVex(G,v2);
G.arc[i][j] = w;
}
}
③ Dijkstra算法实现
void ShortestPath_DIJ(AMGraph G,char v0){
int k = LocateVex(G,v0);
for(int i = 0;i < G.vexnum;i++){
S[i] = false;
D[i] = G.arc[k][i];
if(D[i] < MAX_INT) Path[i] = k;
else Path[i] = -1;
}
D[k] = 0;
S[k] = true;
for(int i = 1;i < G.vexnum;i++){
int min = MAX_INT;
for(int j = 0;j < G.vexnum;j++){
if(!S[j] && D[j] < min){
min = D[j];
k = j;
}
}
S[k] = true;
for(int j = 0;j < G.vexnum;j++){
if(!S[j] && D[j] > D[k]+G.arc[k][j]){
D[j] = D[k] + G.arc[k][j];
Path[j] = k;
}
}
}
}
④ 全部代码
#include<iostream>
using namespace std;
#define MVNum 100
#define MAX_INT 1000000007
typedef struct{
char vex[MVNum];
int arc[MVNum][MVNum];
int vexnum,arcnum;
}AMGraph;
//辅助数组
bool S[MVNum];
int D[MVNum];
int Path[MVNum];
int LocateVex(AMGraph G,char v){
for(int i = 0; i < G.vexnum;i++){
if(G.vex[i] == v) return i;
}
return -1;
}
void CreateDN(AMGraph &G){
int i,j,w;
char v1,v2;
cin >> G.vexnum >> G.arcnum;
for(i = 0;i < G.vexnum;i++){
cin >> G.vex[i];
}
for(i = 0;i < G.vexnum;i++){
for(j = 0;j < G.vexnum;j++){
G.arc[i][j] = MAX_INT;
}
}
for(int k = 0;k < G.arcnum;k++){
cin >> v1 >> v2 >> w;
i = LocateVex(G,v1);
j = LocateVex(G,v2);
G.arc[i][j] = w;
}
}
void ShortestPath_DIJ(AMGraph G,char v0){
int k = LocateVex(G,v0);
for(int i = 0;i < G.vexnum;i++){
S[i] = false;
D[i] = G.arc[k][i];
if(D[i] < MAX_INT) Path[i] = k;
else Path[i] = -1;
}
D[k] = 0;
S[k] = true;
for(int i = 1;i < G.vexnum;i++){
int min = MAX_INT;
for(int j = 0;j < G.vexnum;j++){
if(!S[j] && D[j] < min){
min = D[j];
k = j;
}
}
S[k] = true;
for(int j = 0;j < G.vexnum;j++){
if(!S[j] && D[j] > D[k]+G.arc[k][j]){
D[j] = D[k] + G.arc[k][j];
Path[j] = k;
}
}
}
}
void PrintShortestPath(AMGraph G){
for(int i = 0;i < G.vexnum;i++){
cout << G.vex[i] << "距离:" << D[i] << " 路径为:";
//输入最短路径的具体路径
int k = i;
int res[MVNum];
int len = 0;
res[len] = k;
while(Path[k] != -1){
k = Path[k];
len++;
res[len] = k;
}
while(len >= 0){
cout << G.vex[res[len]] << " ";
len--;
}
cout << endl;
}
}
int main(){
AMGraph G;
char v;
CreateDN(G);
cout << "请输入开始结点:" ;
cin >> v;
ShortestPath_DIJ(G,v);
PrintShortestPath(G);
}
3.5 测试数据
四、弗洛伊德(Floyd)
4.1 算法思想
起初各顶点只有简单的相连关系,然后进行n次遍历,依次选择某一个顶点作为中转点,如果该中转点的距离更近,则更新最短距离,同时记录该中转点。不断遍历中转点,使各距离逐渐逼近最短距离,使用了动态规划的思想实现。
4.2 图例实现过程
4.3 适用题型
适合任意顶点到任意顶点的最短距离
//迪杰斯特拉用于计算指定顶点到各顶点之间的最短距离
//弗洛伊德算法用于计算各顶点到各顶点之间的最短距离
图中任意两顶点之间的最短距离,相当于把迪杰斯特拉求的东西,按照每个顶点开始再求一边。但时间复杂度达到了O(n3)
例题如下:
4.4 代码
① 结构体和辅助数组
重点是想象出辅助数组的作用,以及这个过程中辅助数组需要进行的操作
//弗洛伊德算法用于计算各顶点到各顶点之间的最短距离
//迪杰斯特拉用于计算指定顶点到各顶点之间的最短距离
typedef struct{
char vex[MVNum];
int arc[MVNum][MVNum];
int vexnum,arcnum;
}AMGraph;
//辅助数组
int D[MVNum][MVNum];
int Path[MVNum][MVNum]; //记录中转点
② 创建有向网
int LocateVex(AMGraph G,char v){
for(int i = 0; i < G.vexnum;i++){
if(G.vex[i] == v) return i;
}
return -1;
}
void CreateDN(AMGraph &G){
int i,j,w;
char v1,v2;
cin >> G.vexnum >> G.arcnum;
for(i = 0;i < G.vexnum;i++){
cin >> G.vex[i];
}
for(i = 0;i < G.vexnum;i++){
for(j = 0;j < G.vexnum;j++){
G.arc[i][j] = MAX_INT;
}
}
for(int k = 0;k < G.arcnum;k++){
cin >> v1 >> v2 >> w;
i = LocateVex(G,v1);
j = LocateVex(G,v2);
G.arc[i][j] = w;
}
}
③ Floyd算法实现
void Floyd(AMGraph G){
int i,j;
for(i = 0;i < G.vexnum;i++){
for(j = 0;j < G.vexnum;j++){
D[i][j] = G.arc[i][j];
Path[i][j] = -1;
}
}
for(int k = 0;k < G.vexnum;k++){
for(i = 0;i < G.vexnum;i++){
for(j = 0;j < G.vexnum;j++){
if(D[i][j] > D[i][k]+G.arc[k][j]){
D[i][j] = D[i][k] + G.arc[k][j];
Path[i][j] = k;
}
}
}
}
}
④ 全部代码
#include<iostream>
using namespace std;
#define MVNum 100
#define MAX_INT 1000000007
//弗洛伊德算法用于计算各顶点到各顶点之间的最短距离
//迪杰斯特拉用于计算指定顶点到各顶点之间的最短距离
typedef struct{
char vex[MVNum];
int arc[MVNum][MVNum];
int vexnum,arcnum;
}AMGraph;
//辅助数组
int D[MVNum][MVNum];
int Path[MVNum][MVNum];//记录中转点
int LocateVex(AMGraph G,char v){
for(int i = 0;i < G.vexnum;i++){
if(G.vex[i] == v) return i;
}
return -1;
}
void CreateUDN(AMGraph &G){
int i,j,w;
char v1,v2;
for(i = 0;i < G.vexnum;i++){
G.vex[i] = i+'0';
}
for(i = 0;i < G.vexnum;i++){
for(j = 0;j < G.vexnum;j++){
G.arc[i][j] = MAX_INT;
}
}
for(int k = 0;k < G.arcnum;k++){
cin >> v1 >> v2 >> w;
i = LocateVex(G,v1);
j = LocateVex(G,v2);
G.arc[i][j] = G.arc[j][i] = w;
}
}
void Floyd(AMGraph G){
int i,j;
for(i = 0;i < G.vexnum;i++){
for(j = 0;j < G.vexnum;j++){
D[i][j] = G.arc[i][j];
Path[i][j] = -1;
}
}
for(int k = 0;k < G.vexnum;k++){
for(i = 0;i < G.vexnum;i++){
for(j = 0;j < G.vexnum;j++){
if(D[i][j] > D[i][k]+G.arc[k][j]){
D[i][j] = D[i][k] + G.arc[k][j];
Path[i][j] = k;
}
}
}
}
}
int Location(AMGraph G){
int min = MAX_INT;
int index = -1;
for(int i = 0;i < G.vexnum;i++){
int sum = 0;
for(int j = 0;j < G.vexnum;j++){
if(D[i][j] < MAX_INT){ //无向网时,这个不判断也行,题目保证了城市之间是连通的
sum += D[i][j];
}
}
if(min > sum){
min = sum;
index = i;
}
}
cout << G.vex[index] << endl;
}
int main(){
int n,m;
AMGraph G;
while(cin >> n >> m){
G.vexnum = n;
G.arcnum = m;
CreateUDN(G);
Floyd(G);
Location(G);
}
}
4.5 测试数据
6 5
0 1 1
0 2 1
0 3 1
0 4 1
0 5 1
4 5
0 1 1
0 2 5
1 2 2
1 3 4
2 3 1
e
测试结果如下: