文章目录
- 1.上机名称
- 2.上机要求
- 3.上机环境
- 4.程序清单(写明运行结果及结果分析)
- 4.1 程序清单
- 4.1.1 头文件 Graph.h 内容如下:
- 4.1.2 实现文件 Graph.cpp 内容如下:
- 4.1.3 源文件 main.cpp 内容如下:
- 4.2 运行结果
- 5.上机体会
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的图,图的类型为KindGraph(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_numconst int get_vex_num(); //返回顶点数const int get_arc_num(); //返回边数,无向图、网返回值是边的两倍,在函数调用过程中自洽
protected:void makeneighbour(int id1, int id2, int weight = 1);//造邻居void SetFlag(); //初始化两个flagvoid DFSearch(int firstid = 0); //对一个顶点的DFSvoid BFSearch(int firstid = 0); //对一个顶点的DFSvoid 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; //拿到右邻的idif (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以减少在循环里处理的次数,对于在一个数据群里需要按照某个规律找到有限个满足条件的数据时十分实用;另一个是“代表元素”,也就是简单的并查集,在大量数据群体中,若要拎出两个元素对它们是否在一个群体中进行判断,可以用到这样的思想:每个群体都派出一个代表元素,只要每个在群体里的数据知道自己的代表元素是谁就行,那样任意两个元素是否在一个群体里,只要看他们的代表元素是否相同就行,相比于无脑的循环查找判断能够大大降低时间复杂度。