文章目录
前言
数据与数据之间存在多对多关系的数据结构我们叫做图,由于同构图的存在,因而图的数据之间不能用简单顺序结构来存储,倘若使用多重链式存储结构,当图之中各点的度不一样的的时候又会造成和先前树一样浪费空间的问题,所以不能用简单的链式存储来存储图,而要用几种特殊的办法。
一、存储结构
1. 邻接矩阵
我们可以两个数组分别存储点的信息和边的信息
#define MAXVEX 100 // 顶点数
#define INFINITY 32767 // 极大值
typedef char VertexType; // 顶点的数据类型
typedef int EdgeType; // 边的权值类型
typedef struct{
VertexType vexs[MAXVEX];
EdgeType arcs[MAXVEX][MAXVES];
int vexnum, arcnum
}MGraph;
有了图的存储结构,我们就可以对应写出创建一个图的过程
void CreatMGraph(MGraph &G){
cin >> G.vexnum >> G.arcnum;
for(int i = 0; i < G.vexnum; ++i){
cin >> G.vexs[i]; // 输入点的信息
}
// 初始化边
for(int i = 0; i < MAXVEX; ++i){
for(int j = 0; j < MAXVEX; ++j){
G.arcs[i][j] = INFINITY;
}
}
for(int k = 0; k < G.arcnum; ++k){
int i = -1, j = -1;
cin >> i >> j; // 输入边连接的结点
cin >> w; // 输入边的信息
G.arcs[i][j] = w;
G.arcs[j][i] = w;
}
return OK;
}
2. 邻接表
为了解决稀疏链表使用邻接矩阵存储效率不高的问题,我们可以用链表来存储边的关系
// 边结点的定义
typedef struct EdgeNode{
int adjvex;
EdegeType info;
struct EdgeNode *next;
};
// 图结点的定义
typedef struct VertexNode{
VertexType data;
EdgeNode *firstedge;
}AdjList[MAXVEX];
// 整个图的定义
typedef struct{
AdjList adjlist;
int vexnum, arcnum;
}GraphAdjList;
对于有向图,邻接表就变成了结点出度表。如果需要给边加权重,只需要在边结点新增一个weight域就好
void CreatALGraph(GraphAdjList &G){
cin >> G.vexnum >> G.arcnum;
// 初始化图中的结点
for(int i = 0; i < G.vexnum; ++i){
cin >> G.VertexNode[i].data;
G.VertexNode[i].firstedge = NULL;
}
for(int k = 0; k < G.arcnum; ++k){
int i = -1; j = -1;
cin >> i >> j;
e = new EdgeNode;
e -> adjvex = j;
e -> next = G.adjlist[i].firstedge;
G.adjlist[i].firstedge = e;
e = new EdgeNode;
e -> adjvex = i;
e -> next = G.adjlist[j].firstedge;
G.adjlist[j].firstedge = e;
}
}
3. 十字链表
十字链表主要应用在有向图中,他将邻接表和逆邻接表结合,做到既能关注出度又能关注入度。
这是图结点的结构:
这是边结点的结构:
十字链表如下:
4. 边集数组
这种存储方法关注的是边,他用一个结构体把每条边的起点终点下标和权重记录下来。
typedef struct{
int begin, end, weight;
}edge;
二、图的遍历
遍历图的算法有两种,一种是深度优先遍历,不停地访问新结点直到访问到一个节点,它周围所有的结点都访问过了,再重新回到先前分叉的位置继续深度优先遍历。另一种是广度优先遍历,当遇到一个分叉结点时优先访问依次完他的所有邻接结点然后在访问它邻接节点后的邻接节点。
(一)深度优先遍历(DFS)
1. 邻接矩阵的深度优先遍历
对于邻接矩阵,现在访问到了一个结点,我们要做的就是对每一个结点进行深度优先遍历,所有有一个for循环,在循环里我们调用DFS递归,当这个点和我访问的点邻接,且它没有被访问过。
bool visited[MAXVEX];
// 邻接矩阵的DFS算法
void DFS(MGraph G, int i){
visited[i] = TRUE;
visit(G, i); // 对结点i的操作
for(int j = 0; j < vexnum; ++j){
if(G.arc[i][j] == 1 && !visited[j]){
DFS(G, j);
}
}
}
// 用DFS算法遍历一张图
void DFSTraverse(Graph G){
for(int i = 0; i < G.vexnum; ++i){
visited[i] = FALSE:
}
for(int i = 0; i < G.vexnum; ++i){
if(!visited[i]) DFS(G, i)
}
}
2. 邻接表的深度优先遍历
对于邻接表,我们需要一个从当前访问结点开始的指针,不断地访问我的邻接表,当访问到下一个顶点时递归调用DFS访问他的邻接表,这样就实现了深度优先遍历。
bool visited[MAXVEX];
// 邻接表的DFS算法
void DFS(GraphAdjList GL, int i){
visited[i] = TRUE;
visit(GL, i); // 对结点i的操作
EdgeNode *p;
p = GL -> adjlist[i].firstedge;
while(p){
if(!visited[p -> adjvex]) DFS(GL, p -> adjvex);
p = p -> next;
}
}
// 用DFS算法遍历一张图
void DFSTraverse(Graph G){
for(int i = 0; i < G.vexnum; ++i){
visited[i] = FALSE:
}
for(int i = 0; i < G.vexnum; ++i){
if(!visited[i]) DFS(G, i)
}
}
(二)广度优先遍历(BFS)
1. 邻接矩阵的广度优先遍历
我们用队列来实现一张图的广度优先遍历,首先先访问图的第一个结点,并将他入队,至此完成了遍历的初始化。当队列不为空时,将队中元素出队,随后访问与该节点所有邻接的结点,并在访问完任一结点后,将与它邻接的结点入队。
void BFSTraverse(MGraph G){
Queue q;
for(int i = 0; i < G.vexnum; ++i){
visited[i] = FALSE;
}
InitQueue(q);
for(int i = 0; i < G.vexnum; ++i){
if(!visited[i]){
visited[i] = TRUE;
visit(G, i);
EnQueue(q, i);
while(!QueueEmpty){
DqQueue(q, i);
for(int j = 0; j < G.vexnum; ++j){
if(G.arcs[i][j] == 1 && !visited[j]){
visited[j] = TRUE;
visit(G, j);
EnQueue(q, j);
}
}
}
}
}
}
2. 邻接表的广度优先遍历
在实现邻接表的广度优先遍历时,初始化操作和邻接矩阵是一样的,我们需要对每一个结点都进行广度优先遍历,当一个未访问过的结点入队后,我就开始访问他的邻接点,通过指针p逐个进行访问,直到最后所有邻接点访问完后继续访问BFS其他点。
void BFSTraverse(GraphAdjList GL){
EdgeNode *p;
Queue q;
for(int i = 0; i < GL.vexnum; ++i){
visited[i] = FALSE:
}
InitQueue(q);
for(int i = 0; i < GL.vexnum; ++i){
if(!visited[i]){
visited[i] = TRUE;
visit(i);
EnQueue(q, i);
while(!QueueEmpty(q)){
DeQueue(q, i);
p = GL.adjlist[i].firstedge;
while(p){
if(!visited[p -> adjvex]){
visited[p -> adjvex] = TRUE;
visit(GL, p -> adjvex);
EnQueue(q, p -> adjvex)
}
p = p -> next;
}
}
}
}
}