目录
1.邻接表(无向图)
为避免邻接矩阵对于边数相对顶点较小的图会产生对存储空间的浪费,我们引入一种把数组与链表结合一起来存储的方法,即邻接表(AdjacencyList)。
邻接表的处理方式是:图中顶点用一个一维数组存储,当然,顶点也可以用单链表来存储,不过数组可以较容易的读取顶点信息,更加方便。图中每个顶点Vi的所有邻接点构成一个线性表,因为邻接点的个数不确定,所以我们选择用单链表来存储。
2.邻接表(有向图)
对于有向图来说,邻接表结构也是类似的,把顶点当弧尾建立的邻接表,这样可以很容易的得到每个顶点的出度:
逆邻接表:有时为了便于确定顶点的入度或以顶点为弧头顶的弧,我们可以建立一个有向图的逆邻接表,即first指向它的上一个顶点。
3.十字链表
为了弥补邻接表对有向图的处理上有时候需要再建立一个逆邻接表的不足,我们引入一个把邻接表和逆邻接表结合起来的结构:十字链表。
为此我们重新定义顶点表结点结构:
data | firstIn | firstOut |
data即顶点的数据;firstIn是第一个入边表的指针;firstOut是第一个出边表的指针
接着我们重新定义边表结点结构:
tailVex | headVex | headLink | tailLink |
tailVex表示弧起点的顶点的下标;headVex表示弧终点的顶点的下标;headLink和tailLink分别表示相应入度和出度的指针。
十字链表的优点就是把邻接表和逆邻接表整合再了一起,这样既容易找到以Vi为尾的弧,也容易找到以Vi为头的弧,因而容易求得顶点的入度和出度。十字链表除了结构复杂一点外,其实创建图算法的时间复杂度是和邻接表相同的,因此,在有向图的应用中,十字链表是非常好的数据结构模型。
4.邻接多重表
如果我们在无向图的应用中关注的重点是顶点的话,那么邻接表是不错的选择,但如果我们更关注的是边的操作,比如对已经访问过的边做标记,或者删除某一条边等操作,邻接表就显得没有这么高效了。因此,我们仿照十字链表的方式对边表结构进行改装,改装定义的边表结构如下:
iVex | iLink | jVex | JLInk |
其中iVex和jVex是与某条边依附的两个顶点在顶点表中的下标。iLink指向依附顶点iVex的下一条边,jLink指向依附顶点jVex的下一条边。也就是说在邻接多重表里边,边表存放的是一条边,而不是一个顶点。
5.边集数组
边集数组是由两个一维数组组成,一个是存储顶点的信息,另一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标(begin)、终点下标(end)和权(weight)组成。
6.图的遍历
(1).深度优先遍历(类似于树的前序遍历)
我们可以约定右手原则:在没有碰到重复顶点的情况下,遇到分叉路口始终是向右手边走,每路过一个顶点就做一个记号,如果右手边是已经做过记号的顶点,那就往没有记号的顶点走;如果面临的都是有记号的顶点,就退回上一个顶点,接着判断有没有无记号的顶点可以走,直到最后退回起点,结束遍历。
假设从A开始,在A顶点做好标记后往右手边顶点B走,然后在B做好标记往右手边顶点C走,然后在C做好标记后往右手边D走,在D做好标记后往右手边E走,在E做好标记后往右手边F走,然后本来是往右手边A走,但是因为顶点A我们做了标记,所以判断还有没有未标记的顶点可以走,然后发现顶点G满足条件,所以走到G,接着依次判断右手边的B和D都有标记,H没有标记,选择走到H,然后判断H挨着的顶点都有标记,无路可走,所以退一步退到顶点G,接着继续判断有无可以走的结点,退到F、退到E、退到D、然后发现顶点I可以走,走到I,接着退到D、退到C、退到B、退到A起点,结束遍历。
代码实现(采用邻接表存储无向图):
#include<stdio.h>
#define MAX_VERTEX_NUM 20 //最大顶点数
typedef struct{
char vexs[MAX_VERTEX_NUM]; //一维数组存储顶点集
int AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //邻接表存储边集
int vexnum,arcnum; //图中顶点数和弧的数量
} MGraph;
int LocateVex(MGraph G,char vex){ //定位顶点在图中的位置
for(int i=0;i<G.vexnum;i++){
if(G.vexs[i] == vex)
return i;
}
return -1;
}
void CreateGraph(MGraph &G) { //创建图
//输入图中顶点
printf("请输入图中顶点数量:");
scanf("%d",&G.vexnum);
for(int i=0;i<G.vexnum;i++){
printf("请输入第%d个顶点:",i+1);
scanf(" %c",&G.vexs[i]);
}
//输入图中的边
printf("请输入图中边的数量:");
scanf("%d",&G.arcnum);
//初始化邻接矩阵
for(int i=0;i<G.vexnum;i++){
for(int j=0;j<G.vexnum;j++){
G.AdjMatrix[i][j] = 0; //初始化时顶点间都不存在边
}
}
//构造邻接矩阵
for(int k=0;k<G.arcnum;k++) {
char v1,v2;
printf("请输入第%d条边(如:AB):",k+1);
scanf(" %c%c",&v1,&v2);
int i = LocateVex(G,v1); //获取边第一个顶点在图中的位置
int j = LocateVex(G,v2); //获取边第二个顶点在图中的位置
G.AdjMatrix[i][j] = 1; //存在边就设置为1
G.AdjMatrix[j][i] = 1; //由于是无向图所以相反反向也存在边
}
printf("图创建成功!\n");
}
void VisitFun(MGraph G,int v){
printf("%c ",G.vexs[v]);
}
void DFS(MGraph G,int *visited,int v){
visited[v] = 1; //将访问的结点设置为1
//访问这个这个顶点
VisitFun(G,v);
//寻找与这个顶点相邻的其他结点
for(int k=0;k<G.vexnum;k++){
if(G.AdjMatrix[v][k]==1){ //有边
if(visited[k]==0){ //且该顶点没有被访问过
//那么就递归调用DFS去遍历与这个边邻接的顶点
DFS(G,visited,k);
}
}
}
}
main() {
int visited[MAX_VERTEX_NUM];
//初始化访问标记数组 顶点访问则设置为1
for(int i=0;i<MAX_VERTEX_NUM;i++){
visited[i] = 0;
}
MGraph G;
CreateGraph(G);
DFS(G,visited,0);
}
(2).广度优先遍历(类似于树的层序遍历)
广度优先遍历可以简单的理解为先遍历与顶点相挨着的所有顶点,并做好标记,然后遍历后面那些没有标记的顶点,并且每次都遍历所有与之相邻的所有没有标记过的顶点。
假设从A开始,与A相邻且没有标记的有B、F,与顶点B相邻且没有标记的有C、I、G,与F相邻且没有标记的有E,与顶点C相邻且没有标记的有D,与G相邻且没有标记的有H,最后遍历了D、E、H后没有发现满足相邻且没有标记的顶点,遍历结束。
代码实现(需要借助队列来实现):
# include<stdio.h>
# define MAX_VERTEX_NUM 20 //最多顶点个数
typedef int AdjType;
typedef char VertexData;
typedef struct ArcNode {
AdjType adj; //无权图用1或0表示是否相邻,带权图则为权值类型
}ArcNode;
typedef struct {
VertexData vertex[MAX_VERTEX_NUM]; //顶点向量
ArcNode arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; //邻接矩阵
int vexnum, arcnum; //图的顶点数和弧数
}AdjMatrix;
int LocateVertex(AdjMatrix* G, VertexData v) { //求顶点位置
int k;
for (k = 0; k < G->vexnum; k++) {
if (G->vertex[k] == v)
break;
}
return k;
}
int CreateAdjMatrix(AdjMatrix* G) { //创建无向图的邻接矩阵
int i, j, k;
VertexData v1, v2;
printf("输入图的顶点数和弧数(如3 3):"); //输入图的顶点数和弧数
scanf("%d%d", &G->vexnum, &G->arcnum);
for (i = 0; i < G->vexnum; i++) { //初始化邻接矩阵
for (j = 0; j < G->vexnum; j++)
G->arcs[i][j].adj = 0;
}
printf("输入图的顶点(如A B C):");
for (i = 0; i < G->vexnum; i++) //输入图的顶点
scanf(" %c", &G->vertex[i]);
for (k = 0; k < G->arcnum; k++) {
printf("输入第%d条弧的两个顶点(如AB):", k + 1);
scanf(" %c %c", &v1, &v2); //输入一条弧的两个顶点
i = LocateVertex(G, v1);
j = LocateVertex(G, v2);
G->arcs[i][j].adj = 1; //建立对称弧
G->arcs[j][i].adj = 1;
}
}
int visited[MAX_VERTEX_NUM] = { 0 }; //访问标志数组
void BreadthFirstSearch(AdjMatrix G, int v0) {
int queue[MAX_VERTEX_NUM]; //一维数组模拟队列操作
int rear = 0, front = 0, v; //模拟队尾指针和队头指针
printf("%c ", G.vertex[v0]);
visited[v0] = 1;
queue[rear] = v0; //模拟入队
while (rear >= front) {
v = queue[front]; //模拟取队头元素
front++; //模拟出队
for (int i = 0; i < G.vexnum; i++) {
if (G.arcs[v][i].adj == 1 && !visited[i]) {
printf("%c ", G.vertex[i]);
visited[i] = 1;
rear++;
queue[rear] = i; //模拟入队
}
}
}
}
int main() {
AdjMatrix G;
CreateAdjMatrix(&G);
printf("\n广度优先搜索:");
BreadthFirstSearch(G, 0);
return 0;
}
珍惜在世的时光,享受世界的美好。
谁也不知道明天和意外哪个先来。