浙大数据结构:08-图7 公路村村通 (30 分)Prim与Kruskal算法

08-图7 公路村村通 (30 分)

现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。

输入格式:
输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。

输出格式:
输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。

输入样例:
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
输出样例:
12

代码

使用的是Kruskal算法,原因是为了锻炼一下自己,顺便重新写一下最小堆还有复习一下相关的数据结构。测试结果出现了段错误,不明所以,我自己使用了浙大数据结构实验书上面的数据进行了测试,没有发现问题,不知道是用什么数据进行测试的

#include <iostream>
#define MaxSize 1001
using namespace std;
typedef int Vertex;
typedef int ElementType;
/*图的邻接表定义*/
typedef struct ListNode* List;
typedef struct ENode* Edge;
struct ENode {
	Vertex v1;
	Vertex v2;
	ElementType Weight;
};
struct ListNode {
	Vertex V;
	ElementType Weight;
	List Next;
	ListNode():Next(NULL){}
};

typedef List PtrToNode;
typedef struct VNode{
	PtrToNode FirstEdge;
}PtrArray[MaxSize];

typedef struct GraphNode* Graph;
struct GraphNode {
	int Nv;
	int Ne;
	PtrArray heads;
};


Graph CreatGraph(int N) {
	Graph G = new GraphNode;
	G->Nv = N;
	G->Ne = 0;
	for (int i = 0; i <= N; i++) {
		G->heads[i].FirstEdge = NULL;
	}
	return G;
}

void InsertEdge(Graph G) {
	Vertex v1, v2;
	ElementType Weight;
	cin >> v1 >> v2 >> Weight;
	List tmp = new ListNode;
	tmp->Next = G->heads[v1].FirstEdge;
	tmp->V = v2;
	tmp->Weight = Weight;
	G->heads[v1].FirstEdge = tmp;

	tmp = new ListNode;
	tmp->Next = G->heads[v2].FirstEdge;
	tmp->V = v1;
	tmp->Weight = Weight;
	G->heads[v2].FirstEdge = tmp;
	G->Ne++;
	
}
/*最小堆的定义*/
typedef Vertex SetType[MaxSize];//用于记录每个点所属的集合
typedef Vertex SetName;
Edge Eset;/*定义一个全局变量Eset作为边集合,是一个最小堆*/
void PercDown(Edge Eset, int i, int N);
void InitializeEset(Graph G,Edge Eset) {
	/*用图来初始化边数组*/
	List tmp;
	int Ecount = 1;//边数组也从1开始计数,方便最小堆排列
	for (int i = 1; i <= G->Nv; i++) {
		tmp = G->heads[i].FirstEdge;
		while (tmp != NULL) {
			if (tmp->V < i) {
				Eset[Ecount].v1 = i;
				Eset[Ecount].v2 = tmp->V;
				Eset[Ecount].Weight = tmp->Weight;
				Ecount++;
			}
			tmp = tmp->Next;
		}
	}

	/*调整为最小堆*/
	for (int i = Ecount / 2; i > 0; i--) {
		PercDown(Eset, i, Ecount);
	}

}
void PercDown(Edge Eset, int i, int N) {
	/*对Eset边数组,从第i个顶点进行向下整堆,数组的大小为N,数组区间为[1,N]*/
	ENode Etmp = Eset[i];
	int child, parent;
	for (parent = i; parent*2 <= N;parent=child) {
		child = 2 * parent;
		if (child!=N&&Eset[child].Weight > Eset[child + 1].Weight) { //找更小的
			child++; 
		}
		if (Eset[child].Weight < Etmp.Weight) {
			Eset[parent] = Eset[child];
		}
		else break;
	}
	Eset[parent] = Etmp;
}

ENode PopEdge(Graph G,Edge Eset, int N) {
	/*从Eset中弹出一条最短边*/
	ENode E = Eset[1];
	Eset[1] = Eset[N];
	PercDown(Eset, 1, N - 1);
	return E;
}

Vertex FindRoot(SetType S, Vertex V) {
	/*返回V所在的根节点的集合,使用路径压缩*/
	if (S[V] < 0)
		return V;
	else
		return S[V] = FindRoot(S, S[V]);
}

int IsCycle(SetType V_Set, ENode E) {
	/*检查收录边E是否会在V_Set中构成回路,是回路返回1,不是回路返回0*/
	Vertex v1 = E.v1;
	Vertex v2 = E.v2;
	Vertex root1, root2;
	root1 = FindRoot(V_Set, v1);
	root2 = FindRoot(V_Set, v2);
	if (root1 == root2) {
		return 1;//v1,v2在同一棵树里面,再联通会构成回路
	}
	else {
		return 0;
	}
}

void Union(SetType V_Set, Vertex v1, Vertex v2) {
	V_Set[FindRoot(V_Set,v1)] = FindRoot(V_Set, v2);
}

int Kruskal(Graph G) {
	int TotalWeight;
	int EIsCount, CountPopEdge;
	SetType VSet;//VSet是一个最小生成树集合
	for (int i = 1; i <= G->Nv; i++) {/*初始化顶点集合VSet*/
		VSet[i] = -1;
	}
	TotalWeight = 0;
	EIsCount = 0;/*用于记录收录的边*/
	CountPopEdge = 0;/*用于记录弹出的边,弹出的边不一定都会被收录*/
	ENode Etmp;
	while (EIsCount < G->Nv - 1) {
		if (CountPopEdge >= G->Ne)break;
		Etmp = PopEdge(G, Eset,G->Ne-CountPopEdge);
		CountPopEdge++;
		if (IsCycle(VSet, Etmp) ==0) {
			Union(VSet, Etmp.v1,Etmp.v2);//不在同一棵树里面,连接v1,v2
			TotalWeight = TotalWeight + Etmp.Weight;
			EIsCount++;
		}
	}
	if (EIsCount < G->Nv - 1) {//没有能够生成最小生成树,边不够,不连通
		TotalWeight = -1;
	}
	return TotalWeight;
}



