()程序内容:
1、队列结构与其中几种基本用法
2、无向图的存储方式
3、无向图的深度优先搜索算法
4、无向图的广度优先搜索算法
代码:
duilie.h:
#include <stdio.h>
#include <stdlib.h>
//建立链表队列,输入 10 个数,依次入队,输出队头元素,再依次出队打印输出。
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
//链队列结构类型定义
typedef int QElemType;
typedef struct Qnode
{ //链队列结点的类型定义
QElemType data;
struct Qnode *next;
}QNode,*QueuePtr;
typedef struct
{ //链队列的表头结点的的类型定义
QueuePtr front; //队头指针,指向链表的头结点
QueuePtr rear; //队尾指针,指向队尾结点
}LinkQueue;
//链队列初始化函数定义
Status InitQueue(LinkQueue &Q)
{
Q.front = Q.rear = (QueuePtr)malloc(sizeof(QNode)); //建立头结点
if(!Q.front) exit(OVERFLOW);
Q.front->next = NULL;
return OK;
}
//链队列销毁函数定义
Status DestroyQueue(LinkQueue &Q)
{
while(Q.front){ //若头结点不为空则继续清除
Q.rear = Q.front->next; //让队尾指向队头 下一项
free(Q.front); //释放队头指向
Q.front = Q.rear; //让队头指向队尾指向
}
return OK;
}
//链队列插入队尾元素函数定义
Status EnQueue(LinkQueue &Q,QElemType e)
{
QueuePtr p = (QueuePtr)malloc(sizeof(QNode)); //临时变量存放
if(!p)
{
printf("分配队列元素空间失败");
exit(ERROR);
}
p->data = e;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}
//取链队列队头元素函数定义
Status GetHead(LinkQueue Q,QElemType &e)
{
if(!Q.front->next) exit(ERROR); //若Q.front下一项不为空则输出数据
e = Q.front->next->data;
}
//链队列删除队头元素函数定义
Status DeQueue(LinkQueue &Q,QElemType &e)
{
QueuePtr p;
if(!Q.front->next)
return(ERROR); //若Q.front下一项不为空则进行
p = Q.front->next;
e = p->data;
Q.front->next = p->next;
free(p);
if(!Q.front->next)
Q.rear = Q.front;
}
graph.cpp:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "duilie.h"
//typedef VertexType int;
typedef enum {DG,DN,AG,AN} GraphKind;
typedef struct ArcNode
{ //边(弧)结点的类型定义
int adjvex; //边(弧)的另一顶点的在数组中的位置
struct ArcNode *nextarc; //指向下一条边(弧)结点的指针
}ArcNode,*ANList;
typedef struct Vnode
{ //顶点结点和数组的类型定义
char *data; //顶点信息
ANList finrstarc; //指向关联该顶点的边(弧)链表
}Vnode, *AjList;
typedef struct
{
AjList vertices;//AjList vertices
int vexnum, arcnum; //图的当前顶点数和弧数
GraphKind kind; //图的种类标志
}ALGraph;
//字符串动态输入
char *strInput(){
int n=1;
char *array;
char c;
array = (char *)malloc(sizeof(char));
if(!array)
{
printf("字符串输入分配空间失败");
exit(0);
}
fflush(stdin);//清除缓冲区
while((c = getchar()) != '\n')
{
array = (char *)realloc(array,sizeof(char)*++n);
if(!array) {
printf("字符串重新分配空间失败");
exit(0);
}
array[n-2] = c;
}
array[n-1] = '\0';
return array;
}
//寻找编号对应下标
int seekSub(ALGraph &graph,char *n)
{
for(int i=0;i<graph.vexnum;i++)
{
if(!strcmp(graph.vertices[i].data,n))
{
return i;
}
}
return 0;
}
//创建无向图
void createDG(ALGraph &graph)
{
int num;
graph.kind = DG;
graph.arcnum = 0;
printf("请输入顶点个数:");
fflush(stdin);//清除缓冲区
scanf("%d",&num);
graph.vexnum = num;
graph.vertices = (AjList)malloc(sizeof(Vnode)*num);
if(!graph.vertices)
{
printf("图顶点分配失败");
exit(0);
}
for(int i=0;i<num;i++)
{
printf("请输入第%d个顶点名字",i+1);
graph.vertices[i].data = strInput();
graph.vertices[i].finrstarc = NULL;
}
for(int i=0;i<num;i++)
{
int n;
int again=0;
printf("请输入与顶点%s相连的边数量:",graph.vertices[i].data);
scanf("%d",&n);
graph.arcnum += n;
if(n != 0)
{
for(int j=0;j<n;j++)
{
char *temp;
ANList p;
if(again==0)
printf("请输入第%d个与顶点%s有关的点名字:",j+1,graph.vertices[i].data);
else{
printf("请重新输入第%d个与顶点%s有关的点名字:",j+1,graph.vertices[i].data);
again = 0;
}
temp = strInput();
for(int f=0;f<num;f++)
{
if(!strcmp(graph.vertices[f].data,temp))
{
if(j==0)
{
p = graph.vertices[i].finrstarc = (ANList)malloc(sizeof(ArcNode));
if(!p)
{
printf("分配边空间失败");
exit(0);
}
p->adjvex = f;
p->nextarc = NULL;
break;
}
else
{
p->nextarc = (ANList)malloc(sizeof(ArcNode));
if(!p->nextarc)
{
printf("分配边空间失败");
exit(0);
}
p = p->nextarc ;
p->adjvex = f;
p->nextarc = NULL;
break;
}
}
if(f == num-1)
{
printf("未找到编号%s顶点\n",temp);
again = 1;
j--;
}
}
}
}
}
graph.arcnum /= 2;
}
//展示无向图结构
void showDG(ALGraph &graph)
{
ANList p = NULL;
for(int i=0;i<graph.vexnum;i++)
{
p = graph.vertices[i].finrstarc;
printf("%s-->",graph.vertices[i].data);
if(!p)
printf("NULL");
while(p)
{
printf("%s ",graph.vertices[p->adjvex].data);
p = p->nextarc;
}
printf("\n");
}
}
//深度优先遍历
void DepthFirstSearch(ALGraph g,int i,int *visit,int *searchNum)
{
ANList p;
p = g.vertices[i].finrstarc;
searchNum[searchNum[0]] = i;
searchNum[0]++;
visit[i] = 1;
while(p)
{
if(visit[p->adjvex]==0)
{
DepthFirstSearch(g,p->adjvex,visit,searchNum);
}
p = p->nextarc;
}
}
//广度优先遍历
void BreadthFirstSearch(ALGraph g,int i,int *visit,int *searchNum)
{
int p = i;
ANList pt;
LinkQueue link;
InitQueue(link);
EnQueue(link,p);
searchNum[searchNum[0]]=p;
searchNum[0]++;
visit[p] = 1;
while(link.front->next!=NULL)
{
DeQueue(link,p);
pt = g.vertices[p].finrstarc;
if(pt!=NULL)
p = pt->adjvex;
while(pt != NULL)
{
if(!visit[p])
{
EnQueue(link,p);
searchNum[searchNum[0]] = p;
searchNum[0]++;
visit[p] = 1;
}
pt = pt->nextarc;
if(pt!=NULL)
p = pt->adjvex;
}
}
}
//图遍历
int *GraphSearch(ALGraph g,char *i,void (*fun)(ALGraph,int,int *,int *))//参数 i 表示顶点名字
{
int *searchNum;
int sub;
int state = 1;
int num = g.vexnum;
int visit[num]={0};
searchNum = (int*)malloc(sizeof(int)*(g.vexnum+1));
searchNum[0] = 1;
sub = seekSub(g,i);
while(state==1)
{
(*fun)(g,sub,visit,searchNum);
for(int i=0;i<num;i++)
{
if(visit[i]==0)
{
sub = i;
break;
}
if(i==num-1)
{
state = 0;
}
}
}
return searchNum;
}
int main()
{
ALGraph graph;
char *n;
int *searchNum;
//建立无向图
createDG(graph);
//展示无向图
showDG(graph);
//深度优先遍历图
printf("深度优先遍历\n输入从起始点名字:");
n = strInput();
searchNum = GraphSearch(graph,n,DepthFirstSearch);
for(int i=1;i<=graph.vexnum;i++)
{
printf("%s",graph.vertices[searchNum[i]].data);
if(i != graph.vexnum)
printf("-->");
else
printf("\n");
}
free(n);
free(searchNum);
//广度优先遍历图
printf("广度优先遍历\n输入从起始点名字:");
n = strInput();
searchNum = GraphSearch(graph,n,BreadthFirstSearch);
for(int i=1;i<=graph.vexnum;i++)
{
printf("%s",graph.vertices[searchNum[i]].data);
if(i != graph.vexnum)
printf("-->");
}
free(n);
free(searchNum);
return 0;
}
按照该图输入图
输入结果:
因广度优先遍历需用到队列,故在graph.cpp中引用了队列头文件duilie.h,对应代码在上有显示。
依旧对程序功能进行了一定程度的封装,将功能放进函数中,提高了代码复用性。
根据深度优先遍历与广度优先遍历的代码,可知两个的外壳是一致的,所以对这层代码进行了单独封装成了函数(即GraphSearch函数),该函数需要传入函数指针,该函数是进行广度优先还是深度优先遍历取决于传入的参数是DepthFirstSearch(即深度优先函数)还是BreadthFirstSearch(即广度优先函数),该程序只展示了无向图,该层外壳还可推广运用在无向网、有向图、有向网中的深度与广度优先遍历,进一步提高了代码的复用性。
关于visit[]数组(用于记录点是否已被读取)使用全局变量还是作为参数传入函数两种方法的讨论:
于个人理解,该图的建立与图的遍历等都封装为函数,最终目的为了将其放入头文件中,需要用该方法时预处理进行引用即可,所以若作为函数中调用其他分节函数时用到的参数,可直接使用函数,无其他顾虑。若是作为全局变量写在头文件中,当引用头文件时需要考虑该变量是否没有被重复定义,头文件中并不推荐使用全局变量。总结,个人更加推荐作为参数传入函数的方法。