数据结构与算法实验9 实现无向连通图的最小生成树

1.上机名称

实现无向连通图的最小生成树

2.上机要求

(1)编写程序实现求带权连通图最小生成树的Prim算法。
(2)编写程序实现求带权连通图最小生成树的Kruskal算法。

3.上机环境

visual studio 2022 Windows11 家庭版 64位操作系统

4.程序清单(写明运行结果及结果分析)

4.1 程序清单

4.1.1 头文件 Graph.h 内容如下:

#pragma once

#include<iostream>
#include<queue>
//按照实验要求,本次实验构建的是无向图,为后续实验方便,引进图的种类标志,尝试将有向版本、网的版本统一起来。
enum GraphKind{//枚举类型,无向图,无向网,有向图,有向网
	UN_GRAPH,UN_NET,_GRAPH,_NET
};

typedef char Data;	//图的顶点里的数据元素
typedef int flag;	    //用于标注是否被遍历

typedef struct NeighbourNode {//邻接表的一个成员
	int id;			//邻接表里的下标
	int weight;		//权重,默认为0
}NBnode, * pNBnode;

typedef struct GNode {//定义图的顶点元素结构
	Data data;				//数据成员
	pNBnode* LNeighbours;	//左邻(入)接表
	pNBnode* RNeighbours;   //右邻(出)接表
	flag is_visited;		       //标注是否被遍历
	flag fatherid;			   //标注上一级节点id,用于Kruskal算法辅助
	int LN_cnt;			   //左邻数
	int RN_cnt;			   //右邻数
}Gnode, * pGnode;

typedef struct ARC {//一个弧的抽象数据类型
	pGnode left;	//起点
	int leftid;		
	pGnode right;	//终点
	int rightid;
	int weight;
}Arc, * pArc;

class Graph {
public:
	//创建一个顶点数为vex_num的图,图的类型为Kind
	Graph(int vex_num = 0, int Kind = 0);
	//通过邻接矩阵创建图	
	Graph(int** array_for_summon, int size, int Kind);
	~Graph();								//析构函数
	void insert(Data data);					//插入顶点
	void setdata(int id, Data data);		    //设置顶点数据
	void link(int id1,int id2,int weight = 1);	    //使两个顶点之间产生边关联,weight == 0 表示无关联。
	void CreateArray();						//通过传入的邻接矩阵生成图,并对矩阵的合法性进行检测
	void printNBS(int id);					//打印id顶点的邻接表
	void printARR();						//打印图的邻接矩阵
	void printArc();						//打印所有边
	void DFS(int firstid = 0);				//从下标为firstid的顶点深度优先遍历
	void BFS(int firstid = 0);				//从下标为firstid的顶点广度优先遍历
	void MSTPrim(int firstid = 0);			//使用Prim算法求最小生成树
	void MSTKruskal(int firstid = 0);		    //使用kruskal算法求最小生成树
	void MergeArcWeigt(int low ,int high);    //对带权边进行从小到大的排序,参数为0,arc_num
	const int get_vex_num();				//返回顶点数
	const int get_arc_num();					//返回边数,无向图、网返回值是边的两倍,在函数调用过程中自洽
protected:
	void makeneighbour(int id1, int id2, int weight = 1);//造邻居
	void SetFlag();					//初始化两个flag
	void DFSearch(int firstid = 0);		//对一个顶点的DFS
	void BFSearch(int firstid = 0);        //对一个顶点的DFS
	void swap(int id1, int id2);			//在排序中对两个边进行互换
	int fa(int id);						//返回代表元素,用于kruskal算法
	int vex_num;			//顶点数量
	int arc_num;			//弧数量
	int** GArray;			//邻接矩阵
	int GKind;			//图的类型标记
	pGnode* vex;			//存放顶点的顺序表
	pArc* arc;			//存放弧的顺序表
};

4.1.2 实现文件 Graph.cpp 内容如下:

#include "Graph.h"
Graph::Graph(int vex_num , int Kind ){
	this->vex_num = vex_num;
	vex = new pGnode[vex_num];
	for (int i = 0; i < vex_num; i++) {
		vex[i] = new Gnode;
	}
	for (int i = 0; i < vex_num; i++) {
		vex[i]->LN_cnt = 0;
		vex[i]->RN_cnt = 0;
		vex[i]->LNeighbours = new pNBnode;
		vex[i]->RNeighbours = new pNBnode;
		vex[i]->is_visited = 0;
	}
	GArray = new int* [vex_num]; for (int i = 0; i < vex_num; i++)GArray[i] = new int[vex_num];
	for (int i = 0; i < vex_num; i++)for (int j = 0; j < vex_num; j++) GArray[i][j] = 0;
	GKind = Kind;
	arc_num = 0;
	arc = new pArc;
}

