/*实验目的:
1、掌握在图的邻接矩阵和邻接表存储结构实现图的基本运算的算法。
2、学习使用图算法解决应用问题的方法。
实验内容:
1. 图的基本运算。
(1)验证教材中关于在邻接矩阵和邻接表两种不同存储结构上实现图的基本运算的算法。
(2)在邻接矩阵和邻接表存储结构上实现图的深度和宽度优先遍历算法。
(3)设计主函数,测试上述运算。
2. 飞机最少换乘次数问题。
(1)设有n个城市,编号为0~n-1,m条航线的起点和终点由用户输入提供。寻找一条换乘次数最少的线路方案。
(2)参考:可以使用有向图表示城市间的航线;只要两城市间有航班,则图中这两点间存在一条权值为1的边;可以使用Dijkstra算法实现。*/
#include <stdio.h>
#include<stdlib.h>
#define ElemType int
#define INFTY 1e6
//////////////////////////*在邻接矩阵存储结构上实现图的基本运算的算法*/////////////////////
//邻接矩阵的结构定义
typedef struct mGragh
{
ElemType **a;//邻接矩阵
int n;//图的当前顶点数
int e;//图的当前边数
ElemType noEdge;//两顶点间无边时的值
}MGragh;
//邻接矩阵初始化
int MInit (MGragh *mg, int nSize, ElemType noEdgeValue){
int i, j;
mg->n=nSize; //初始化顶点数
mg->e=0;//初始时没有边
mg->noEdge=noEdgeValue; //初始化没有边时的取值
mg->a= (ElemType**)malloc(nSize*sizeof(ElemType*));//生成长度为n的一维指针数纽
if (!mg->a)
return 0;
for(i=0;i<mg->n;i++) //动态生成二维数组
{
mg->a[i]= (ElemType*)malloc(nSize*sizeof(ElemType));
for (j=0;j<mg->n;j++)mg->a[i][j]=mg->noEdge;
mg->a[i][i]=0;
}
}
//邻接矩阵的撤销
void MDestroy(MGragh *mg){
int i;
for(i=0;i<mg->n;i++)
free(mg->a[i]);//释放n个一维数组的存储空间
free(mg->a);//释放一维指针数组的存储空间
}
//边的搜索
int MExist(MGragh *mg, int u, int v){
if(u<0||v<0||u>mg->n-1||u==v||mg->a[u][v]==mg->noEdge)
return 0;
return 1;
}
//边的插入
int MInsert(MGragh *mg, int u, int v, ElemType w){
if(u<0||v<0||u>mg->n-1||v>mg->n-1||u==v)
return 0;
if(mg->a[u][v]!=mg->noEdge)
return 5;//若待插入边已存在,则返回出错信息
mg->a[u][v]=w;//插入新边
mg->e++;
return 1;
}
//边的删除
int MRemove(MGragh *mg, int u, int v){
if(u<0||v<0||u>mg->n-1||v>mg->n-1||u==v)
return 0;
if(mg->a[u][v]==mg->noEdge)
return 4;//若待插入边不存在,则返回出错信息
mg->a[u][v]=mg->noEdge;//删除边
mg->e--;
return 1;
}
//队列的结构体定义
typedef struct queue
{
int front;
int rear;
int maxSize;
ElemType *element;
}Queue;
//创建一个能容纳mSize个单位的空队列
void create(Queue *Q,int mSize){
Q->maxSize=mSize;
Q->element=(ElemType*)malloc(sizeof(ElemType)*mSize);
Q->front=Q->rear=0;
}
//销毁一个已存在的队列,即释放队列占用的数组空间
void Destroy(Queue *Q){
Q->maxSize=0;
free (Q->element);
Q->front=Q->rear=-1;
}
//判断队列是否为空,若是,则返回1;否则返回0
int IsEmpty(Queue *Q){
return Q->front==Q->rear;
}
//判断队列是否已满,若是,则返回1;否则返回0
int IsFULL(Queue *Q){
return (Q->rear+1)%Q->maxSize==Q->front;
}
//获取队头元素,并通过x返回。若操作成功,则返回1;否则返回0
int Front(Queue *Q, ElemType *x){
if(IsEmpty(Q)) //空队列处理
return 0;
*x=Q->element [(Q->front+1)%Q->maxSize];
return 1;
}
//在队列Q的队尾插入元素x(入队操作)操作成功,则返回1;否则返回0
int EnQueue(Queue *Q, ElemType x){
if (IsFULL(Q)) //溢出处理
return 0;
Q->rear=(Q->rear+1)%Q->maxSize;
Q->element [Q->rear]=x;
return 1;
}
//从队列Q中删除队头元素(出队操作)。操作成功,则返回1;否则返回0
int DeQueue(Queue *Q){
if(IsEmpty(Q)) //空队列处理
return 0;
Q->front=(Q->front+1)%Q->maxSize;
return 1;
}
//清除队列中全部元素,使队列恢复到初始状态(Q->front=Q->rear=0),但并不释放空间
void Clear(Queue *Q){
Q->front=Q->rear=0;
}
////////////*在邻接矩阵存储结构上实现图的深度和宽度优先遍历算法*////////////////
//邻接矩阵存储的图的深度优先遍历
void MDFS(int v,int visited[],MGragh g){
printf("%d",v);
visited[v]=1;//为v打上访问标记
for(int i=0;i<g.n;i++)//遍历v的邻接点
if(g.a[v][i]>=0)
if(!visited[i])//若w未被访问,递归调用LDFS
MDFS(i,visited,g);
}
void MDFSGragh(MGragh g){
int i;
int *visited=(int *)malloc(g.n*sizeof(int));//动态生成标记数组visited
for(int i=0;i<g.n;i++)//初始化visited数组
visited[i]=0;
for(int i=0;i<g.n;i++)//逐一检查每个顶点,如果未被访问,则调用LDFS
if(!visited[i])MDFS(i,visited,g);
free(visited);
}
//邻接矩阵存储的图的宽度优先遍历
void MBFS(int v,int visited[],MGragh g){
Queue q;
create(&q,g.n);//初始化队列
visited[v]=1;//为v打上访问标记
printf("%d",v);//访问顶点v
EnQueue(&q,v);//顶点v入队
while(!IsEmpty(&q)){
Front(&q,&v);
DeQueue(&q);//队首出队
for(int i=0;i<g.n;i++)//遍历v的邻接点
if(g.a[v][i]>=0)
if(!visited[i])//若w未被访问,则访问w并将其放入队中
{
visited[i]=1;
printf("%d",i);
EnQueue(&q,i);
}
}
Destroy(&q);
}
void MBFSGragh(MGragh g){
int i;
int *visited=(int *)malloc(g.n*sizeof(int));//动态生成标记数组visited
for(int i=0;i<g.n;i++)//初始化visited数组
visited[i]=0;
for(int i=0;i<g.n;i++)//逐一检查每个顶点,如果未被访问,则调用LDFS
if(!visited[i])MBFS(i,visited,g);
free(visited);
}
/////////////////////////*设计主函数,测试邻接矩阵相关算法*////////////////////////
void M(){
MGragh mg;
int n;
printf("请输入图中的顶点数:");
scanf("%d",&n);
if(MInit(&mg,n,-1)==0){
printf("图初始化出错\n");
return;
}
int u=-1,v=-1;
/* int flag=1;
do{
printf("请输入图中一条边所邻接的两个顶点,用空格分隔:");
scanf("%d %d",&u,&v);
LInsert(&lg,u,v,w);
printf("是否需要继续输入(需要输入1,不需要输入0):");
scanf("%d",&flag);
}while(flag==1);*/
int m;
int w=0;
printf("请输入图中的边数:");
scanf("%d",&m);
for(int i=0;i<m;i++){
printf("请输入图中第%d条边所邻接的两个顶点,用空格分隔:",i+1);
scanf("%d %d",&u,&v);
int j=MInsert(&mg,u,v,w);
if(j==0){
printf("该边无效\n");
i--;
}
else if(j==5){
printf("该边重复\n");
i--;
}
}
printf("图的深度优先遍历为:");
MDFSGragh(mg);
printf("\n");
printf("图的宽度优先遍历为:");
MBFSGragh(mg);
printf("\n");
MDestroy(&mg);
}
//////////////////////*在邻接表存储结构上实现图的基本运算的算法*/////////////////////
//邻接表的结构定义
typedef struct eNode
{
int adjVex;//与任意顶点u相邻接的顶点
ElemType w;//边的权值
struct eNode* nextArc;//指向下一个边结点
}ENode;
typedef struct lGragh
{
int n;//当前顶点数
int e;//当前边数
ENode **a;//指向一维指针数组
}LGragh;
//邻接表的初始化
int LInit (LGragh *lg, int nSize){
int i;
lg->n= nSize;
lg->e=0;
lg->a=(ENode**)malloc(nSize*sizeof(ENode*));//动态生成长度为n的一维指针数组
if(!lg->a)
return 0;
else{
for(i=0;i<lg->n;i++) lg->a[i]=NULL;//将指针数组a置空
return 1;
}
}
//邻接表的撤销
void LDestroy(LGragh *lg){
int i;
ENode *p,*q;
for(i=0;i<lg->n;i++){
p=lg->a[i];//指针p指向顶点i的单链表的第一个边结点
q=p;
while (p){
p=p->nextArc;
free(q);//释放顶点i的単链表中所有边结点
q=p;
}
}
free(lg->a);//释放一维指针数组a的存储空间
}
//边的搜索
int LExist(LGragh *lg, int u, int v){
ENode* p;
if(u<0||v<0||u>lg->n-1||v>lg->n-1||u==v)
return 0;
p=lg->a[u]; //指针p指向顶点u的单链表的第一个边结点
while(p&&p->adjVex!=v) p=p->nextArc;
if(!p) return 0;//若未找到此边,则返回0
else return 1;//若找到此边,则返回1
}
//边的插入
int LInsert (LGragh *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 0;
if(LExist(lg,u,v))
return 5;//重复了
p=(ENode *)malloc(sizeof(ENode));//为新的边结点分配存储空间
p->adjVex=v;
p->w=w;
p->nextArc=lg->a[u];//将新的边结点插入单链表的最前面
lg->a[u]=p;
lg->e++;
return 1;
}
//边的删除
int LRemove (LGragh *lg, int u, int v){
ENode *p,*q;
if(u<0||v<0||u>lg->n-1||v>lg->n-1||u==v)
return 0;
p=lg->a[u],q=NULL;
while(p&&p->adjVex!=v)//查找待删除边是否存在
{
q=p;
p=p->nextArc;
}
if(!p) return 4;//p为空,待删除边不存在
if(q) q->nextArc=p->nextArc;//从单链表中删除此边
else lg->a[u]=p->nextArc;
free(p);
lg->e--;
return 1;
}
////////////*在邻接表存储结构上实现图的深度和宽度优先遍历算法*/////////////////////
//邻接表存储的图的深度优先遍历
void LDFS(int v,int visited[],LGragh g){
ENode *w;
printf("%d",v);
visited[v]=1;//为v打上访问标记
for(w=g.a[v];w;w=w->nextArc)//遍历v的邻接点
if(!visited[w->adjVex])//若w未被访问,递归调用LDFS
LDFS(w->adjVex,visited,g);
}
void LDFSGragh(LGragh g){
int i;
int *visited=(int *)malloc(g.n*sizeof(int));//动态生成标记数组visited
for(int i=0;i<g.n;i++)//初始化visited数组
visited[i]=0;
for(int i=0;i<g.n;i++)//逐一检查每个顶点,如果未被访问,则调用LDFS
if(!visited[i])LDFS(i,visited,g);
free(visited);
}
//邻接表存储的图的宽度优先遍历
void LBFS(int v,int visited[],LGragh g){
ENode *w;
Queue q;
create(&q,g.n);//初始化队列
visited[v]=1;//为v打上访问标记
printf("%d",v);//访问顶点v
EnQueue(&q,v);//顶点v入队
while(!IsEmpty(&q)){
Front(&q,&v);
DeQueue(&q);//队首出队
for(w=g.a[v];w;w=w->nextArc)//遍历v的邻接点
if(!visited[w->adjVex])//若w未被访问,则访问w并将其放入队中
{
visited[w->adjVex]=1;
printf("%d",w->adjVex);
EnQueue(&q,w->adjVex);
}
}
Destroy(&q);
}
void LBFSGragh(LGragh g){
int i;
int *visited=(int *)malloc(g.n*sizeof(int));//动态生成标记数组visited
for(int i=0;i<g.n;i++)//初始化visited数组
visited[i]=0;
for(int i=0;i<g.n;i++)//逐一检查每个顶点,如果未被访问,则调用LDFS
if(!visited[i])LBFS(i,visited,g);
free(visited);
}
/////////////////////////*设计主函数,测试邻接表相关算法*////////////////////////
void L(){
LGragh lg;
int n;
printf("请输入图中的顶点数:");
scanf("%d",&n);
if(LInit(&lg,n)==0){
printf("图初始化出错\n");
return;
}
int u=-1,v=-1;
/* int flag=1;
do{
printf("请输入图中一条边所邻接的两个顶点,用空格分隔:");
scanf("%d %d",&u,&v);
LInsert(&lg,u,v,w);
printf("是否需要继续输入(需要输入1,不需要输入0):");
scanf("%d",&flag);
}while(flag==1);*/
int m;
int w=0;
printf("请输入图中的边数:");
scanf("%d",&m);
for(int i=0;i<m;i++){
printf("请输入图中第%d条边所邻接的两个顶点,用空格分隔:",i+1);
scanf("%d %d",&u,&v);
int j=LInsert(&lg,u,v,w);
if(j==0){
printf("该边无效\n");
i--;
}
else if(j==5){
printf("该边重复\n");
i--;
}
}
printf("图的深度优先遍历为:");
LDFSGragh(lg);
printf("\n");
printf("图的宽度优先遍历为:");
LBFSGragh(lg);
printf("\n");
LDestroy(&lg);
}
///////////////////*使用Dijkstra算法解决飞机最少换乘次数问题算法*//////////////////
int choose(int *d,int*s,int n){//选出最小的d[i]
int i,minpos;
ElemType min;
min=INFTY;
minpos=-1;
for(int i=0;i<n;i++){
if(d[i]<min&&!s[i]){
min=d[i];
minpos=i;
}
}
return minpos;//返回下标位置
}
int Dijkstra(int v,int u,MGragh g){
ElemType d[1000];
int path[1000];
int i,k,w;
int *s;
if(v<0||v>g.n-1)
return 0;
s=(int*)malloc(sizeof(int)*g.n);
for(int i=0;i<g.n;i++){
s[i]=0;//初始化
d[i]=g.a[v][i];
if(i!=v&&d[i]<INFTY)path[i]=v;
else path[i]=-1;
}
s[v]=1;d[v]=0;//顶点v为源点
for(int i=1;i<g.n-1;i++){
k=choose(d,s,g.n);
if(k==-1)continue;
s[k]=1;//k加入s
for(w=0;w<g.n;w++){//更新d和path
if(!s[w]&&d[k]+g.a[k][w]<d[w]){
d[w]=d[k]+g.a[k][w];
path[w]=k;
}
}
}
/* for(int i=0;i<g.n;i++){
printf("path=%d,d=%d\n",path[i],d[i]);
}*/
int x=d[u]-1;
int y=u;
int p[1000];
i=0;
while(path[u]!=-1){
p[i]=path[u];
i++;
u=path[u];
}
for(int j=i-1;j>=0;j--){
printf("%d->",p[j]);
}
printf("%d\n",y);
return x;
}
void plane(){
MGragh g;
int n,m;
printf("请输入城市的数量:");
scanf("%d",&n);
if(MInit(&g,n,INFTY)==0){
printf("图初始化出错\n");
return;
}
printf("请输入航线的数量:");
scanf("%d",&m);
int u=-1,v=-1,w=1;
for(int i=0;i<m;i++){
printf("请输入第%d条航线的起点和终点,用空格分隔:",i+1);
scanf("%d %d",&u,&v);
int j=MInsert(&g,u,v,w);
if(j==0){
printf("该边无效\n");
i--;
}
else if(j==5){
printf("该边重复\n");
i--;
}
}
printf("请输入你想要出发和到达的城市,用空格分隔:");
scanf("%d %d",&u,&v);
printf("换乘路线为:");
int d=Dijkstra(u,v,g);
printf("最少换乘次数为:%d",d);
}
int main(){
L();
M();
plane();
return 0;
}