int main() {
	int N, M;
	cin >> N >> M;
	Graph G = CreatGraph(N);
	for (int i = 0; i < M; i++) {
		InsertEdge(G);
	}
	Eset = new ENode[M+1];
	InitializeEset(G, Eset);
	cout<<Kruskal(G);
	return 0;
}

测试结果

在这里插入图片描述

测试点

在这里插入图片描述
第一测试点
在这里插入图片描述
第2测试点
在这里插入图片描述

Prim算法代码

主要是有的地方1-N的时候写循环是要for(i=1;i<=Nv;i++),有的时候一重循环注意到了,二重循环没有注意到,就出错了。另外果然还是初始化什么的用一个函数来写会顺眼很多,代码里面写在了main()里面进行初始化,虽然写的时候会快一点,但是出错了查找还是比较慢的。

另外,个人认为prim算法比kruskal好写,而且使用邻接矩阵即使是稀疏图,速度和内存也还好,要求不高的情况下用邻接矩阵建图,使用ide来debug的效率会高很多!

#include<iostream>
#define MaxSize 1001
#define Inf 65535
#define Error -1
using namespace std;
typedef struct GraphNode* Graph;
typedef int Vertex;
struct GraphNode {
	int Nv;
	int Ne;
	int Weight[MaxSize][MaxSize];
};
void InsertEdge(Graph G,int M) {
	/*向G中插入M条边*/
	Vertex v1, v2;
	int weight;
	auto W = G->Weight;
	for (int i = 0; i < M; i++) {
		cin >> v1 >> v2 >> weight;
		W[v1][v2] = weight;//序号从1-N
		W[v2][v1] = weight;
		G->Ne++;
	}
}
int dist[MaxSize];
int collect[MaxSize];//这里其实不需要collect,因为dist[i]==0就说明已经收录进集合了
int path[MaxSize];

Vertex FindMinDist(int Dist[], int Collect[],int Nv) {
	/*搜寻未收录顶点中的最小边,由于是未收录的顶点,因此不可能形成回路,但是dist[]的定义和dijkstra不同,要注意这点!*/
	int MinDist = Inf;
	Vertex V;
	for (int i = 1; i <= Nv; i++) {
		if (Collect[i] != 1 && dist[i]<MinDist) {
			V = i;
			MinDist = dist[i];
		}
	}
	if (MinDist < Inf)return V;
	else
		return Error;

}

int Prim(Graph G) {
	Vertex S = 1;//这里我打算做个特殊处理,假设从S点开始建立最小生成树,S可以改变,而不是固定从0开始
	/*初始化dist[]与collect[],path[]*/
	for (int i = 1; i <= G->Nv; i++) {
		dist[i] = Inf;
		collect[i] = -1;
		path[i] = -1;
	}
	path[S] = -1;
	dist[S] = 0;
	collect[S] = 1;//将S点收入
	
	/*对S邻接点进行初始化*/
	for (int i = 1; i <= G->Nv; i++) {
		if (G->Weight[S][i] != Inf) {
			dist[i] = G->Weight[S][i];
			path[i] = S;
		}
	}
	int TotalCount = 0;
	Vertex V;
	int Vcount = 1;//prim算法收录的点个数是NV个,收录边的条数是NV-1条
	/*执行prim算法*/
	while (1) {
		V = FindMinDist(dist,collect,G->Nv);//寻找未收录点中的最小边
		if (V == Error)break;
		collect[V] = 1;//将V收入集合,从此不再计算
		Vcount++;//收入的节点数加1
		TotalCount = TotalCount + dist[V];//添加边长
		/*更新V周围的点*/
		for (int i = 1; i <= G->Nv; i++) {
			if (G->Weight[V][i] != Inf&&collect[i] != 1) {
				/*未收录的邻接点中如果因为收录了dist[]使得边长更小了,那么就更新dist和path*/
				if (G->Weight[V][i] < dist[i]) {
					dist[i] = G->Weight[V][i];
					path[i] = V;
				}
			}
		}
	}
	if (Vcount != G->Nv)
		TotalCount = -1;
	return TotalCount;
}

int main() {
	int N, M;
	cin >> N >> M;
	Vertex v1, v2;
	int weight;
	Graph G = new GraphNode;
	G->Nv = N;
	G->Ne = 0;
	for (int i = 0; i <= N; i++) {
		for (int j = 0; j <= N; j++) {
			G->Weight[i][j] = Inf;
		}
	}
	InsertEdge(G,M);
	/*以上为简略的使用邻接矩阵建图*/
	cout<<Prim(G);


	return 0;
}

测试结果2

在这里插入图片描述

总结:

个人认为prim算法比kruskal好写,而且使用邻接矩阵即使是稀疏图,速度和内存也还好,要求不高的情况下用邻接矩阵建图,使用ide来debug的效率会高很多,因为可以对于图来说可以直接看到里面的元素,很方便。

另外,上面的prim算法我特意设置了一个collect数组来记录点被收进集合,其实可以直接dist[i]=0,就可以表示点被收入集合了,判断是否收入集合直接判断dist[i]=?0即可。

还有一个就是我故意设置了一个从S点开始进行最小生成树生成,后来想了想觉得没有必要。比如说我令S=5,那么就是从第5个点进行生成树生成,但是可能测试的数据只有4个点,那么就没有什么意义了。所以一般来讲都是从默认的第1个点进行最小生成树生成。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值