王道数据结构源码实战ch6图
邻接矩阵结构体定义
typedef struct
{
int VexNum; //顶点数
int ArcNum; //边数
VertexType Vex[MaxVertexNum]; //顶点表
EdgeType Edge [MaxVertexNum][MaxVertexNum]; //边表
} MGraph;
邻接表结构体定义
- 这里为了方便自顶向下的理解,实际写代码时应先写内部最小的结构体,最后再写最大的。
邻接表总的结构体(定点数+边数+头部结构体数组)
typedef struct //邻接表
{
int vexnum; //顶点数
int edgnum; //边数
VNode vexs[MAX]; //邻接表的头部组成的数组
} ALGraph;
邻接表的头部节点结构体(顶点信息+链表节点指针)
typedef struct VNode //邻接表的头部(表的顶点)
{
char data; //存放顶点信息(可能很复杂)
ArcNode *first_edge; //指向第一条跟着这个顶点的弧
} VNode;
存放弧的链表节点结构体(指向的顶点+链表节点指针)
typedef struct ArcNode //邻接表中链表的节点
{
int adjvex; //该弧指向的顶点的下标,好处是节省空间,不需要存储节点的其他复杂信息,就能找到该节点
struct ArcNode* next_edge; //指向存放下一条弧的节点的指针
} ArcNode;
创建无向图
- 可以输入外部数据或使用写死的数据,得到一个顶点表和一个边表
动态分配内存创建图pG,分别初始化顶点数、边数、邻接表的头部节点,邻接表的弧节点(由于是无向图,弧数是边数的两倍,制作节点时,每条边需要分成方向相反的两条弧)
ALGraph* creat_example_algraph() //创建无向图
{
//写死的一组数据
char vexs[]= {'A','B','C','D','E','F','G'}; //7个顶点
char edges[][2]=
{
{'A','C'},
{'A','D'},
{'A','F'},
{'B','C'},
{'C','D'},
{'E','G'},
{'F','G'},
};
int vlen=LENGTH(vexs); //节点数
int elen=LENGTH(edges); //边数
//开始创建图pG的邻接表
char c1,c2; //分别存放某条边起点和终点
int pos1,pos2; //分别存放某条边起点和终点的顶点 下标
ArcNode* node1,*node2; //存放边的链表节点
ALGraph* pG; //代表图(指向图的指针)
pG=(ALGraph*)malloc(sizeof(ALGraph));
//给图pG申请空间
memset(pG,0,sizeof(ALGraph)); //全部置0
pG->vexnum=vlen; //初始化定点数
pG->edgnum=elen; //初始化边数
for(int i=0; i<pG->vexnum; i++) //初始化邻接表的头部
{
pG->vexs[i].data=vexs[i]; //放入顶点的信息(非编号)
pG->vexs[i].first_edge=NULL; //指针域置空
}
for(int i=0; i<pG->edgnum; i++) //初始化邻接表的边
{
c1=edges[i][0]; //拿第i条边的起点
c2=edges[i][1]; //拿第i条边的终点
pos1=get_position(*pG,c1); //起点的下标。对pG解引用,所以传入图结构体本身
pos2=get_position(*pG,c2); //终点的下标
//初始化node1
node1=(ArcNode*)calloc(1,sizeof(ArcNode));
node1->adjvex=pos2; //存放边的终点顶点的下标
//node1->next_edge=NULL //calloc自动清空内存,并把指针置为NULL,所以指针不用手动置为NULL了
//因为边的起点是下标为pos1的顶点,所以接在它的头的后面
if(pG->vexs[pos1].first_edge==NULL)
pG->vexs[pos1].first_edge=node1; //邻接表头后面没节点,直接把当前node1接上去
else
link_last(pG->vexs[pos1].first_edge,node1); //否则把当前node1接到这个链表的尾部
//初始化node2,因为是无向图,所以要反向再最一遍
node2=(ArcNode*)calloc(1,sizeof(ArcNode));
node2->adjvex=pos1;
if(pG->vexs[pos2].first_edge==NULL)
pG->vexs[pos2].first_edge=node2;
else
link_last(pG->vexs[pos2].first_edge,node2);
}
return pG;
}
创建有向图
- 只需要跟据边表里的每条弧的前后顺序,建立邻接表,不需要反方向的弧。
ALGraph* creat_example_algraph_directed() //创建有向图
{
//写死的一组数据
char vexs[]= {'A','B','C','D','E','F','G'}; //7个顶点
char edges[][2]=
{
{'A','C'},
{'A','D'},
{'A','F'},
{'B','C'},
{'C','D'},
{'E','G'},
{'F','G'},
};
int vlen=LENGTH(vexs); //节点数
int elen=LENGTH(edges); //边数
//开始创建图pG的邻接表
char c1,c2; //分别存放某条边起点和终点
int pos1,pos2; //分别存放某条边起点和终点的顶点 下标
ArcNode* node1,*node2; //存放边的链表节点
ALGraph* pG; //代表图(指向图的指针)
pG=(ALGraph*)malloc(sizeof(ALGraph));
//给图pG申请空间
memset(pG,0,sizeof(ALGraph)); //全部置0
pG->vexnum=vlen; //初始化定点数
pG->edgnum=elen; //初始化边数
for(int i=0; i<pG->vexnum; i++) //初始化邻接表的头部
{
pG->vexs[i].data=vexs[i]; //放入顶点的信息(非编号)
pG->vexs[i].first_edge=NULL; //指针域置空
}
for(int i=0; i<pG->edgnum; i++) //初始化邻接表的边
{
c1=edges[i][0]; //拿第i条边的起点
c2=edges[i][1]; //拿第i条边的终点
pos1=get_position(*pG,c1); //起点的下标。对pG解引用,所以传入图结构体本身
pos2=get_position(*pG,c2); //终点的下标
//初始化node1
node1=(ArcNode*)calloc(1,sizeof(ArcNode));
node1->adjvex=pos2; //存放边的终点顶点的下标
//node1->next_edge=NULL //calloc自动清空内存,并把指针置为NULL,所以指针不用手动置为NULL了
//因为边的起点是下标为pos1的顶点,所以接在它的头的后面
if(pG->vexs[pos1].first_edge==NULL)
pG->vexs[pos1].first_edge=node1; //邻接表头后面没节点,直接把当前node1接上去
else
link_last(pG->vexs[pos1].first_edge,node1); //否则把当前node1接到这个链表的尾部
}
return pG;
}
深度优先遍历(DFS)
- 为了考虑非连通图的情况,对每个连通分量都进行DFS
打个比方:在一个小岛上探索完所有点时,直接空降到另一个小岛上,直到探索完所有地方。
0.首先建立一个访问标记数组并置空,表示都没访问过
void DFSTraverse(ALGraph G) //假如图不是连通的,就需要重新找点多次DFS
{
//初始化所有顶点都没被访问
int visited[MAX];
for(int i=0; i<G.vexnum; i++) //对每个连通分量使用DFS
visited[i]=0;
cout<<"DFS:"<<endl;
for(int i=0; i<G.vexnum; i++)
{
if(!visited[i])
DFS(G,i,visited);
}
cout<<endl;
}
- 对于某个连通分量,进行DFS
1.使用下标为i的点作为遍历起点,首先把这个点标记已访问,输出
2.使用一个工作指针进行遍历,如果当前顶点邻接表后面的第一条弧指向的顶点没被访问过,每次递归的取当前顶点(上一层顶点后面的第一条弧指向的顶点)邻接表后面的第一条弧指向的顶点,一路走到底。
3.无路可走回退到本层时,取本顶点邻接表下一条弧继续,直到没有顶点未被访问。
static void DFS(ALGraph G,int i,int visited[])
{
ArcNode* node; //工作指针,指向存放边的链表节点
visited[i]=1; //访问标记
cout<<G.vexs[i].data<<" ";
node=G.vexs[i].first_edge; //从当前顶点的后面跟的第一条边开始遍历
while(node!=NULL)
{
if(!visited[i])
DFS(G,node->adjvex,visited); //没访问过,深入下一个顶点访问
node=node->next_edge; //回退到这一层时,访问这个顶点的后面跟的下一条边
}
}
广度优先遍历(BFS)
0.首先建立一个访问标记数组并置空,和一个存放顶点的**辅助队列
对于每个连通分量,进行BFS,使用下标为i的点作为遍历起点
1.首先把这个点标记已访问,输出,并入队
2.当队列非空时,做以下事情:
i队头元素出队,依次访问队头元素所有的弧(从邻接表中顶点的第一条弧开始)指向的顶点(3个步骤,同1),并依次把这些顶点入队(访问一个入队一个)。
void BFS(ALGraph G)
{
int front=0,rear=0;
int queue[MAX]; //建立辅助队列
int visited[MAX]; //标技数组
ArcNode* node;
for(int i=0; i<G.vexnum; i++) //标记数组置0
visited[i]=0;
cout<<"BFS:"<<endl;
for(int i=0; i<G.vexnum; i++) //对每个连通分量遍历
{
if(!visited[i]) //标记打印入队
{
visited[i]=1;
cout<<G.vexs[i].data<<" ";
queue[rear++]=i; //尾指针原本指向尾部元素的后面,所以先入队,指针再++
}
while(front!=rear) //队列非空
{
int j=queue[front++]; //出队
node=G.vexs[j].first_edge; //取刚刚出队的顶点跟着的第一条边
while(node!=NULL) //把刚刚出队的顶点的每一条边都访问
{
int k=node->adjvex;
if(!visited[k]) //与第一个顶点的访问完全一样
{
visited[k]=1;
cout<<G.vexs[k].data<<" ";
queue[rear++]=k;
}
node=node->next_edge; //下一条边
}
}
}
cout<<endl;
}
完整代码
#include<bits/stdc++.h>
using namespace std;
#define MaxVertexNum 100
#define MAX 100
typedef char VertexType; //顶点一般用字母表示
typedef int EdgeType; //边的权值一般为数字
#define isLetter(a) ((((a)>='a')&&((a)<='z')) || (((a)>='A')&&((a)<='Z')))
#define LENGTH(a) (sizeof(a)/sizeof(a[0])) //宏定义
//邻接矩阵结构体定义
typedef struct
{
int VexNum; //顶点数
int ArcNum; //边数
VertexType Vex[MaxVertexNum]; //顶点表
EdgeType Edge [MaxVertexNum][MaxVertexNum]; //边表
} MGraph;
//邻接表结构体定义
typedef struct ArcNode //邻接表中链表的节点
{
int adjvex; //该边指向的顶点的下标,好处是节省空间,不需要存储节点的其他复杂信息,就能找到该节点
struct ArcNode* next_edge; //指向下一条边的指针
} ArcNode;
typedef struct VNode //邻接表的头部(表的顶点)
{
char data; //存放顶点信息(可能很复杂)
ArcNode *first_edge; //指向第一条跟着这个顶点的边
} VNode;
typedef struct //邻接表
{
int vexnum; //顶点数
int edgnum; //边数
VNode vexs[MAX]; //邻接表的头部组成的数组
} ALGraph;
//由图的顶点信息来获得顶点编号,为后续存储邻接表节省空间
static int get_position(ALGraph g,char ch) //不改变图,所以不需要传指针或者引用
{
for(int i=0; i<g.vexnum; i++) //遍历存放邻接表头部的数组
if(g.vexs[i].data==ch)
return i; //返回对应顶点在数组中的下标
return -1;
}
static void link_last(ArcNode *head,ArcNode*node) //在邻接表头部顶点head的最后面插入node
{
ArcNode* p=head; //用工作指针p遍历
while(p->next_edge!=NULL)
p=p->next_edge;
p->next_edge=node;
}
// 读取一个输入字符
static char read_char()
{
char ch;
do
{
ch = getchar();
}
while(!isLetter(ch)); //先读一个,直到不是字母为止,对比单独的getchar(),可以防止把回车误读入
return ch;
}
void print_adjacency_table(ALGraph G) //打印邻接表
{
ArcNode *node; //工作指针,找到每个头,后面接的第1个顶点
cout<<"adjacency table"<<endl;
for(int i=0; i<G.vexnum; i++)
{
printf("%d(%c): ", i, G.vexs[i].data); //先打印每个顶点的下标号和本身的信息
node=G.vexs[i].first_edge;
while(node!=NULL)
{
printf("%d(%c) ", node->adjvex, G.vexs[node->adjvex].data); //打印每条边终点顶点是编号,和本身的信息
node=node->next_edge; //向后走
}
cout<<endl;
}
}
//创建邻接表对应的图(自己输入)
ALGraph* create_lgraph()
{
char c1, c2;
int v, e;
int i, p1, p2;
ArcNode *node1, *node2;
ALGraph* pG;
// 输入"顶点数"和"边数"
printf("input vertex number: ");
scanf("%d", &v);
printf("input edge number: ");
scanf("%d", &e);
if ( v < 1 || e < 1 || (e > (v * (v-1))))
{
printf("input error: invalid parameters!\n");
return NULL;
}
if ((pG=(ALGraph*)malloc(sizeof(ALGraph))) == NULL )
return NULL;
memset(pG, 0, sizeof(ALGraph));
// 初始化"顶点数"和"边数"
pG->vexnum = v;
pG->edgnum = e;
// 初始化"邻接表"的顶点
for(i=0; i<pG->vexnum; i++)
{
printf("vertex(%d): ", i);
pG->vexs[i].data = read_char();
pG->vexs[i].first_edge = NULL;
}
// 初始化"邻接表"的边
for(i=0; i<pG->edgnum; i++)
{
// 读取边的起始顶点和结束顶点
printf("edge(%d): ", i);
c1 = read_char();
c2 = read_char();
p1 = get_position(*pG, c1);
p2 = get_position(*pG, c2);
// 初始化node1
node1 = (ArcNode*)calloc(1,sizeof(ArcNode));
node1->adjvex = p2;
// 将node1链接到"p1所在链表的末尾"
if(pG->vexs[p1].first_edge == NULL)
pG->vexs[p1].first_edge = node1;
else
link_last(pG->vexs[p1].first_edge, node1);
// 初始化node2
node2 = (ArcNode*)calloc(1,sizeof(ArcNode));
node2->adjvex = p1;
// 将node2链接到"p2所在链表的末尾"
if(pG->vexs[p2].first_edge == NULL)
pG->vexs[p2].first_edge = node2;
else
link_last(pG->vexs[p2].first_edge, node2);
}
return pG;
}
ALGraph* creat_example_algraph() //创建无向图
{
//写死的一组数据
char vexs[]= {'A','B','C','D','E','F','G'}; //7个顶点
char edges[][2]=
{
{'A','C'},
{'A','D'},
{'A','F'},
{'B','C'},
{'C','D'},
{'E','G'},
{'F','G'},
};
int vlen=LENGTH(vexs); //节点数
int elen=LENGTH(edges); //边数
//开始创建图pG的邻接表
char c1,c2; //分别存放某条边起点和终点
int pos1,pos2; //分别存放某条边起点和终点的顶点 下标
ArcNode* node1,*node2; //存放边的链表节点
ALGraph* pG; //代表图(指向图的指针)
pG=(ALGraph*)malloc(sizeof(ALGraph));
//给图pG申请空间
memset(pG,0,sizeof(ALGraph)); //全部置0
pG->vexnum=vlen; //初始化定点数
pG->edgnum=elen; //初始化边数
for(int i=0; i<pG->vexnum; i++) //初始化邻接表的头部
{
pG->vexs[i].data=vexs[i]; //放入顶点的信息(非编号)
pG->vexs[i].first_edge=NULL; //指针域置空
}
for(int i=0; i<pG->edgnum; i++) //初始化邻接表的边
{
c1=edges[i][0]; //拿第i条边的起点
c2=edges[i][1]; //拿第i条边的终点
pos1=get_position(*pG,c1); //起点的下标。对pG解引用,所以传入图结构体本身
pos2=get_position(*pG,c2); //终点的下标
//初始化node1
node1=(ArcNode*)calloc(1,sizeof(ArcNode));
node1->adjvex=pos2; //存放边的终点顶点的下标
//node1->next_edge=NULL //calloc自动清空内存,并把指针置为NULL,所以指针不用手动置为NULL了
//因为边的起点是下标为pos1的顶点,所以接在它的头的后面
if(pG->vexs[pos1].first_edge==NULL)
pG->vexs[pos1].first_edge=node1; //邻接表头后面没节点,直接把当前node1接上去
else
link_last(pG->vexs[pos1].first_edge,node1); //否则把当前node1接到这个链表的尾部
//初始化node2,因为是无向图,所以要反向再最一遍
node2=(ArcNode*)calloc(1,sizeof(ArcNode));
node2->adjvex=pos1;
if(pG->vexs[pos2].first_edge==NULL)
pG->vexs[pos2].first_edge=node2;
else
link_last(pG->vexs[pos2].first_edge,node2);
}
return pG;
}
ALGraph* creat_example_algraph_directed() //创建有向图
{
//写死的一组数据
char vexs[]= {'A','B','C','D','E','F','G'}; //7个顶点
char edges[][2]=
{
{'A','C'},
{'A','D'},
{'A','F'},
{'B','C'},
{'C','D'},
{'E','G'},
{'F','G'},
};
int vlen=LENGTH(vexs); //节点数
int elen=LENGTH(edges); //边数
//开始创建图pG的邻接表
char c1,c2; //分别存放某条边起点和终点
int pos1,pos2; //分别存放某条边起点和终点的顶点 下标
ArcNode* node1,*node2; //存放边的链表节点
ALGraph* pG; //代表图(指向图的指针)
pG=(ALGraph*)malloc(sizeof(ALGraph));
//给图pG申请空间
memset(pG,0,sizeof(ALGraph)); //全部置0
pG->vexnum=vlen; //初始化定点数
pG->edgnum=elen; //初始化边数
for(int i=0; i<pG->vexnum; i++) //初始化邻接表的头部
{
pG->vexs[i].data=vexs[i]; //放入顶点的信息(非编号)
pG->vexs[i].first_edge=NULL; //指针域置空
}
for(int i=0; i<pG->edgnum; i++) //初始化邻接表的边
{
c1=edges[i][0]; //拿第i条边的起点
c2=edges[i][1]; //拿第i条边的终点
pos1=get_position(*pG,c1); //起点的下标。对pG解引用,所以传入图结构体本身
pos2=get_position(*pG,c2); //终点的下标
//初始化node1
node1=(ArcNode*)calloc(1,sizeof(ArcNode));
node1->adjvex=pos2; //存放边的终点顶点的下标
//node1->next_edge=NULL //calloc自动清空内存,并把指针置为NULL,所以指针不用手动置为NULL了
//因为边的起点是下标为pos1的顶点,所以接在它的头的后面
if(pG->vexs[pos1].first_edge==NULL)
pG->vexs[pos1].first_edge=node1; //邻接表头后面没节点,直接把当前node1接上去
else
link_last(pG->vexs[pos1].first_edge,node1); //否则把当前node1接到这个链表的尾部
}
return pG;
}
static void DFS(ALGraph G,int i,int visited[])
{
ArcNode* node; //工作指针,指向存放边的链表节点
visited[i]=1; //访问标记
cout<<G.vexs[i].data<<" ";
node=G.vexs[i].first_edge; //从当前顶点的后面跟的第一条边开始遍历
while(node!=NULL)
{
if(!visited[i])
DFS(G,node->adjvex,visited); //没访问过,深入下一个顶点访问
node=node->next_edge; //回退到这一层时,访问这个顶点的后面跟的下一条边
}
}
void DFSTraverse(ALGraph G) //假如图不是连通的,就需要重新找点多次DFS
{
//初始化所有顶点都没被访问
int visited[MAX];
for(int i=0; i<G.vexnum; i++) //对每个连通分量使用DFS
visited[i]=0;
cout<<"DFS:"<<endl;
for(int i=0; i<G.vexnum; i++)
{
if(!visited[i])
DFS(G,i,visited);
}
cout<<endl;
}
void BFS(ALGraph G)
{
int front=0,rear=0;
int queue[MAX]; //建立辅助队列
int visited[MAX]; //标技数组
ArcNode* node;
for(int i=0; i<G.vexnum; i++) //标记数组置0
visited[i]=0;
cout<<"BFS:"<<endl;
for(int i=0; i<G.vexnum; i++) //对每个连通分量遍历
{
if(!visited[i]) //标记打印入队
{
visited[i]=1;
cout<<G.vexs[i].data<<" ";
queue[rear++]=i; //尾指针原本指向尾部元素的后面,所以先入队,指针再++
}
while(front!=rear) //队列非空
{
int j=queue[front++]; //出队
node=G.vexs[j].first_edge; //取刚刚出队的顶点跟着的第一条边
while(node!=NULL) //把刚刚出队的顶点的每一条边都访问
{
int k=node->adjvex;
if(!visited[k]) //与第一个顶点的访问完全一样
{
visited[k]=1;
cout<<G.vexs[k].data<<" ";
queue[rear++]=k;
}
node=node->next_edge; //下一条边
}
}
}
cout<<endl;
}
int main()
{
//打印有向图的邻接表
cout<<"Undirected graph"<<endl;
ALGraph *pG2;
pG2=creat_example_algraph_directed();
print_adjacency_table(*pG2);
cout<<endl;
//打印无向图的邻接表
cout<<"Directed graph"<<endl;
ALGraph *pG1;
pG1=creat_example_algraph();
print_adjacency_table(*pG1);
//遍历
DFSTraverse(*pG1);
BFS(*pG1);
cout<<endl;
//无向图读入外部数据测试
/*
7
7
A
B
C
D
E
F
G
A
C
A
D
A
F
B
C
C
D
E
G
F
G
*/
cout<<"Undirected graph"<<endl;
ALGraph *pG3;
pG3=create_lgraph();
print_adjacency_table(*pG3);
DFSTraverse(*pG3);
BFS(*pG3);
return 0;
}