Graph::Graph(int** array_for_summon, int size, int Kind){   
	vex_num = size;
	arc_num = 0;
	vex = new pGnode[vex_num];
	for (int i = 0; i < vex_num; i++) {
		vex[i] = new Gnode;
	}
	for (int i = 0; i < vex_num; i++) {
		vex[i]->LN_cnt = 0;
		vex[i]->RN_cnt = 0;
		vex[i]->LNeighbours = new pNBnode;
		vex[i]->RNeighbours = new pNBnode;
		vex[i]->is_visited = 0;
	}
	GKind = Kind;
	GArray = new int*[size];for (int i = 0; i < size; i++) GArray[i] = new int[size];
	for (int i = 0; i < size; i++) for (int j = 0; j < size; j++) GArray[i][j] = array_for_summon[i][j];
	CreateArray();
}

Graph::~Graph(){
	for (int i = 0; i < vex_num; i++) delete[]GArray[i];
	delete[]GArray;
	for (int i = 0; i < vex_num; i++) {
		delete[]vex[i]->LNeighbours;
		delete[]vex[i]->RNeighbours;
		vex[i]->LN_cnt = 0;
		vex[i]->RN_cnt = 0;
		vex[i]->is_visited = 0;
		vex[i]->data = 0;
	}
	delete[]vex;
	vex_num = 0;
	std::cout << "内存释放完毕!" << std::endl;
}

void Graph::insert(Data data){
	++vex_num;
	int** tmp = new int* [vex_num]; for (int i = 0; i < vex_num; i++) tmp[i] = new int[vex_num];
	for (int i = 0; i < vex_num; i++) for (int j = 0; j < vex_num; j++) {
		if (i == vex_num - 1 || j == vex_num - 1) tmp[i][j] = 0;
		else tmp[i][j] = GArray[i][j];
	}
	if (vex_num != 1) {	//不要释放空
		for (int i = 0; i < vex_num - 1; i++)delete[] GArray[i];
		delete []GArray;
	}
	vex[vex_num - 1]->data = data;
	std::cout << "new vertex ID: " << vex_num - 1 << "(start from zero)" << std::endl;
	GArray = tmp;
}

void Graph::setdata(int id, Data data){
	vex[id]->data = data;
}

void Graph::link(int id1, int id2, int weight ){
	switch (GKind){
	case UN_GRAPH: {//无向图
		if (id1 == id2) makeneighbour(id1, id2);
		else{
			makeneighbour(id1, id2);
			makeneighbour(id2, id1);
		}
		break;
	}
	case UN_NET: {//无向网
		if(id1==id2) makeneighbour(id1, id2, weight);
		else {
			makeneighbour(id1, id2, weight);
			makeneighbour(id2, id1, weight);
		}
		break;
	}
	case _GRAPH: {//有向图
		makeneighbour(id1, id2);
		break;
	}
	case _NET: {//有向网
		makeneighbour(id1, id2, weight);
		break;
	}
	default:std::cout << "Link failed!" << std::endl; break;
	}
}

void Graph::CreateArray(){
	switch (GKind)
	{
	case UN_GRAPH:
	case UN_NET: {
		for (int i = 0; i < vex_num; i++) {
			for (int j = 0; j <= i; j++) {
				if (GArray[i][j] != GArray[j][i]) {
					std::cout << "传入的矩阵作为无向图/网不合法!输入Y/N选择兼容或退出:" << std::endl;
					char c = getchar(); getchar();
					switch (c) {
					default:exit(-1);
					case 'y':case 'Y': {
						link(i, j, GArray[i][j] > GArray[j][i] ? GArray[i][j] : GArray[j][i]);
						continue;
					}
					}
				}
				if (GArray[i][j] != 0 ) link(i, j, GArray[i][j]);
			}
		}
		break;
	}
	case _GRAPH:
	case _NET: {
		for (int i = 0; i < vex_num; i++) {
			for (int j = 0; j < vex_num; j++) {
				if (GArray[i][j] != 0)link(i, j, GArray[i][j]);
			}
		}
		break;
	}
	default: std::cout << "Summon From Array Failed!" << std::endl;
	}
}

