题目:有向图的简单路径求解问题
一、课题内容和要求
给定一个有向图G两个顶点a和b,试编写算法求a到b的简单路径的数量,并分别输出最短的简单路径和最长的简单路径。
头文件:
#ifndef PATH_HEADER_
#define PATH_HEADER_
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#define ERROR 0
#define OK 1
#define Overflow 2 //Overflow表示上溢出
#define Underflow 3 //Underflow表示下溢出
#define NotPresent 4 //NotPresent表示元素不存在
#define Duplicate 5 //Duplicate 表示有重复的元素
#define STLEN 5
static int sum = 0; //统计有几条简单路径
static int min_number = 100; //假设此图最多包含100个顶点
typedef int Status;
typedef int ElemType;
typedef struct eNode
{
int adjVex; //与任意顶点u相邻接的顶点
ElemType w; //边的权值
struct eNode* nextArc; //指向下一个边节点
}ENode;
typedef struct lGraph
{
int n; //图的当前顶点数
int e; //图的当前边数
ENode **a; //指向一维指针数组
}LGraph;
Status Init(LGraph *lg, int nSize);
void Destroy(LGraph *lg);
Status Exist(LGraph *lg, int u, int v);
Status Insert(LGraph *lg, int u, int v,ElemType w);
Status Remove(LGraph *lg, int u, int v);
void DFS(int v, int length, int visited[],LGraph *Lg);
void DFSGraph(LGraph *Lg);
#endif
头文件实现部分
#include <stdio.h>
#include "pathheader.h"
int min = INT_MAX;
int max = INT_MIN;
/**
* 1、初始化
* 邻接表的初始化运算是构造一个有n个顶点,但不包含边的邻接表。使用动态分配数组空间方式构造一个长度为
* n的一维指针数组a
*
* 算法步骤:
* 1、对n、e的值进行初始化
* 2、动态分配一维指针数组a,若失败,则返回出错信息;否则,对数组a中每个元素赋初值NULL。并且返回OK
*/
Status Init(LGraph *lg, int nSize)
{
lg->n = nSize;
lg->e = 0;
lg->a = (ENode **)malloc(nSize * sizeof(ENode *)); //动态生成长度为n的一维指针数组
if (!lg->a)
{
return ERROR;
}
else
{
for (int i = 0; i < lg->n; i++)
{
lg->a[i] = NULL;
}
return OK;
}
}
/**
* 2、撤销
* 撤销运算的主要功能是首先释放邻接表中每条单链表的所有边节点,然后释放一维指针数组a的存储空间,以防止
* 内存泄露
*
* 算法步骤:
* 1、释放邻接表中每条单链表的所有边节点,即相当于lg->a[i] = NULL;
* 2、释放一维指针数组a的存储空间
*/
void Destroy(LGraph *lg)
{
ENode *p, *q;
for (int i = 0; i < lg->n; i++)
{
p = lg->a[i];
q = p;
while (p)
{
p = p->nextArc;
free(q);
q = p;
}
}
free(lg->a);
}
/**
* 3、边的搜索
* 若参数u,v无效,则返回ERROR,否则,从a[u]指示的顶点u的单链表的第一个边节点开始,搜索adjVex的值为v
* 边节点,若找到此边,但会OK,否则返回ERROR
*
* 算法步骤:
* 1、若参数u、v无效,则返回ERROR
* 2、指针p指向顶点u的单链表的第一个边节点
* 3、搜索u的单链表中是否存在adjVex的值为v的边节点,若找到此边,返回OK,否则返回ERROR
*/
Status Exist(LGraph *lg, int u, int v)
{
ENode *p;
if (u < 0 || v < 0 || u > lg->n - 1 || v > lg->n - 1 || u == v)
{
return ERROR;
}
p = lg->a[u];
while (p && p->adjVex != v)
{
p = p->nextArc;
}
if (!p)
{
return ERROR;
}
else
{
return OK;
}
}
/**
* 4、边的插入
* 若参数u、v无效,返回ERROR,反之,从a[u]指示的边节点开始搜索adjVex的值为v的边节点,若找到,说明边节点
* <u,v>存在,返回Duplicate,否则,创建边节点并在u的单链表的最前面插入此边节点。
*
* 算法步骤:
* 1、若参数u、v无效,返回ERROR
* 2、搜索u的单链表中是否存在adjVex值为v的边节点,若找到<u,v>,则返回Duplicate。
* 3、为新的边节点分配存储空间,将此节点插入u的单链表的最前面
* 4、图中边数+1,返回OK
*/
Status Insert(LGraph *lg, int u, int v, ElemType w)
{
ENode *p;
if (u < 0 || v < 0 || u > lg->n - 1 || v > lg->n - 1 || u == v)
{
return ERROR;
}
if (Exist(lg, u, v))
{
return Duplicate;
}
p = (ENode *)malloc(sizeof(ENode *)); //为新的节点分配内存
p->adjVex = v;
p->w = w;
p->nextArc = lg->a[u]; //将新的边节点插入单链表的最前面
lg->a[u] = p;
lg->e++;
return OK;
}
/**
* 5、边的删除
* 若参数u、v无效,返回ERROR,否则,从a[u]指示边节点开始搜索adjVex值为v的边节点,若找到边<u,v>对应的
* 边节点,删除之。
*
* 算法步骤:
* 1、若参数u、v无效,返回ERROR
* 2、指针p指向顶点u的单链表的第一个边节点
* 3、搜索u的单链表中是否存在adjVex的值为v的边节点,若没有找到边<u,v>,则返回NotPresent
* ,否则删除。
* 4、图中边数-1,返回OK
*/
Status Remove(LGraph *lg, int u, int v)
{
ENode *p, *q;
if (u < 0 || v < 0 || u > lg->n - 1 || v > lg->n - 1 || u == v)
{
return ERROR;
}
p = lg->a[u], q = NULL;
while (p && p->adjVex != v)
{
q = p;
p = p->nextArc;
}
if (!p)
{
return NotPresent; //p为空,待删除边不存在
}
if (q)
{
q->nextArc = p->nextArc; //从单链表删除此边
}
else
{
lg->a[u] = p->nextArc; //此时为第一个边节点就是待删除边的情况
}
free(p);
lg->e--;
return OK;
}
/**
* 在此采用邻接表作为图的存储结构,下面分别给出了优先深度遍历的递归函数DFS和深度优先遍历整个图的DFSGraph
* 函数。后者调用前者完成对整个图的深度优先遍历。若是非连通图,执行一次DFS之后,图中还有顶点未被访问,需要
* 再次从未被访问的顶点出发,重复执行深度优先遍历,直至图中所有顶点均被访问过。
*
* 算法步骤:
* 1、从顶点v出发,访问顶点v,并且令visited[v] = 1;
* 2、依次查找v的所有邻接点w,若visited[w]的值为0,则从w出发,深度优先遍历G
*/
int a, b;
void DFS(int v, int length, int visited[], LGraph *Lg)
{
ENode *p = NULL;
if (v == b)
{
sum++;
if (length < min)
{
min = length;
}
if (length > max)
{
max = length;
}
return;
}
for (p = Lg->a[v]; p; p = p->nextArc)
{
if (!visited[p->adjVex] && p)
{
visited[p->adjVex] = 1;
DFS(p->adjVex, length + p->w, visited, Lg);
visited[p->adjVex] = 0;
}
}
}
void DFSGraph(LGraph *Lg)
{
int *visited = (int *)malloc(Lg->n * sizeof(int)); //动态生成标记数组visited
for (int i = 0; i < Lg->n; i++)
{
visited[i] = 0; //初始化visited数组,令所有顶点都未被访问过
}
printf("请输入顶点a,b的值:");
scanf("%d %d", &a, &b); //输入始点和终点
visited[a] = 1; //为此时此刻加入路径标记为1,即路径不可再走此点
DFS(a, 0, visited, Lg);
printf("一共有%d条简单路径,其中最长的路径长度是%d, 最短的路径是%d\n", sum, max, min);
free(visited);
}
主函数部分
/**
* 2020-06-08 pm:20:01
* 有向图的简单路径求解问题:在一个有向图中,寻找给定的两个顶点a、b,求a到b的简单路径的数量,并且
* 给出最短和最长的路径
* 关键字:有向图、简单路径、最短、最长
* 参考书:《数据结构》(C语言)慕课版、《大话数据结构》、《啊哈!算法》、《C Primer Plus》
* 主要功能:初始化图、为图增加边及其权值、删除误增加的边、进行深度优先遍历实现简单路径的查询
* 主要算法:深度优先遍历、图的存储结构之邻接表、单链表的遍历、增加、删除
*/
#include <stdio.h>
#include "pathheader.h"
int main()
{
LGraph Lg; //声明一个图,名字为Lg
int n; //此图顶点的个数
printf("请输入需要此图需要的顶点数:");
scanf("%d",&n);
getchar();
int status = Init(&Lg, n); //初始化图,为每个顶点分配内存空间,初始化成功,status为1,反之返回0
if(!status)
{
printf("没有成功动态生成长度为%d的一维指针数组,程序终止运行!\n\n",n);
}
else
{
printf("带有%d个顶点的图已经初始化完成,请插入边<u,v>及权值w,以构成完整的图\n\n",n);
}
int u, v, w; //表示顶点u、v和权值w
int i = 1; //统计第几条边
char flag[5];//接收输入
printf("是否准备为初始化的图插入边(yes/no):");
gets(flag);
while(strcmp(flag, "yes") == 0)
{
printf("请插入第%d条边<u,v>及权值w:",i);
scanf("%d %d %d",&u,&v,&w);
getchar();
status = Insert(&Lg, u, v, w); //检查插入操作是否成功
if(status == 5)
{
printf("您输入的顶点已存在,请重新输入!\n\n");
continue;
}
else if(status == 0)
{
printf("参数u=%d、v=%d无效,请重新输入!\n\n",u, v);
continue;
}
else
{
printf("第%d条边已经成功插入完成!\n\n",i);
}
i++; //为即将插入的边做好准备
printf("是否继续插入边(yes/no):");
gets(flag); //接收输入
if(strcmp(flag, "yes") != 0)
{
printf("带有%d个顶点的图一共成功插入%d条边\n\n",n, i-1);
break;
}
}
int m = i-1; //为了统计还剩下几条边
printf("是否错误插入某条边,请删除!(yes/no):");
gets(flag);
i = 1;
while(strcmp(flag, "yes") == 0)
{
printf("请输入要删除第%d条边<u,v>:",i);
scanf("%d %d",&u,&v);
getchar();
status = Remove(&Lg, u, v); //调用remove操作,返回删除操作的状态
if(status == 4)
{
printf("您输入的顶点不存在,请重新输入!\n\n");
continue;
}
else if(status == 0)
{
printf("参数u=%d、v=%d无效,请重新输入!\n\n",u, v);
continue;
}
else
{
printf("第%d条边已经成功删除完成!\n\n",i);
}
i++;//为继续删除边做好准备
printf("是否继续删除边(yes/no):");
gets(flag);
if(strcmp(flag, "yes") != 0)
{
printf("带有%d个顶点的图一共删除了%d条边,还剩下%d条边\n\n",n, i-1, m-(i-1));
break;
}
}
DFSGraph(&Lg); //调用此图的深度优先遍历函数
Destroy(&Lg); //释放内存空间
return 0;
}