实验七 图的深度优先遍历(选做,验证性实验,4学时)
- 实验目的
熟悉图的数组表示法和邻接表存储结构,掌握构造有向图、无向图的算法 ,在掌握以上知识的基础上,熟悉图的深度优先遍历算法,并实现。
- 实验内容
(1)图的数组表示法定义及基本操作的实现。
(2)图的邻接表表示法定义及基本操作的实现。
(3)写函数实现图的深度优先遍历(分别在两种结构上)
(4)在邻接表上实现拓扑排序、关键路径的求法,在邻接矩阵上实现最短路经、最小生成树的求法。
//邻接矩阵表示法存储无向图 深度优先搜索 最短路径 最小生成树
#include<stdio.h>
#define MAX_VERTEX_NUM 20
#define INT_MAX unsigned(-1)>>1
typedef int VRType;
typedef int Status;
typedef char VertexType;
typedef char* InfoType;
typedef int PathMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef int ShortPathTable[MAX_VERTEX_NUM];
int visited[MAX_VERTEX_NUM];//全局变量 访问标志数组
typedef enum{DG,DN,UDG,UDN}GraphKind;
typedef struct{
VertexType adjvex;
int lowcost;
}Closedge[MAX_VERTEX_NUM];//存放所有已经插入的节点到当前顶点的权值最小的那条边 终点就是i位置上的顶点 adj存放边的起点 lowcost存放最小的权值
typedef struct{
VRType adj;//顶点关系
InfoType info;//该弧相关信息的指针 --权值
}ArcCell,AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
typedef struct{
VertexType vexs[MAX_VERTEX_NUM];//顶点向量
AdjMatrix arcs;//邻接矩阵
int vexnum,arcnum;
GraphKind kind;//图的种类标志
}MGraph;
int LocateVex(MGraph G,VertexType a){
for(int i=0;i<G.vexnum;i++)
if(G.vexs[i]==a) return i;
}
Status CreatUDN(MGraph &G){
int IncInfo;
G.kind = GraphKind(UDN);
printf("请输入顶点数 边数 边上是否有相关信息(是1否0)\n");
scanf("%d%d%d",&G.vexnum,&G.arcnum,&IncInfo);//顶点个数 边数
//先构造顶点向量 给顶点数组赋值
fflush(stdin);
printf("请输入顶点集:\n");
for(int i=0;i<G.vexnum;i++) scanf(" %c",&G.vexs[i]);
//初始化邻接矩阵
for(int i=0;i<G.vexnum;i++)
for(int j=0;j<G.vexnum;j++)
G.arcs[i][j].adj=INT_MAX;//初始化全是断路
printf("请输入弧头弧尾和权值\n");
for(int i=0;i<G.arcnum;i++){
char head,tail;//弧头弧尾的两个字符
int weight,headlo,taillo;//边的权值 弧头弧尾在顶点向量中的位置
fflush(stdin);
scanf(" %c %c %d",&tail,&head,&weight);
headlo = LocateVex(G,head);
taillo = LocateVex(G,tail);
G.arcs[taillo][headlo].adj = weight;
if(IncInfo) scanf("%s",G.arcs[headlo][taillo].info);
// G.arcs[taillo][headlo] = G.arcs[headlo][taillo];//无向图的边的关系在矩阵里是对称的
}
}
Status Visit(VertexType c){
printf("%c",c);
}
int FirstAdjVex(MGraph G,int v){
for(int j=0;j<G.vexnum;j++)
if(G.arcs[v][j].adj) return j;
}
int NextAdjVex(MGraph G,int v,int w){
for(int j=w+1;j<G.vexnum;j++)
if(G.arcs[v][j].adj) return j;
return -1;
}
Status DFS(MGraph G,int i){
visited[i] = 1;
Visit(G.vexs[i]);
for(int w=FirstAdjVex(G,i);w>=0;w=NextAdjVex(G,i,w))
if(!visited[w])
DFS(G,w);
return 0;
}
Status DFSTraverse(MGraph G,Status (*Visit)(VertexType c)){//深度优先遍历算法
for(int i=0;i<G.vexnum;i++)
visited[i] = 0;
for(int i=0;i<G.vexnum;i++)
if(!visited[i])
DFS(G,i);
return 0;
}
void MiniSpanTree_PRIME(MGraph G,VertexType u){//从u顶点出发开始构造最小生成树
int k = LocateVex(G,u),minlocate;//新插入节点在顶点向量里的下标
Closedge closedge;
for(int i=0;i<G.vexnum;i++){//辅助数组初始化
if(i!=k){
closedge[i].adjvex = u;//边的起点
closedge[i].lowcost = G.arcs[k][i].adj;//边的权值
}
}
closedge[k].lowcost = 0;
minlocate = 0;
for(int i=1;i<G.vexnum;i++){//选择其余的i-1个顶点
for(int m=0;m<G.vexnum;m++){
if(!closedge[m].lowcost) continue;
if(!closedge[minlocate].lowcost) minlocate = m;
if(closedge[minlocate].lowcost>closedge[m].lowcost) minlocate = m;
}
k = minlocate;//选出权值最小的边的终点的下标
printf("\n%c %c",closedge[k].adjvex,G.vexs[k]);
closedge[k].lowcost = 0;//第k顶点已经插入在树里了 所以不会存在以第k顶点为终点的待插入的边了
for(int j=0;j<G.vexnum;j++){
if(G.arcs[k][j].adj<closedge[j].lowcost){
closedge[j].lowcost = G.arcs[k][j].adj;
closedge[j].adjvex = G.vexs[k];
}
}
}
}
void ShortestPath_DIJ(MGraph G,VertexType e,PathMatrix &P,ShortPathTable &D){
int final[MAX_VERTEX_NUM];
int v0 = LocateVex(G,e);
int newin,min;
//初始化D[] P[][] final[]
for(int i=0;i<G.vexnum;i++){
D[i] = G.arcs[v0][i].adj;
final[i] = 0;//均设置为没有找到最短路径
for(int j=0;j<G.vexnum;j++) P[i][j] = 0;//设置空路径
if(D[i]<INT_MAX){
P[i][v0] = 1;//权值不是无穷大 说明存在通路
P[i][i] = 1;
}
}
D[v0] = 0;//从源点到源点的最小路径为0
final[v0] = 1;//初始化v0 v0属于S集
//开始主循环 每次求得v0到某个顶点v的最短路径 并加v到S集
for(int i=1;i<G.vexnum;i++){
min = INT_MAX;
for(int j=0;j<G.vexnum;j++){
if(!final[j]){//min存放的是当前与v0最近的的节点的路径 newin是最近的顶点的下标 也就是即将插入S的顶点
if(D[j]<min){
min = D[j];
newin = j;
}
}
}
final[newin] = 1;//将第j个顶点插入S中 说明该顶点已经找到了最短路径
for(int j=0;j<G.vexnum;j++){
if(!final[j]&&(D[newin]+G.arcs[newin][j].adj>0)&&(D[newin]+G.arcs[newin][j].adj<D[j])){//修改D[w]和P[w]
D[j] = D[newin]+G.arcs[newin][j].adj;
for(int m=0;m<G.vexnum;m++) P[j][m] = P[newin][m];
P[j][newin] = 1;
P[j][j]=1;
}
}
}
printf("endvex\tpastvex\tpathlength\n");
for(int i=0;i<G.vexnum;i++){
printf(" %c \t",G.vexs[i]);
if(D[i]<INT_MAX){
for(int j=0;j<G.vexnum;j++) if(P[i][j]) printf("%c",G.vexs[j]);
printf(" \t%d\n",D[i]);
}else{
printf("无\n");
}
}
}
main(){
MGraph G;
CreatUDN(G);
printf("\n\n深度优先遍历:");
DFSTraverse(G,Visit);
printf("\n\n最小生成树的构成:");
MiniSpanTree_PRIME(G,'a');
PathMatrix P;
ShortPathTable D;
printf("\n\n从源点a到其余各点的最短路径:\n");
ShortestPath_DIJ(G,'a',P,D);
return 0;
}
//邻接表表示法存储有向图 广度优先搜索拓 扑排序 关键路径
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 20
#define MAX_VERTEX_NUM 20
typedef int Status;
typedef int QElemType;//队列里放的是元素在一位数组里的下标
typedef char VertexType;
typedef int SElemType;
typedef enum{DG,DN,UDG,UDN}GraphKind;
typedef struct{
QElemType *base;//队列的基地址
int front;//队头下标 相当于队头指针 指向队列头元素
int rear;//队尾下标 指向队列尾元素的下一个位置
}SqQue;
typedef struct ArcNode{//表结点
int adjvex;//该弧指向的顶点的位置
int weight;//权重
int info;
struct ArcNode* next;
}ArcNode;
typedef struct{//头结点
VertexType data;//顶点信息
ArcNode *firstarc;//指向第一个邻接点
}VNode,AdjList[MAX_VERTEX_NUM];
typedef struct{
AdjList vertices;
int vexnum,arcnum;
int kind;
}ALGraph;
typedef struct{
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
int visited[MAX_VERTEX_NUM];
int ve[MAX_VERTEX_NUM];
int vl[MAX_VERTEX_NUM];
int indegree[MAX_VERTEX_NUM];
SqStack S,T;//S是零入度顶点栈 T是拓扑序列顶点栈
int LocateVex(ALGraph G,VertexType v){
for(int i=0;i<G.vexnum;i++)
if(G.vertices[i].data==v) return i;
}
int FirstAdjVex(ALGraph G,int v){
if(!G.vertices[v].firstarc) return -1;//不存在邻接点
return G.vertices[v].firstarc->adjvex;
}
int NextAdjVex(ALGraph G,int v,int w){
ArcNode *p;
for(p=G.vertices[v].firstarc;p;p=p->next)
if(p->adjvex==w&&p->next) return p->next->adjvex;
return -1;
}
Status Visit(VertexType c){
printf("%c",c);
}
//初始化一个空栈
Status InitStack(SqStack &S){
S.base = (SElemType *)malloc(MAX_VERTEX_NUM*sizeof(SElemType));
if(!S.base) exit(-1);
S.top = S.base;
S.stacksize = MAX_VERTEX_NUM;
return 1;
}
//判空
Status StackEmpty(SqStack S){
return S.base==S.top;
}
//入栈
Status Push(SqStack &S,SElemType e){
*S.top++ = e;
return 1;
}
//出栈
Status Pop(SqStack &S,SElemType &i){
if(S.base==S.top){
printf("空栈!\n");
return 0;
}
i = *--S.top;
return 1;
}
Status CreatDN(ALGraph &G){
int IncInfo;
printf("请输入顶点数 边数 边上是否有相关信息(是1否0)\n");
scanf("%d%d%d",&G.vexnum,&G.arcnum,&IncInfo);//顶点个数 边数
//先构造顶点向量
fflush(stdin);
printf("请输入顶点集:\n");
for(int i=0;i<G.vexnum;i++){
scanf(" %c",&G.vertices[i].data);
G.vertices[i].firstarc = NULL;//初始化指针为空
}
printf("请输入弧头 弧尾 ");
if(IncInfo) printf("边上的关系:\n");
for(int i=0;i<G.arcnum;i++){
char head,tail;//弧头弧尾的两个字符
int weight,headlo,taillo;//边的权值 弧头弧尾在顶点向量中的位置
ArcNode *pnew;
fflush(stdin);
pnew = (ArcNode*)malloc(sizeof(ArcNode));
if(!pnew) exit(0);
scanf(" %c %c",&head,&tail);
if(IncInfo)
scanf("%d",&(pnew->info));
headlo = LocateVex(G,head);
pnew->adjvex = LocateVex(G,tail);
pnew->next = G.vertices[headlo].firstarc;
G.vertices[headlo].firstarc = pnew;//头插法插入表结点
}
}
//构造一个空的循环队列
Status InitQueue(SqQue &q){
q.base = (QElemType *)malloc(MAXSIZE*sizeof(QElemType));
if(!q.base) exit(0);
q.front = q.rear = 0;//空队列 两指针均指向下表为0的位置
return 1;
}
//出队列
Status DeQueue(SqQue &q,int &e){
if(q.rear==q.front) return 0;
e = q.base[q.front];
q.front = (q.front+1)%MAXSIZE;
return 1;
}
//入队列
Status EnQueue(SqQue &q,int e){
if((q.rear+1)%MAXSIZE==q.front){
printf("队列已满!\n");
return 0;
}
q.base[q.rear] = e;
q.rear = (q.rear+1)%MAXSIZE;
return 1;
}
Status BFSTraverse(ALGraph G,Status (*Visit)(VertexType c)){//深度优先遍历算法
for(int i=0;i<G.vexnum;i++) visited[i] = 0;
SqQue q;
int u;//保存出队列元素
InitQueue(q);
for(int i=0;i<G.vexnum;i++){
if(!visited[i]){
visited[i] = 1;
Visit(G.vertices[i].data);
EnQueue(q,i);//将下标入队列 而不是元素值
while(q.rear!=q.front){
DeQueue(q,u);
for(int j=FirstAdjVex(G,u);j>=0;j=NextAdjVex(G,u,j)){
if(!visited[j]){
visited[j] = 1;
Visit(G.vertices[j].data);
EnQueue(q,j);
}
}
}
}
}
}
Status FindInDegree(ALGraph G){
ArcNode *p;
for(int i=0;i<G.vexnum;i++) indegree[i] = 0;
for(int i=0;i<G.vexnum;i++)
for(p=G.vertices[i].firstarc;p;p=p->next)
indegree[p->adjvex]++;
}
Status TopologicalSort(ALGraph G){
SqStack S;
int i,j,count;
ArcNode* p;
FindInDegree(G);
InitStack(S);
for(i=0;i<G.vexnum;i++)
if(!indegree[i]) Push(S,i);//将入度为0的顶点的下标入栈
count = 0;//对输出顶点进行计数
while(!StackEmpty(S)){
Pop(S,i);//将入度为0的顶点位置出栈
printf("%c",G.vertices[i].data);//输入出栈顶点的内容
count++;//计数器+1
for(p=G.vertices[i].firstarc;p;p=p->next){
j = p->adjvex;
if(!(--indegree[j])) Push(S,j);
}
}
if(count<G.vexnum) printf("该有向图中有环存在\n");
}
Status TopologicalOrder(ALGraph G){
int i,j,count=0;
ArcNode *p;
FindInDegree(G);
InitStack(S);InitStack(T);
for(i=0;i<G.vexnum;i++)
if(!indegree[i])
Push(S,i);//将入度为0的顶点的下标入栈
for(int i=0;i<G.vexnum;i++) ve[i] = 0;//ve[i]指的是事件i的最早开始时间
while(!StackEmpty(S)){
Pop(S,i);
Push(T,i);
count++;//和拓扑排序里的大致一样
for(p=G.vertices[i].firstarc;p;p=p->next){
j = p->adjvex;
if((ve[i]+p->info)>ve[j]) ve[j] = ve[i] + p->info;//计算入度为0的顶点的邻接点的最早开始时间
indegree[j]--;
if(!indegree[j]){
Push(S,j);
}
}
}
if(count<G.vexnum) return 0;
return 1;
}
Status CriticalPath(ALGraph G){
int j,k,dut,ee,el;//ee是活动的最早开始时间 el是活动的最晚开始时间
ArcNode *p;
if(!TopologicalOrder(G)) return 0;//如果该带权有向图存在环的话就有活动以自身为开始条件 出现错误
for(int i=0;i<G.vexnum;i++) vl[i] = ve[G.vexnum-1];
while(!StackEmpty(T)){//求事件的最晚发生时间
for(Pop(T,j),p=G.vertices[j].firstarc;p;p=p->next){
k = p->adjvex;
dut = p->info;//关键活动的时间
if(vl[k]-dut<vl[j]) vl[j] = vl[k]-dut;
}
}
printf("startevent\tendevent\tlasttime\tstarttime\n");
for(int j=0;j<G.vexnum;j++){
for(p=G.vertices[j].firstarc;p;p=p->next){
k = p->adjvex;
dut = p->info;
ee = ve[j];
el = vl[k]-dut;
if(ee==el) printf(" %c\t %c\t %d\t %d\n",G.vertices[j].data,G.vertices[k].data,dut,ee);
}
}
}
main(){
ALGraph G;
CreatDN(G);
printf("\n\n广度优先搜索:");
BFSTraverse(G,Visit);
printf("\n\n拓扑排序结果:");
TopologicalSort(G);
printf("\n\n关键活动:\n");
CriticalPath(G);
return 0;
}