1 基本概念
1.1 图的定义
图是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:
G=(V,E)
其中:G表示一个图,V是图G中顶点的集合,E是图G中顶点之间边的集合。
注:
在线性表中,元素个数可以为零,称为空表;
在树中,结点个数可以为零,称为空树;
在图中,顶点个数不能为零,但可以没有边。
1.2 图的存储结构
考虑图的定义,图是由顶点和边组成的,分别考虑如何存储顶点、如何存储边。
1.2.1 邻接矩阵存储(数组表示法)
基本思想:用一个一维数组存储图中顶点的信息,用一个二维数组(称为邻接矩阵)存储图中各顶点之间的邻接关系。
假设图G=(V,E)有n个顶点,则邻接矩阵是一个n×n的方阵,定义为:



1.2.2 邻接表存储
邻接表存储的基本思想:对于图的每个顶点vi,将所有邻接于vi的顶点链成一个单链表,称为顶点vi的边表(对于有向图则称为出边表),所有边表的头指针和存储顶点信息的一维数组构成了顶点表。
邻接表有两种结点结构:顶点表结点和边表结点。

其中:
vertex:数据域,存放顶点信息。firstedge:指针域,指向边表中第一个结点。
adjvex:邻接点域,边的终点在顶点表中的下标。next:指针域,指向边表中的下一个结点。(本文在实现的时候将adjvex设置为了顶点元素)
2 代码实现
本文使用面向对象的思想实现,将图的两种存储方式及相应的成员变量,成员函数封装在了类Graph中。为方便实现,顶点数据类型设为char。关于边的添加删除,点的添加删除因为实现原理简单,但实现过程繁琐本文未给予实现。
2.1 使用邻接矩阵存储(adjacency matrix)
2.1.1 成员变量
使用邻接矩阵存储图时,不需要定义新的结构体。
int VexNum, ArcNum; //顶点数和边数
//邻接矩阵所需成员变量
int** AM_Edge; //邻接矩阵边集
char* AM_Node; //邻接矩阵顶点集
//深度和广度优先搜索中,用于标记结点是否被访问的数组,按序号对应每个顶点
bool *visited;
复制代码
2.1.2 创建图
依次从控制台输入所有顶点,及所有的边创建完整的图,例如:
ABCDEFGH (enter)
AB AC BF CD FD CE FH DG (enter,再ctrl+z标示输入中止)
//创建邻接矩阵存储的图
void AM_GraphInitial() { //初始化邻接矩阵并构造该图
cout << "please input all the vextex: " << endl;
string s;
cin >> s;
VexNum = s.length(); //确定顶点总数
AM_Node = new char[VexNum]; //创建存储顶点的数组
for (int i = 0; i < VexNum; i++) //给顶点数组赋值
AM_Node[i] = s[i];
AM_Edge = new int*[VexNum]; //申请邻接矩阵存储空间
for (int i = 0; i < VexNum; i++) //对邻接矩阵初始化
{
AM_Edge[i] = new int[VexNum];
for (int j = 0; j < VexNum; j++)
AM_Edge[i][j] = 0;
}
//输入所有的边,形式为 AB CD ED
cout << "please input all the edge,format as:AB CD...,end with ctrl+'z'!"<<endl;
while (cin >> s) {//输入所有的边后回车,再输入ctrl+z用于中止输入
int i = AM_locate(s[0]);
int j= AM_locate(s[1]);
if (i != -1 && j != -1)
{
AM_Edge[i][j] = 1;
ArcNum++; //统计边数
}
}
}
int AM_locate(char vex) { //寻找顶点在顶点数组中的下标
for (int i = 0; i < VexNum; i++)
if (AM_Node[i] == vex)
return i;
cout << vex << " is not exit in this graph!" << endl;
return -1;
}
复制代码
2.1.3 以邻接矩阵的形式输出该图
void AM_GraphPrint() { //以邻接矩阵的形式输出该图
cout << "adjacency matrix of this graph:" << endl;
cout << " ";
for (int i = 0; i < VexNum; i++)
cout << AM_Node[i]<<" ";
cout << endl;
for (int i = 0; i < VexNum; i++) {
cout << AM_Node[i] << " ";
for (int j = 0; j < VexNum; j++)
cout << AM_Edge[i][j] << " ";
cout << endl;
}
复制代码
2.1.4 求某个顶点的入度和出度及输出所有点的入度出度
某个顶点的入度,即顶点所在列非零项个数;出度,即顶点所在行非零项个数。
//输出所有点的入度出度;
cout << "vertex in-degree out-degree" << endl;
int d1, d2;
for (int i = 0; i < VexNum; i++)
{
AM_degree(AM_Node[i], d1, d2);
cout << setw(6) << AM_Node[i] << setw(10) << d1 << setw(11) << d2 << endl;
}
}
//求某个顶点的入度和出度
void AM_degree(char vex,int &d1,int &d2) {
d1 = d2 = 0;
int loc = AM_locate(vex);
for (int i = 0; i < VexNum; i++)
if (AM_Edge[i][loc] != 0) //入度,即顶点所在列非零项个数
d1++;
for (int i = 0; i < VexNum; i++)
if (AM_Edge[loc][i] != 0) //出度,即顶点所在行非零项个数
d2++;
}
复制代码
2.1.5 广度优先遍历(BFS)(针对一个连通分量)
基本思想:
⑴ 访问顶点v;
⑵ 依次访问v的各个未被访问的邻接点v1, v2, …, vk;
⑶ 分别从v1,v2,…,vk出发依次访问它们未被访问的邻接点,并使“先被访问顶点的邻接点”先于“后被访问顶点的邻接点”被访问。直至图中所有与顶点v有路径相通的顶点都被访问到。
已访问过的结点后续不需要再次访问,因此需要使用一个数组visited[VeNum]标记每个顶点的访问状态,未访问过标记为false。实现方式类似于二叉树的层次遍历,每次将队列最前元素取出并将与其相连且未访问过的顶点加入队尾。
//使用队列实现,类似于二叉树的层次遍历
void AM_BFS(int startLoc) {
if (startLoc > VexNum - 1)
{
cout << "AL_BFS start location is error!" << endl;
return;
}
cout << "The BFS of this graph(stored by adjacency matrix):" << endl;
InitialVisited(); //初始化标记数组
queue<int> bfs;
bfs.push(startLoc);
visited[startLoc] = true; //将起始结点加入队列并标记为已访问
while (!bfs.empty()) { //每次取出队列最前的顶点,并将其相连且未标记过的顶点加入队列尾部
int j = bfs.front();
cout << AM_Node[j]<<" ";
// visited[j] = true;
bfs.pop();
//寻找该结点为起始结点,为结点未访问过的边;
for (int i = 0; i < VexNum; i++) {
if (AM_Edge[j][i] != 0 && visited[i] != true) //存在指向顶点i的边且该点未被访问则加入队列
{
bfs.push(i);
visited[i] = true; //应该在进入队列的时候标记为已经访问,而不能是输出时,否则可能会多次入队
}
}
}
cout << endl;
}
复制代码
2.1.6 深度优先遍历(DFS)(针对一个连通分量)
基本思想 :
⑴ 访问顶点v;
⑵ 从v的未被访问的邻接点中选取一个顶点w,从w出发进行深度优先遍历;
⑶ 重复上述两步,直至图中所有和v有路径相通的顶点都被访问到。
这里使用了两种实现方式,递归实现和借助栈实现,递归实现较为简单,使用栈实现时,注意设置压栈和出栈的条件和时间即可,详细步骤见代码注释。
//深度优先遍历(DFS),输入为起始点在数组中的下标(针对一个连通分量)
void AM_DFS(int startLoc) {
if (startLoc > VexNum - 1)
{
cout << "AM_BFS start location is error!" << endl;
return;
}
cout << "The DFS of this graph(stored by adjacency matrix):" << endl;
cout << "use recursion: ";
InitialVisited(); //切记要先初始化标记数组
AM_RecursiveDFS(startLoc); //使用递归;
cout << endl;
cout << "use stack: ";
InitialVisited();
AM_StackDFS(startLoc); //使用栈
cout << endl;
}
//使用递归实现DFS(针对一个连通分量)
void AM_RecursiveDFS(int startLoc) {
cout << AM_Node[startLoc]<<" ";
visited[startLoc] = true;
for (int i = 0; i < VexNum; i++) {
//如果发现存在相连且未标记的顶点,则递归为对该顶点的访问
if (AM_Edge[startLoc][i] != 0 && visited[i] != true)
AM_RecursiveDFS(i);
}
}
//使用栈实现DFS(针对一个连通分量)
void AM_StackDFS(int startLoc) {
if (startLoc > VexNu

本文详细介绍图的两种主要存储方式——邻接矩阵与邻接表,并提供了C++实现代码。通过实例演示了如何创建图、输出图的结构、求顶点的度数以及进行广度优先与深度优先遍历等操作。
5654

被折叠的 条评论
为什么被折叠?



