文章目录
1.上机名称
实现图的存储和遍历
2.上机要求
对无向图、无向网、有向图、有向网进行如下操作:
(1)建立图/网的邻接矩阵,并进行输出。
(2)建立图/网的邻接表,并进行输出。
(3)对图/网进行深度优先遍历。
(4)对图/网进行广度优先遍历。
(5)销毁图/网。
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; //标注是否被遍历
int LN_cnt; //左邻数
int RN_cnt; //右邻数
}Gnode, * pGnode;
图类的定义
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 makeneighbour(int id1, int id2, int weight = 1);//造邻居
void printNBS(int id); //打印id顶点的邻接表
void printARR(); //打印图的邻接矩阵
void DFS(int firstid = 0); //从下标为firstid的顶点深度优先遍历
void BFS(int firstid = 0); //从下标为firstid的顶点广度优先遍历
protected:
void SetFlag(); //为遍历初始化flag
void DFSearch(int firstid = 0);
void BFSearch(int firstid = 0);
int vex_num; //顶点数量
int** GArray; //邻接矩阵
int GKind; //图的类型标记
pGnode* vex; //存放顶点的顺序表
};
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;
}
通过array初始化
Graph::Graph(int** array_for_summon, int size, int Kind){
vex_num = size;
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;
}
insert
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;
}
setdata
void Graph::setdata(int id, Data data){
vex[id]->data = data;
}
link
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();
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;
//别忘了对邻接矩阵和用于记录的数据进行修改
GArray[id1][id2] = weight;
vex[id1]->RN_cnt++;
vex[id2]->LN_cnt++;
}
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[1]->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[1]->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[1]->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::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::SetFlag(){
for (int i = 0; i < vex_num; i++) vex[i]->is_visited = 0;
}
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(); //删除队头元素
}
}
4.1.3 源文件main.cpp中,内容如下:
#include"Graph.h"
int main() {
Graph gr(8, 0);
gr.link(0, 1);
gr.link(1, 2);
gr.link(1, 7);
gr.link(1, 4);
gr.link(2, 3);
gr.link(2, 5);
gr.link(3, 6);
for (int i = 0; i < 8; i++) {
gr.setdata(i, 'A' + i);
}
for (int i = 0; i < 8; i++) {
gr.printNBS(i);
}
gr.printARR();
std::cout << "深度优先遍历" << std::endl;
gr.DFS();
std::cout << "广度优先遍历" << std::endl;
gr.BFS();
std::cout << "**************************" << std::endl;
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, 0, 0, 0, 0, 0,
1, 0, 1, 0, 1, 0, 0, 1,
0, 1, 0, 1, 0, 1, 0, 0,
0, 0, 1, 0, 0, 0, 1, 0,
0, 1, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0,
0, 0, 0, 1, 0, 0, 0, 0,
0, 1, 0, 0, 0, 0, 0, 1 };
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, 0);
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();
}
4.2 实验结果部分
本次实验展示以无向图为例:
第一张截图是通过link接口实现的图的构造结果展示,
第二张截图是通过传入构造矩阵创建的图的构造结果展示。
第三章截图是当我们构造无向图时,传入的矩阵不是无向图的兼容性提醒,选择y进行兼容处理,其他则退出程序,由于输入矩阵里,有两个非对称点,故提醒了两次不合法。
5.上机体会
本次实验实现了图这个大类,在基础实验要求上多了有向图和网的操作,做实验时,需要从下面4个方面入手。
1、理解图的基本概念:在开始实验之前,需要先理解图的基本概念,如顶点、边、有向图、无向图等。这有助于更好地理解图的数据结构和算法,在本次实验中,虽然性质略有不同,但经过简单的处理,可以将四种结构的要求统一到一起,通过一个类来实现。
2、选择合适的数据结构:图的数据结构有多种,如邻接矩阵、邻接表、邻接多重表等。在选择数据结构时,需要根据实际情况考虑,如图的大小、是否需要动态修改等,本次实验使用邻接表和邻接矩阵的方式进行了简单的实现,如果需要,可以再声明一个存放所有节点邻接表地址的数组,形成邻接多重表,进一步化简代码。
3、实现基本操作:实现图的基本操作,如添加顶点、添加边、构造邻接表、构造邻接矩阵。删除顶点、删除边的删除操作尚未完成,但实现也并不复杂。在实现这些操作时,需要注意算法的时间复杂度和空间复杂度,以确保算法的效率,故多采用递归算法、数组存储结构。