广度优先和深度优先【搜索】(验证性实验)
1. 需求分析
要求:很多涉及图上操作的算法都是以图的遍历操作为基础的。试编写一个程序,演示无向图的遍历操作。
在主程序中提供下列菜单:
(1)“1”代表图的建立;
(2)“2”代表深度优先遍历图;
(3)“3”代表广度优先遍历图;
(4)“0”代表结束。
基本要求
以邻接表为存储结构,实现连通无向图的深度优先和广度优先遍历。以用户指定的结点为起点,分别输出每种遍历下的结点访问序列和相应生成树的边集。
分析:
(1) 输入的形式和输入值的范围:字符与int型数字
(2) 输出的形式:输出对应遍历的结果字符串。
(3) 程序所能达到的功能:建立图,深度优先遍历图,广度优先遍历图。
(4) 测试数据:
· DG 9 15
· A B C D E F G H I
· AB AF BG BC CD BI CI DI DG DH DE EH EF FG GH
2. 概要设计
- 为了实现程序功能,需要定义图的抽象数据类型。
ADT:图(Graph)
Data:顶点的有穷非空集合和边集合
Operation:
Graph(V):按照顶点集 VV 初始化图。
addVex(v):在图 G 中添加新顶点 vv。
deleteVex(v):删除图 G 中顶点 vv 及其相关弧。
addEdge(v1,v2):在图 G 中添加弧<v1,v2><v1,v2>,若 G 是无向图,则还需要增添对称弧 <v2,v1><v2,v1>。
deleteEdge(v1,v2):在图 G 中删除弧<v1,v2><v1,v2>,若 G 是无向图,则还需要删除对称弧 <v2,v1><v2,v1>。
DFSTraverse(v):从顶点 vv 开始对图 G 进行深度优先遍历。
BFSTraverse(v):从顶点 vv 开始对图 G 进行广度优先遍历。
3.具体代码
- 队列.h:
#define MAXQSIZE 31
#define qelemtype int
#define OVERFLOW -1
#define OK 1
#define ERROR 0
typedef struct{
qelemtype *base;//队列基准地址
int front;//头部
int rear;//尾部
}sqqueue;
void initqueue(sqqueue *q) //创建队列
{
q->base = (qelemtype *)malloc(MAXQSIZE*sizeof(qelemtype));
if(!q->base)
{
exit(OVERFLOW);
}
q->front = q->rear = 0;
}
int enqueue(sqqueue *q,qelemtype e) //添加队列节点
{
if((q->rear + 1) % MAXQSIZE == q->front)
{
return(ERROR);
}
q->base[q->rear] = e;
q->rear = (q->rear + 1) % MAXQSIZE;
return OK;
}
int dequeue(sqqueue *q,qelemtype *e) //移出队列节点
{
if(q->front == q->rear)
{
return ERROR;
}
*e = q->base[q->front];
q->front = (q->front + 1) % MAXQSIZE;
return OK;
}
int queueempty(sqqueue q) //判断队列是否为空
{
if(q.front == q.rear)
{
return OK;
}
else
{
return ERROR;
}
}
- 主程序.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"队列.h"
#define VertexType char
#define GraphKind char
//使用邻接表储存,优点很多
//如果用邻接矩阵来存的话,进行遍历的时候不方便,要把一整行全部扫描完才能确定连接的节点有多少
//而这个题目要求只要进行广度和深度优先遍历 ,不需要增加和删除节点,同时无向图也不需要考虑入度问题
//DG,DN,UDG,UDN分别表示 有向图,有向网,无向图,无向网
typedef struct ArcNode{
int adjvex;//边节点
struct ArcNode *nextarc;//下一个边节点
}ArcNode; //邻接表节点
typedef struct VNode{
VertexType vexdata;//顶点
ArcNode *firstarc;//边
}VNode; //邻接表结结构
typedef struct{
VNode *vertices;//vertices为vertex的复数,指向顶点【但由于是指针形式,所以可以当作数组基准地址】
int vexnum;//定点数
int arcnum;//边数
GraphKind kind[4];//图的种类
}ALGraph; //图结构
void visit(VNode v) //打印图节点
{
printf("%c ",v.vexdata);
}
int LocateVex(ALGraph *a,char ch) //查找图节点
{
int i = 0;//用于for循环
for(;i < a->vexnum;++i)//遍历所有节点
{
if(a->vertices[i].vexdata == ch)//如果某个节点数值等于查找值
{
return i;//返回节点序号i
}
}
exit(-1);//否则返回错误
}
void CreateGraph(ALGraph *a) //建图
{
printf("请输入图类,顶点数和边数:");
scanf("%s",&a->kind[0]);//%[^ ]s表示输入非空字符串,存在kind数组中【4位长度】
// getchar();
scanf("%d%d",&a->vexnum,&a->arcnum);//定点数和边数
getchar();
a->vertices = (VNode*)malloc(a->vexnum * sizeof(VNode));//足够的节点分配空间
printf("请输入顶点:");
int i = 0;//用于for循环
for(;i < a->vexnum;++i)//遍历所有节点 来做初始化
{
scanf("%c",&(a->vertices[i].vexdata));
getchar();
a->vertices[i].firstarc = NULL;//边先全部设置为null
}
printf("请输入各边:");
i = 0;//用于for循环
for(;i < a->arcnum;++i)//遍历所有边
{
char sv,tv;//两个字符用于接收边
scanf("%c%c",&sv,&tv);
getchar();
int j = LocateVex(a,sv);//用查找定位到sv节点位置
int k = LocateVex(a,tv);//用查找定位到tv节点位置
ArcNode *p = (ArcNode*)malloc(sizeof(ArcNode));//创建一个边节点并且分配内存空间
//使用头插法
p->adjvex = k;//先把边取成k
p->nextarc = a->vertices[j].firstarc;//指向首个边节点
a->vertices[j].firstarc = p;//再将首个边节点指向p
if(a->kind[0] == 'U'&&a->kind[1] == 'D')//如果是无向图
{
ArcNode *q = (ArcNode*)malloc(sizeof(ArcNode));//创建新节点q,对另一部分做类似操作
q->adjvex = j;
q->nextarc = a->vertices[k].firstarc;
a->vertices[k].firstarc = q;
}
}
}
void DFS(ALGraph a,int *sign,int v,void (*visit)(VNode v)) //连通图深度优先遍历
{
sign[v] = 1;//先把当前节点访问成功
visit(a.vertices[v]);//访问当前节点
ArcNode *p = a.vertices[v].firstarc;//用于for循环
for(;p != NULL;p = p->nextarc)//遍历p的所有相连的节点
{
if(!sign[p->adjvex])//只要未被访问则被重新访问
{
DFS(a,sign,p->adjvex,visit);//传入下一个节点 p->adjvex
}
}
}
void DFSTraverse(ALGraph a,void (*visit)(VNode v)) //非连通图深度优先遍历
{
int sign[a.vexnum];//定义标记数组
memset(sign,0,sizeof(sign)); //清空数组
int v = 0;
for(;v < a.vexnum;++v)//遍历全部的节点
{
if(!sign[v])//如果这个节点还没被标记,则对其进行DFS
{
DFS(a,sign,v,visit);
}
}
}
void BFS(ALGraph a,int *sign,int v,sqqueue *q,void (*visit)(VNode v)) //连通图广度优先遍历
{
sign[v] = 1;//先把当前节点访问成功
visit(a.vertices[v]);//访问当前节点
enqueue(q,v);//当前节点入队
for(;queueempty(*q) != 1;)//只要队列不空
{
int w;//定义w用来储存出队的元素
dequeue(q,&w);//出队
ArcNode *p = a.vertices[w].firstarc;//定义p指向w的邻接节点 的首节点
for(;p != NULL;p = p->nextarc) //遍历w邻接节点
{
if(sign[p->adjvex] == 0)//只要未被访问
{
sign[p->adjvex] = 1;//标记访问
enqueue(q,p->adjvex);//入队
visit(a.vertices[p->adjvex]);//访问
}
}
}
}
void BFSTraverse(ALGraph a,void (*visit)(VNode v)) //非连通图广度优先遍历
{
int sign[a.vexnum];//定义标记数组
memset(sign,0,sizeof(sign)); //清空数组
sqqueue q;//创建一个辅助队列,用于广度优先
initqueue(&q); //队列初始化
int v = 0;
for(;v < a.vexnum;++v)//遍历全部的节点
{
if(!sign[v])
{
BFS(a,sign,v,&q,visit);//如果这个节点还没被标记,则对其进行BFS
}
}
}
int main()
{
ALGraph UDG;//先创建无向图
for(;;)
{
printf("\n请选择 1.建立图 2.深度优先遍历图 3.广度优先遍历图 0.结束:\n");
int n;//用于接收输入
scanf("%d",&n);
// getchar();
switch(n)
{
case 1:
{
CreateGraph(&UDG);//创建无向图
printf("建立成功。\n");
break;
}
case 2:
{
DFSTraverse(UDG,visit);//深度优先遍历
printf("\n深度优先遍历成功。\n");
break;
}
case 3:
{
BFSTraverse(UDG,visit);
printf("\n广度优先遍历成功。\n");
break;
}
case 0:
return 0;
break;
default:{
printf("你输入的请求不符合规范,请重新输入!\n");
break;
}
}
}
}