图是比较常用的一种数据结构,我针对期末考试对其进行了大概整理,形成了本文。
声明
#include<iostream>
#include<fstream>
#include<queue>
#include<stack>
using namespace std;
#define MAX_V 20 // 最大顶点数目
#define READMODE 2//文件内容格式,1是完整的矩阵,2是读入边
typedef char ElemType;//元素类型
typedef int GraphKind;//图形类型
typedef struct
{
ElemType info; // 顶点其它信息
}VertexType; // 顶点类型定义
class MGraph
{
private:
int arcs[MAX_V][MAX_V]; // 邻接矩阵
int num1, num2; // 图包含的顶点数以及边数
VertexType vexs[MAX_V]; // 存放顶点信息
GraphKind type;//如果提供的数据区分不了,默认是无向图,十位数为一代表网,没有数代表图,个位数为一代表无向,为二代表有向
public:
MGraph();
void BFS();//广度遍历输出图
void DFS();//深度遍历输出图
void dfs(bool* f, int i);//深度遍历辅助函数
void print();//输出图的邻接矩阵
void DJ(int b);//迪杰斯特拉(Dijkstra)算法求最短路径
void prim();//普里姆(Prim)算法求最小生成树,输出其邻接矩阵
void DPauxiliary(int b, int* f, bool* v, int* pre);//迪杰斯特拉算法和普里姆算法的辅助函数
};
图的建立
整体上是基于文件进行图的建立,有两种文件内容格式,READMODE ==1时,是读入顶点个数,顶点信息以及邻接矩阵,READMODE ==2时,是读入顶点个数,顶点信息,边的个数,边的信息,样例如下:
两种读入形成的邻接矩阵都是从1开始存,0不存数据。
READMODE ==1
if (READMODE == 1)
{
for (i = 1; i <= num1; i++)
{
for (int j = 1; j <= num1; j++)
{
file >> arcs[i][j];
if (arcs[i][j] != 0 && i != j)
num2++;
if (arcs[i][j] > 1)
type += 10;//网
}
}
i = 0;
if (num2 % 2 == 0)
{
type += 1;//无向
num2 /= 2;//提前去掉重复统计的边
for (int i = 1; i <= num1; i++)
{
for (int j = 1; j <= i; j++)
{
if (arcs[i][j] != arcs[j][i])
{
num2 *= 2;//还原边
type += 1;//有向
break;
}
}
if (type % 10 == 2)
break;
}
}
}
READMODE==2
if (READMODE == 2)//默认是无向
{
file >> num2;
for (int i = 1; i <= num1; i++)
{
for (int j = 1; j <= num1; j++)
{
if (i != j)
arcs[i][j] = 0;
else
arcs[i][j] = 1;
}
}
type += 1;
for (i = 1; i <= num2; i++)
{
int x, y, l;
file >> x >> y >> l;
arcs[x][y] = arcs[y][x] = l;
if (l > 1)
type += 10;
}
}
遍历
无论是广度遍历还是深度遍历,其目的都是将所有点的信息进行遍历输出,不能有遗落,只不过BFS是广度优先,DFS是深度优先。
广度遍历(BFS)
广度优先的字面意思很好理解,其思想也差不多,我理解的就是一层一层访问,以最开始访问的顶点作为基准,记其层数为0,其他点几条线与之相连就是第几层,每一层的访问顺序由其上层(指的是最开始的分支层)的标号大小决定,编号小的先访问,上层完全相同的,比较本身编号大小,编号小的先访问。对于非连通图来讲,将图分成几个连通的子图,按照标号顺序访问整个子图。
这个只是个人思路,仅供参考,但是代码思路还是很清晰的:
1.先将起点入队
2.出队输出,并将与出队点直接相连的所有点入队
3.反复执行第二步
4.遍历,将所有未入队过的点从第1步开始执行,直至所有点都入队过
5.反复出队输出,直至队空
void MGraph::BFS()
{
queue<int> q;
bool* f = new bool[num1 + 1];
fill(f, f + num1 + 1, 0);
cout << "广度遍历结果为:";
for (int i = 1; i <= num1; i++)
{
if (f[i] == 1)
continue;
q.push(i);
f[i] = 1;
for (int j = 1; j <= num1; j++)
{
if (arcs[i][j] == 0 || f[j] == 1 || j == i)//两点之间没有边,该点已经访问过,该点与先行点是一个的情况排除
continue;
q.push(j);//压入队列
f[j] = 1;
}
cout << vexs[q.front()].info << ' ';
q.pop();
}
while (!q.empty())
{
cout << vexs[q.front()].info << ' ';
f[q.front()] = 1;
q.pop();
}
cout << endl;
}
深度遍历(DFS)
同样的,广度优先的字面意思也很好理解,我的解释是,一条线一条线的遍历,出现分支的话,先进入编号小的一边,访问到头后,回到分支点,访问其他分支,同样的,对于非连通图来讲,将图分成几个连通的子图,按照标号顺序访问整个子图。 代码思路就是递归,首先一个大循环防止是非连通图导致访问不全。然后就是一条线一条线的进行访问,到头就返回。
void MGraph::DFS()
{
bool* f = new bool[num1 + 1];
fill(f, f + num1 + 1, 0);
cout << "深度遍历结果为:";
for (int i = 1; i <= num1; i++)
{
if (f[i] == 1)
continue;
dfs(f, i);
}
cout << endl;
}
void MGraph::dfs(bool* f, int i)
{
cout << vexs[i].info << ' ';
f[i] = 1;
for (int j = 1; j <= num1; j++)
{
if (i == j || arcs[i][j] == 0 || f[j] == 1)
continue;
dfs(f, j);
}
}
最短路径和最小生成树(迪杰斯特拉算法和普里姆算法)
迪杰斯特拉算法和普里姆算法很像,**都是以一个点为基准,反复从未接入过的点中选出与之最近的点,接入图中,然后更新与新接入点直接相连的点到基准点的最短路径长度,只不过迪杰斯特拉算法说的最短针对的是起点,而普里姆算法的最短针对的是新的图。**但是思想还是差不多的。
另外我写的代码是针对无向的,有向没试,但应该不行。
迪杰斯特拉算法
找最短路径就是一个大循环内嵌两个循环,大循环是为了遍历到所有的点,第一个小循环是为了寻找未接入过的点中选出与基准点最近的点,第二个小循环就是最短路径长度和前驱点的更新。文章来源:https://www.toymoban.com/news/detail-765246.html
void MGraph::DJ(int b)
{
if (type % 10 == 2)
{
cout << "有向图不行" << endl;
return;
}
int* f;
bool* v;
int* pre;
v = new bool[num1 + 1];//标记是否经过
f = new int[num1 + 1];//记录最短距离
pre = new int[num1 + 1];//记录最短路径的上一节点
fill(f, f + num1 + 1, INT_MAX);
fill(v, v + num1 + 1, false);
for (int i = 1; i <= num1; i++)
pre[i] = i;
f[b] = 0;
for (int i = 1; i <= num1; i++)
{
int near = -1;
int min = INT_MAX;
for (int j = 1; j <= num1; j++)//每次寻找到的点,都是未选过的,距离起点最近的点
{
if (v[j] == false && f[j] < min)
{
near = j;
min = f[j];
}
}
if (near == -1)
break;
v[near] = 1;
for (int j = 1; j <= num1; j++)
{
if (arcs[near][j] != 0 && j != near && v[j] == false)
{
if (min + arcs[near][j] < f[j]) {
f[j] = min + arcs[near][j];
pre[j] = near;//pre保存当前结点的上一个节点,以便输出最短路线
}
}
}
}
for (int i = 1; i <= num1; i++)
{
if (f[i] == INT_MAX)
{
cout << b << "和" << i << "不连通" << endl;
continue;
}
if (i == b)
continue;
stack<int>path;
int j = i;
while (j != b)
{
path.push(j);
j = pre[j];
}
cout << b << "-";
while (!path.empty())
{
if (path.top() != b)
cout << path.top();
path.pop();
if (!path.empty())
cout << '-';
}
cout <<" " << "最短距离是" << f[i] << endl;
}
}
最小生成树
我是直接输出构成最小生成树的边了,找边跟最短路径差不多文章来源地址https://www.toymoban.com/news/detail-765246.html
void MGraph::prim()
{
bool* f = new bool[num1 + 1];//是否接入新图
int* linknode = new int[num1 + 1];//与新的图的连接点
int *k = new int[num1 + 1];//与新的图的最小长度
fill(f, f + num1 + 1, false);
for (int i = 2; i <= num1; i++)
{
k[i] = arcs[i][1];
if (k[i] == 0)
{
k[i] = 999;
linknode[i] = 0;
}
else
linknode[i] = 1;
}
k[1] = 0;
f[1] = 1;
for (int i = 2; i <= num1; i++)
{
int min = 999;
int minnode = 0;
for (int j = 2; j<= num1; j++)
{
if (f[j])
continue;
if (k[j] < min)
{
min = k[j];
minnode = j;
}
}
if (min == 999)
break;
f[minnode] = 1;
cout << linknode[minnode] << ' ' << minnode << ' ' << k[minnode] << endl;
for (int j = 2; j <= num1; j++)//更新
{
if (f[j])
continue;
if (arcs[j][minnode] != 0 && k[j] > arcs[j][minnode])
{
k[j] = arcs[j][minnode];
linknode[j] = minnode;
}
}
}
}
源代码
#include<iostream>
#include<fstream>
#include<queue>
#include<stack>
using namespace std;
#define MAX_V 20 // 最大顶点数目
#define READMODE 2//文件内容格式,1是完整的矩阵,2是读入边
typedef char ElemType;//元素类型
typedef int GraphKind;//图形类型
typedef struct
{
ElemType info; // 顶点其它信息
}VertexType; // 顶点类型定义
class MGraph
{
private:
int arcs[MAX_V][MAX_V]; // 邻接矩阵
int num1, num2; // 图包含的顶点数以及边数
VertexType vexs[MAX_V]; // 存放顶点信息
GraphKind type;//如果提供的数据区分不了,默认是无向图,十位数为一代表网,没有数代表图,个位数为一代表无向,为二代表有向
public:
MGraph();
void BFS();
void DFS();
void dfs(bool* f, int i);
void print();
void DJ(int b);
void prim();
void DPauxiliary(int b, int* f, bool* v, int* pre);
};
int main()
{
MGraph G;
G.print();
G.DFS();
G.BFS();
G.DJ(1);
G.prim();
}
MGraph::MGraph()
{
type = 0;
num2 = 0;
fstream file("data1.txt", ios::in);
file >> num1;
int j = 1, i = 1;
while (i <= num1)
file >> vexs[i++].info;
if (READMODE == 1)
{
for (i = 1; i <= num1; i++)
{
for (int j = 1; j <= num1; j++)
{
file >> arcs[i][j];
if (arcs[i][j] != 0 && i != j)
num2++;
if (arcs[i][j] > 1)
type += 10;//网
}
}
i = 0;
if (num2 % 2 == 0)
{
type += 1;//无向
num2 /= 2;//提前去掉重复统计的边
for (int i = 1; i <= num1; i++)
{
for (int j = 1; j <= i; j++)
{
if (arcs[i][j] != arcs[j][i])
{
num2 *= 2;//还原边
type += 1;//有向
break;
}
}
if (type % 10 == 2)
break;
}
}
}
if (READMODE == 2)//默认是无向
{
file >> num2;
for (int i = 1; i <= num1; i++)
{
for (int j = 1; j <= num1; j++)
{
if (i != j)
arcs[i][j] = 0;
else
arcs[i][j] = 1;
}
}
type += 1;
for (i = 1; i <= num2; i++)
{
int x, y, l;
file >> x >> y >> l;
arcs[x][y] = arcs[y][x] = l;
if (l > 1)
type += 10;
}
}
else
type += 2;//有向
cout << "这是一个" << num1 << "个顶点" << num2 << "条边" << "的";
if (type % 10 == 2)
cout << "有向";
else
cout << "无向";
if (type / 10 > 0)
cout << "网" << endl;
else
cout << "图" << endl;
}
void MGraph::BFS()
{
queue<int> q;
bool* f = new bool[num1 + 1];
fill(f, f + num1 + 1, 0);
cout << "广度遍历结果为:";
for (int i = 1; i <= num1; i++)
{
if (f[i] == 1)
continue;
q.push(i);
f[i] = 1;
for (int j = 1; j <= num1; j++)
{
if (arcs[i][j] == 0 || f[j] == 1 || j == i)//两点之间没有边,该点已经访问过,该点与先行点是一个的情况排除
continue;
q.push(j);//压入队列
f[j] = 1;
}
cout << vexs[q.front()].info << ' ';
q.pop();
}
while (!q.empty())
{
cout << vexs[q.front()].info << ' ';
f[q.front()] = 1;
q.pop();
}
cout << endl;
}
void MGraph::dfs(bool* f, int i)
{
cout << vexs[i].info << ' ';
f[i] = 1;
for (int j = 1; j <= num1; j++)
{
if (i == j || arcs[i][j] == 0 || f[j] == 1)
continue;
dfs(f, j);
}
}
void MGraph::DFS()
{
bool* f = new bool[num1 + 1];
fill(f, f + num1 + 1, 0);
cout << "深度遍历结果为:";
for (int i = 1; i <= num1; i++)
{
if (f[i] == 1)
continue;
dfs(f, i);
}
cout << endl;
}
void MGraph::print()
{
for (int i = 1; i <= num1; i++)
{
for (int j = 1; j <= num1; j++)
cout << arcs[i][j] << ' ';
cout << endl;
}
}
void MGraph::DJ(int b)
{
if (type % 10 == 2)
{
cout << "有向图不行" << endl;
return;
}
int* f;
bool* v;
int* pre;
v = new bool[num1 + 1];//标记是否经过
f = new int[num1 + 1];//记录最短距离
pre = new int[num1 + 1];//记录最短路径的上一节点
fill(f, f + num1 + 1, INT_MAX);
fill(v, v + num1 + 1, false);
for (int i = 1; i <= num1; i++)
pre[i] = i;
f[b] = 0;
for (int i = 1; i <= num1; i++)
{
int near = -1;
int min = INT_MAX;
for (int j = 1; j <= num1; j++)//每次寻找到的点,都是未选过的,距离起点最近的点
{
if (v[j] == false && f[j] < min)
{
near = j;
min = f[j];
}
}
if (near == -1)
break;
v[near] = 1;
for (int j = 1; j <= num1; j++)
{
if (arcs[near][j] != 0 && j != near && v[j] == false)
{
if (min + arcs[near][j] < f[j]) {
f[j] = min + arcs[near][j];
pre[j] = near;//pre保存当前结点的上一个节点,以便输出最短路线
}
}
}
}
for (int i = 1; i <= num1; i++)
{
if (f[i] == INT_MAX)
{
cout << b << "和" << i << "不连通" << endl;
continue;
}
if (i == b)
continue;
stack<int>path;
int j = i;
while (j != b)
{
path.push(j);
j = pre[j];
}
cout << b << "-";
while (!path.empty())
{
if (path.top() != b)
cout << path.top();
path.pop();
if (!path.empty())
cout << '-';
}
cout <<" " << "最短距离是" << f[i] << endl;
}
}
void MGraph::prim()
{
bool* f = new bool[num1 + 1];
int* linknode = new int[num1 + 1];
int *k = new int[num1 + 1];
fill(f, f + num1 + 1, false);
for (int i = 2; i <= num1; i++)
{
k[i] = arcs[i][1];
if (k[i] == 0)
{
k[i] = 999;
linknode[i] = 0;
}
else
linknode[i] = 1;
}
k[1] = 0;
f[1] = 1;
for (int i = 2; i <= num1; i++)
{
int min = 999;
int minnode = 0;
for (int j = 2; j<= num1; j++)
{
if (f[j])
continue;
if (k[j] < min)
{
min = k[j];
minnode = j;
}
}
if (min == 999)
break;
f[minnode] = 1;
cout << linknode[minnode] << ' ' << minnode << ' ' << k[minnode] << endl;
for (int j = 2; j <= num1; j++)
{
if (f[j])
continue;
if (arcs[j][minnode] != 0 && k[j] > arcs[j][minnode])
{
k[j] = arcs[j][minnode];
linknode[j] = minnode;
}
}
}
}
到了这里,关于图的基本操作(邻接矩阵)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!