void Graph::makeneighbour(int id1, int id2, int weight ) {
	//id1 出度+1。
	pNBnode* fresh = new pNBnode[vex[id1]->RN_cnt + 1];
	for (int i = 0; i < vex[id1]->RN_cnt + 1; i++) fresh[i] = new NBnode;
	for (int i = 0; i < vex[id1]->RN_cnt; i++) {
		fresh[i]->id = vex[id1]->RNeighbours[i]->id;
		fresh[i]->weight = vex[id1]->RNeighbours[i]->weight;
	}
	fresh[vex[id1]->RN_cnt]->id = id2;
	fresh[vex[id1]->RN_cnt]->weight = weight;
	for (int i = 0; i < vex[id1]->RN_cnt; i++) delete[] vex[id1]->RNeighbours[i];
	delete[] vex[id1]->RNeighbours;
	vex[id1]->RNeighbours = fresh;
	
	//id2 入度+1。
	pNBnode* fresh2 = new pNBnode[vex[id2]->LN_cnt + 1];
	for (int i = 0; i < vex[id2]->LN_cnt + 1; i++) fresh2[i] = new NBnode;
	for (int i = 0; i < vex[id2]->LN_cnt; i++) {
		fresh2[i]->id = vex[id2]->LNeighbours[i]->id;
		fresh2[i]->weight = vex[id2]->LNeighbours[i]->weight;
	}
	fresh2[vex[id2]->LN_cnt]->id = id1;
	fresh2[vex[id2]->LN_cnt]->weight = weight;
	for (int i = 0; i < vex[id2]->LN_cnt; i++) delete[] vex[id2]->LNeighbours[i];
	delete[] vex[id2]->LNeighbours;
	vex[id2]->LNeighbours = fresh2;
	
	//弧段添加
	pArc* fresh3 = new pArc[arc_num + 1];
	for (int i = 0; i < arc_num + 1; i++) {
		fresh3[i] = new Arc;
		fresh3[i]->right = new Gnode;
		fresh3[i]->left = new Gnode;
	}
	for (int i = 0; i < arc_num; i++) fresh3[i] = arc[i];
	fresh3[arc_num]->left = vex[id1];
	fresh3[arc_num]->right = vex[id2];
	fresh3[arc_num]->weight = weight;
	fresh3[arc_num]->leftid = id1;
	fresh3[arc_num]->rightid = id2;
	delete[] arc;
	arc = fresh3;

	//别忘了对邻接矩阵和用于记录的数据进行修改
	GArray[id1][id2] = weight;
	vex[id1]->RN_cnt++;
	vex[id2]->LN_cnt++;
	arc_num++;
}

void Graph::printNBS(int id){
	pGnode obj = vex[id];
	switch (GKind)
	{
	case UN_GRAPH: {
		std::cout << "顶点ID:" << id << "的邻居ID:" << std::endl;
		if (obj->RN_cnt != 0)
			for (int i = 0; i < obj->RN_cnt; i++)
				std::cout << obj->RNeighbours[i]->id << " ";
		std::cout << std::endl;
		break;
	}
	case UN_NET: {
		if (obj->LN_cnt != 0)
			std::cout << "顶点ID:" << id << "的邻居ID[权重]:" << std::endl;
		for (int i = 0; i < obj->LN_cnt; i++)
			std::cout << obj->LNeighbours[i]->id << "[" << obj->LNeighbours[i]->weight << "]" << " ";
		std::cout << std::endl;
		break;
	}
	case _GRAPH: {
		std::cout << "顶点ID:" << id << std::endl;
		if (obj->LN_cnt != 0) {
			std::cout << "左邻ID:" << std::endl;
			for (int i = 0; i < obj->LN_cnt; i++)
				std::cout << obj->LNeighbours[i]->id << " ";
			std::cout << std::endl;
		}
		if (obj->RN_cnt != 0) {
			std::cout<< "\n右邻ID:" << std::endl;
			for (int i = 0; i < obj->RN_cnt; i++)
				std::cout << obj->RNeighbours[i]->id << " ";
			std::cout << std::endl;
		}
		break;
	}
	case _NET: {
		std::cout << "顶点ID:" << id << std::endl;
		if (obj->LN_cnt != 0) {
			std::cout << "左邻ID[权重]:" << std::endl;
			for (int i = 0; i < obj->LN_cnt; i++)
				std::cout << obj->LNeighbours[i]->id <<
				"[" << obj->LNeighbours[i]->weight << "]" << " ";
			std::cout << std::endl;
		}
		if (obj->RN_cnt != 0) {
			std::cout << "右邻ID[权重]:" << std::endl;
			for (int i = 0; i < obj->RN_cnt; i++)
				std::cout << obj->RNeighbours[i]->id <<
				"[" << obj->RNeighbours[i]->weight << "]" << " ";
			std::cout << std::endl;
		}
		break;
	}
	default:
		std::cout << "Find Neighbours Error!" << std::endl;
	}
}

