目录
定义
邻接表存储图
深度优先遍历
广度优先遍历:
BFS部分
完整代码
要实现该算法首先要知道邻接表的概念。
邻接表是一种常用的图的存储结构,它的结构特点是:
顶点由一个一维数组存储;
邻接点用链表存储
相比于单纯用数组实现的邻接矩阵,邻接表可以避免空间浪费
其图解如下:
firstedge指向边表第一个结点。
边表的adjvex的值代表与V0顶点有边的顶点下标,例:
A的firstedge指向边表的1而后指向2,3,意思是A与B,C,D各有一条共同的边。
算法的时间复杂度:对于n个顶点e条边的图,找邻接点所需的时间取决于顶点和边的数量,故为O(n+e)
邻接表结构代码实现如下:
/*邻接表结构*/
typedef char VertexType; //顶点类型
typedef int EdgeType; //权值类型
/*边表结点*/
typedef struct EdgeNode
{
int adjvex; //邻接点域,保存邻接点下标
EdgeType weight; //存储权值,非网图则不需要
struct EdgeNode *next; //链域,指向下一个邻接点
}EdgeNode;
typedef struct VertexNode
{
VertexType data; //顶点域
EdegeNode *firstedge; //边表头指针
}VertexNode,AdList[MAX];
typedef struct
{
AdjList adjList;
int numVertexes,numEdges; //顶点数量和边数量
}GraphAdjList,*GraphAdj;
创建邻接表:
/*邻接表创建*/
void create(GraphAdj G)
{
int i,j,k;
EdgeNode *e;
printf("输入顶点数,边数:");
scanf("%d%d",&G->numVertexes,&G->numEdges);
for(i=0;inumVertexes;i++) //建立顶点表
{
scanf("%c",&G->adjList[i].data);
G->adjList[i].firstedge=NULL; //注意将边表置空
}
for(k=0;knumEdges;k++) //建立边表
{
printf("输入边(Vi,Vj)上的顶点序号:");
scanf("%d%d",&i,&j);
/*使用头插法加入边表结点*/
e=(EdgeNode *)malloc(sizeof(EdgeNode)); //生成边表结点
e->adjvex=j;
e->next=G->adjList[i].firstedge;
G->adjList[i].firstedge=e;
e=(EdgeNode *)malloc(sizeof(EdgeNode)); //生成边表结点
e->adjvex=i;
e->next=G->adjList[j].firstedge;
G->adjList[j].firstedge=e;
}
}
邻接表的深度优先遍历同邻接矩阵的深度优先遍历大同小异,都需要创建一个标志数组,数组存放bool类型成员TRUE,FALSE。 其中TRUE表示已经访问过。
不同点:在递归函数中需要声明一个EdgeNode *类型的变量p来进行边表的遍历,也就是在边表的链表中遍历邻接点。
/*邻接表的深度优先递归*/
void DFS(GraphAdj G,int i)
{
EdgeNode *p;
visited[i]=TRUE; //访问过了该顶点,标记为TRUE
printf("%c",G->adjList[i].data);
p=G->adjList[i].firstedge; //让p指向边表第一个结点
while(p) //在边表内遍历
{
if(!visited[p->adjvex]) //对未访问的邻接顶点递归调用
DFS(G,p->adjvex);
p=p->next;
}
}
//邻接表的深度遍历操作
void DFSTraverse(GraphAdj G)
{
int i;
for(i=0;inumVertexes;i++)
visited[i]=FALSE; //初始设置为未访问
for(i=0;inumVertexes;i++)
if(!visited[i])
DFS(G,i); //对未访问的顶点调用DFS,若是连通图只会执行一次
}
使用循环队列实现:
typedef struct LoopQueue{ //定义循环队列结构体
int data[MAX];
int front;
int rear; //注意每次队尾标记指向最后一个元素的下一个位置
}Queue, *LQueue;
需要手动实现队列的各种操作:
void InitQueue(LQueue &Q){ //初始化队列
Q->front = Q->rear = 0;
}
bool QueueisFull(LQueue &Q){ //判断队列是否满了
if((Q->rear + 1) % MAX == Q->front){
return true; //已满
}
else{
return false;
}
}
bool QueueisEmpty(LQueue &Q){//判断队列是否为空
if(Q->front == Q->rear){
return true;
}
return false;
}
void EnQueue(LQueue &Q, int i){ //入队列
if(!QueueisFull(Q)){
Q->data[Q->rear] = i;
Q->rear = (Q->rear + 1) % MAX; //队尾指针后移
}
}
void DeQueue(LQueue &Q, int *k){ //出队列
if(!QueueisEmpty(Q)){
*k = Q->data[Q->front];
Q->front = (Q->front + 1) % MAX;
}
}
该算法的思路就是从初始位置顶点开始,遍历所有的邻接顶点并将其加入队列,当一个顶点的所有邻接点都遍历完了之后就把该顶点出队列,拿出下一个顶点,然后不断的重复该过程
/*广度优先遍历*/
void BFS(GraphAdj G){
Queue *Q =(LQueue)malloc(sizeof(Queue));
for(int i = 0; i < G->numVertexes; i++){
visited[i] = FALSE;
}
InitQueue(Q); //初始化队列
for(int i = 0; i < G->numVertexes; i++){
visited[i] = TRUE;
printf("\t%c", G->adjList[i].data);
EnQueue(Q, i);
while(!QueueisEmpty(Q)){
DeQueue(Q, &i); //这里不断的修改i的值!!
EdgeNode *e = G->adjList[i].firstedge; //i顶点的邻接链表的第一个结点
while(e){//e存在时,将e的所有邻接点加入队列,也就是遍历i的所有邻接点
if(!visited[e->adjvex]){ // adjvex是e所表示的结点下标
visited[e->adjvex] = TRUE;
printf("\t%c", G->adjList[e->adjvex].data);
EnQueue(Q, e->adjvex); //将该结点入队
}
e = e->next; //遍历i的下一个邻接点
}
}
}
}
#include
#include
#define MAX 10
#define INIFINITY 65535
#define TRUE 1
#define FALSE 0
typedef int Boole; //布尔类型 存储TRUE FALSE
Boole visited[MAX]; //访问标志数组
//邻接表结点定义
typedef char VertexType; //顶点数据类型
typedef int EdgeType; //边上的权值类型
typedef struct EdgeNode //边表结点 存储边表信息
{
int adjvex; //邻接点域,存储该顶点对应的下标
EdgeType weight; //权值
struct EdgeNode *next; //链域,指向下一个邻接点
}EdgeNode;
typedef struct VertexNode //顶点表结点
{
VertexType data; //顶点域,存储顶点信息
EdgeNode *firstedge; //边表头指针,指向此顶点的第一个邻接点
}VertexNode,AdjList[MAX];
typedef struct
{
AdjList adjList;
int numVertexes,numEdges; //图中当前顶点数和边数
}GraphAdjList,*GraphAdj;
typedef struct LoopQueue{ //定义循环队列结构体
int data[MAX];
int front;
int rear; //注意每次队尾标记指向最后一个元素的下一个位置
}Queue, *LQueue;
void InitQueue(LQueue &Q){ //初始化队列
Q->front = Q->rear = 0;
}
bool QueueisFull(LQueue &Q){ //判断队列是否满了
if((Q->rear + 1) % MAX == Q->front){
return true; //已满
}
else{
return false;
}
}
bool QueueisEmpty(LQueue &Q){//判断队列是否为空
if(Q->front == Q->rear){
return true;
}
return false;
}
void EnQueue(LQueue &Q, int i){ //入队列
if(!QueueisFull(Q)){
Q->data[Q->rear] = i;
Q->rear = (Q->rear + 1) % MAX; //队尾指针后移
}
}
void DeQueue(LQueue &Q, int *k){ //出队列
if(!QueueisEmpty(Q)){
*k = Q->data[Q->front];
Q->front = (Q->front + 1) % MAX;
}
}
/*邻接表创建*/
void create(GraphAdj G)
{
int i,j,k;
EdgeNode *e;
printf("输入顶点数和边数:");
scanf("%d%d",&G->numVertexes,&G->numEdges);
getchar(); //注意要清除缓冲
for(i=0;inumVertexes;i++) //建立顶点表
{
scanf("%c",&G->adjList[i].data); //输入顶点的符号
G->adjList[i].firstedge=NULL; //将边表置空
getchar();
}
for(k=0;knumEdges;k++) //建立边表
{
printf("输入边(Vi,Vj)上的顶点序号:");
scanf("%d%d",&i,&j);
/*使用头插法加入边表结点*/
e=(EdgeNode *)malloc(sizeof(EdgeNode)); //生成结点
e->adjvex=j;
e->next=G->adjList[i].firstedge;
G->adjList[i].firstedge=e;
e=(EdgeNode *)malloc(sizeof(EdgeNode)); //生成结点
e->adjvex=i;
e->next=G->adjList[j].firstedge;
G->adjList[j].firstedge=e;
}
printf("\n");
}
/*邻接表的深度优先递归*/
void DFS(GraphAdj G,int i)
{
EdgeNode *p;
visited[i]=TRUE; //访问过了该顶点,标记为TRUE
printf("\t%c",G->adjList[i].data);
p=G->adjList[i].firstedge; //让p指向边表第一个结点
while(p) //在边表内遍历
{
if(!visited[p->adjvex]) //对未访问的邻接顶点递归调用
DFS(G,p->adjvex);
p=p->next;
}
}
//邻接表的深度遍历操作
void DFSTraverse(GraphAdj G)
{
int i;
for(i=0;inumVertexes;i++)
visited[i]=FALSE; //初始设置为未访问
for(i=0;inumVertexes;i++)
if(!visited[i]) //对未访问的顶点调用DFS,若是连通图只会执行一次
DFS(G,i);
}
/*广度优先遍历*/
void BFS(GraphAdj G){
Queue *Q =(LQueue)malloc(sizeof(Queue));
for(int i = 0; i < G->numVertexes; i++){
visited[i] = FALSE;
}
InitQueue(Q); //初始化队列
for(int i = 0; i < G->numVertexes; i++){
visited[i] = TRUE;
printf("\t%c", G->adjList[i].data);
EnQueue(Q, i);
while(!QueueisEmpty(Q)){
DeQueue(Q, &i); //这里不断的修改i的值!!
EdgeNode *e = G->adjList[i].firstedge; //i顶点的邻接链表的第一个结点
while(e){//e存在时,将e的所有邻接点加入队列,也就是遍历i的所有邻接点
if(!visited[e->adjvex]){ // adjvex是e所表示的结点下标
visited[e->adjvex] = TRUE;
printf("\t%c", G->adjList[e->adjvex].data);
EnQueue(Q, e->adjvex); //将该结点入队
}
e = e->next; //遍历i的下一个邻接点
}
}
}
}
int main()
{
GraphAdjList G;
create(&G);
printf("深度优先遍历为:");
DFSTraverse(&G);
printf("\n");
printf("广度优先遍历为:");
BFS(&G);
printf("\n图遍历完毕");
return 0;
}
错误警示:
一开始我在main函数中写的是
int main()
{
GraphAdj G;
create(G);
printf("\n");
DFSTraverse(G);
printf("\n图遍历完毕");
return 0;
}
可是运行时会出现问题,后来我才知道,GraphAdj G需要初始化,指针变量在分配了内存空间,即先要有指向之后才可以引用其值
但若是在cpp中可以把create函数的形参改成:
GraphAdj &G
&的意思是传进来节点指针的引用,括号内等价于 GraphAdj* &G,目的是让传递进来的指针发生改变
使用引用后,main函数就可以写成上述的形式了。