void Graph::printARR(){
	std::cout << "图的邻接矩阵:\n";
	for (int i = 0; i < vex_num; i++){
		for (int j = 0; j < vex_num; j++) {
			std::cout << GArray[i][j] << " ";
		}
		std::cout << std::endl;
	}
}
void Graph::printArc(){
	switch (GKind) {
	case UN_GRAPH:
	case UN_NET: {
		for (int i = 0; i < arc_num; i+=2) {
			std::cout << "(" << arc[i]->left->data << " " << arc[i]->weight << " " << arc[i]->right->data << ")" << std::endl;
		}
		break;
	}
	case _GRAPH:
	case _NET: {
		for (int i = 0; i < arc_num; i ++) {
			std::cout << "<" << arc[i]->left->data << " " << arc[i]->weight << " " << arc[i]->right->data << ">" << std::endl;
		}
		break;
	}
	default:std::cout << "遍历边序列有误" << std::endl;
	}
	
}

void Graph::DFS(int firstid){
	SetFlag();
	DFSearch(firstid);
	for (int i = 0; i < vex_num; i++) {
		if (vex[i]->is_visited == 0)DFSearch(i);
	}
	std::cout << std::endl;

}

void Graph::BFS(int firstid){	
	SetFlag();
	BFSearch(firstid); 
	for (int i = 0; i < vex_num; i++) {
		if (vex[i]->is_visited == 0)BFSearch(i);
	}
	std::cout << std::endl;
}

void Graph::MSTPrim(int firstid){
	SetFlag();
	std::queue<int> v;
	v.push(firstid);
	vex[firstid]->is_visited = 1;
	while (v.size() != vex_num) {
		int wflag = 65535;
		int idflag = 65535;
		int iflag = firstid;
		for (int i = 0; i < v.size(); i++) {
			for (int j = 0; j < vex[i]->RN_cnt; j++) {
				if (vex[vex[i]->RNeighbours[j]->id]->is_visited == 0
					&& wflag > vex[i]->RNeighbours[j]->weight) {
					wflag = vex[i]->RNeighbours[j]->weight;
					idflag = vex[i]->RNeighbours[j]->id;
					iflag = i;
				}
			}
		}
		std::cout << vex[iflag]->data << " " << wflag << " " << vex[idflag]->data << std::endl;
		v.push(idflag);
		vex[idflag]->is_visited = 1;
	}
		
}

void Graph::MSTKruskal(int firstid){
	SetFlag();
	MergeArcWeigt(0,arc_num);			//首先我们进行排序为贪心算法做准备
	int num = 0;
	for (int i = 0; i <arc_num; i++){
		if (num == vex_num - 1)break;	//达到要求就退出
		else {
			if (fa(arc[i]->leftid) != fa(arc[i]->rightid)) {	//如果一个边左右不属于一个代表元素,也就是说他们不在一个连通图中,将他们连到一起。
				arc[i]->right->fatherid = arc[i]->leftid;
				num++;
				std::cout << arc[i]->left->data << " " << arc[i]->weight << " " << arc[i]->right->data << std::endl;
			}
			else if (fa(arc[i]->leftid) != fa(arc[i]->rightid))continue;
		}
	}
	if (num != vex_num - 1) {	//可能出现不连通的情况,这时有这个式子进行判断进行提示
		std::cout << "所给图并非连通图!" << std::endl;
		exit(-2);
	}
}

void Graph::MergeArcWeigt(int low,int high){//此部分代码参考快速排序
		int flag = low;	//标杆元素下标
		if (low == high) return;
		for (int i = flag + 1; i < high; i++) {
			if (arc[flag]->weight <= arc[i]->weight)continue;
			if (arc[flag]->weight > arc[i]->weight) {
				swap(flag + 1, i);
				swap(flag, flag + 1);
				flag++;
			}
		}
		if (flag > low)	MergeArcWeigt(low, flag - 1);
		if (flag < high)MergeArcWeigt(flag + 1, high);
}

const int Graph::get_vex_num(){
	return vex_num;
}

const int Graph::get_arc_num(){
	return arc_num;
}

void Graph::SetFlag(){
	for (int i = 0; i < vex_num; i++) {
		vex[i]->is_visited = 0;
		vex[i]->fatherid = i;
	}
}

void Graph::DFSearch(int firstid){
	if (vex[firstid]->is_visited == 0) {	//如果元素没被访问
		vex[firstid]->is_visited = 1;		//进行访问
		std::cout << vex[firstid]->data << " ";
		for (int i = 0; i < vex[firstid]->RN_cnt; i++) {//对这个元素所有右邻
			if (vex[vex[firstid]->RNeighbours[i]->id]->is_visited == 0) {	//如果没被访问
				DFSearch(vex[firstid]->RNeighbours[i]->id);					//进行访问
			}
		}
	}
	
}

void Graph::BFSearch(int firstid){
	std::queue<int> q;		//一个队列
	q.push(firstid);		//放入一个元素
	vex[firstid]->is_visited = 1;	//标记为访问过
	while (!q.empty()) {	//当队列非空的时候
		for (int i = 0; i < vex[q.front()]->RN_cnt; i++) {		//对队头元素所有的右邻
			int nextid = vex[q.front()]->RNeighbours[i]->id;	//拿到右邻的id
			if (vex[nextid]->is_visited == 0) {					//如果没有被访问
				q.push(nextid);									//入队,标记为访问过
				vex[nextid]->is_visited = 1;
			}									
			else continue;
		}
		std::cout << vex[q.front()]->data << " ";
		q.pop();	//删除队头元素
	}
	
}

void Graph::swap(int id1, int id2){//用于快速排序中改变指针指向
	pArc tmp = arc[id1];
	arc[id1] = arc[id2];
	arc[id2] = tmp;
}

int Graph::fa(int id){
	if (vex[id]->fatherid == id) return id;
	else {
		return fa(vex[id]->fatherid);
	}
}

4.1.3 源文件 main.cpp 内容如下:

#include"Graph.h"
int main() {
	int** arr_for_summon = new int* [8]; for (int i = 0; i < 8; i++)arr_for_summon[i] = new int[8];
	int arr[8][8] = {
		0, 1, 0, 2, 0, 3, 0, 4,
		1, 0, 4, 0, 5, 6, 0, 0,
		0, 4, 0, 3, 0, 0, 0, 0,
		2, 0, 3, 0, 2, 0, 0, 5,
		0, 5, 0, 2, 0, 1, 0, 0,
		3, 6, 0, 0, 1, 0, 1, 0,
		0, 0, 0, 0, 0, 1, 0, 2,
		4, 0, 0, 5, 0, 0, 2, 0 };
	for (int i = 0; i < 8; i++)for (int j = 0; j < 8; j++)arr_for_summon[i][j] = arr[i][j];
	Graph gr1(arr_for_summon, 8, UN_NET);

	for (int i = 0; i < 8; i++) {
		gr1.setdata(i, 'A' + i);
	}
	for (int i = 0; i < 8; i++) {
		gr1.printNBS(i);
	}
	//gr1.printARR();
	std::cout << "深度优先遍历" << std::endl;
	gr1.DFS();
	std::cout << "广度优先遍历" << std::endl;
	gr1.BFS();
	std::cout << "Prim算法最小生成树:" << std::endl;
	gr1.MSTPrim();
	std::cout << "Kruskal算法最小生成树:" << std::endl;
	gr1.MSTKruskal();
}

4.2 运行结果

在这里插入图片描述
在这里插入图片描述

5.上机体会

本次实验在上次实验基础上,利用矩阵输入,在无向连同图中完成了Prim算法和Kruskal算法,进行最小生成树的生成。在实验过程中发现了上次实验中存在的问题缺陷,例如在某些循环里将‘i’写成了‘1’,导致访问越界问题,可见在一个项目完成的过程中需要进行全面的测评。在本次实验中主要掌握了两种算法思想:一个是“做标记”,即在不确定是否满足条件的情况下,用一个flag先标记数据,在满足条件下更改flag以减少在循环里处理的次数,对于在一个数据群里需要按照某个规律找到有限个满足条件的数据时十分实用;另一个是“代表元素”,也就是简单的并查集,在大量数据群体中,若要拎出两个元素对它们是否在一个群体中进行判断,可以用到这样的思想:每个群体都派出一个代表元素,只要每个在群体里的数据知道自己的代表元素是谁就行,那样任意两个元素是否在一个群体里,只要看他们的代表元素是否相同就行,相比于无脑的循环查找判断能够大大降低时间复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值