数据结构基础(6) 图

图的概念与相关定义

(不再赘述)

图的存储结构

typedef struct arc
{
	int index;//邻接点的编号
	int weight;//边的权值
	struct arc *next;//指向下一个邻接点
}AR;

typedef struct mygragh
{
	int arcnum;//图中边的数量
	int vexnum;//图中顶点数量
	int type;//图的类型(比如,1表示有向图(网),0表示无向图(网))
	AR *N;//为邻接表的表头定义动态数组
	char **vexname;//存储顶点名的动态数组
	int **A;//邻接矩阵动态数组
}GH;

常见算法

DFS(深度优先搜索)

描述

DFSTraverse(G, v, Visit()); 从顶点v起深度优先遍历图G,并对每个顶点调用函数Visit一次且仅一次。与树的先根遍历很相似。主要侧重于遍历的深度,想法简单,没有考虑距离因素。(废话少说,放码过来)

代码实现
void DFSvisit(GH *G)
{
	int i,*visit;
	visit=(int *)malloc(G->vexnum*sizeof(int));//
	memset(visit,0,G->vexnum*sizeof(int));//
	int st=clock();
	for(i=0;i<G->vexnum;i++)//
	{
		if(!visit[i])//只要顶点i还未被访问
			DFS(G,visit,i);//以i为起始点对图做DFS遍历
	}
	printf("\n");//
	free(visit);//
	printf("DFS:%dms",clock()-st);
}

void DFS(GH *G,int *visit,int index)
{
//邻接表/
	AR *p;//
	printf("%s ",G->vexname[index]);//访问当前的起始点index
	visit[index]=1;//修改其访问标记
	p=G->N[index].next;//令p指向index的第一个邻接点
	while(p)//只有存在有邻接点
	{
		if(!visit[p->index])//且当前邻接点还未被访问
			DFS(G,visit,p->index);//以当前邻接点为起始点对图继续做DFS的遍历
		p=p->next;//接着遍历下一个邻接点
	}
邻接矩阵//
	/*int i,j;
	printf("%s ",G->vexname[index]);
	visit[index]=1;
	for(i=0;i<G->vexnum;i++)
	{
		if(G->A[index][i]&&visit[i]==0)
			DFS(G,visit,i);
	}*/
}

BFS(广度优先搜索)

描述

BFSTraverse(G, v, Visit()); 从顶点v起广度优先遍历图G,并对每个顶点调用函数Visit一次且仅一次。和树的层次遍历很像,考虑了距离因素,同一层次的点到顶点v的距离都是相等的

代码实现
void BFSvisit(GH *G)
{
	int i,*visit;//visit是用来做标记的数组,0表示未访问过,1表示访问过,防止重复遍历
	visit=(int *)malloc(G->vexnum*sizeof(int));//
	memset(visit,0,G->vexnum*sizeof(int));//初始,图中的每个定均未被访问,所以初始值全部为0
	int st=clock();
	for(i=0;i<G->vexnum;i++)//对图中每个顶点
	{
		if(!visit[i])//只要该顶点还未被访问
			BFS(G,visit,i);//则以该点为起始点,对图做B广度优先遍历
	}
	printf("\n");
	free(visit);//释放 visit数组
	printf("BFS:%dms",clock()-st);
}

void BFS(GH *G,int *visit,int index)
{	
///邻接表
	int *q,front=0,rear=0;//定义队列,以及队列参数
	int i;//
	AR *p;//
	q=(int *)malloc(G->vexnum*sizeof(int));//为队列分配空间
	q[rear]=index;//其实元素入队
	rear++;//
	visit[index]=1;//入队的元素设置为已访问状态,防止重复入队
	while(front!=rear)//只要队列非空
	{
		i=q[front];//读出队头元素
		printf("%s ",G->vexname[i]);//输出队头元素对应的顶点名
		front++;//
		p=G->N[i].next;//令指针p指向当前顶点对应领接表的第一个节点
		while(p)//只要当前顶点存在有邻接点
		{
			if(!visit[p->index])//只要该邻接点还未被访问
			{
				q[rear]=p->index;//将其入队
				rear++;//
				visit[p->index]=1;//修改访问标记
			}
			p=p->next;//再继续遍历下一个邻接点
		}
	}
	free(q);//释放队列数组空间
	
邻接矩阵
	/*int *q,front=0,rear=0;
	int i,j;
	q=(int *)malloc(G->vexnum*sizeof(int));
	q[rear]=index;
	rear++;
	visit[index]=1;
	while(front!=rear)
	{
		i=q[front];
		front++;
		printf("%s ",G->vexname[i]);
		for(j=0;j<G->vexnum;j++)
		{
			if(G->A[i][j]&&visit[j]==0)
			{
				q[rear]=j;
				rear++;
				visit[j]=1;
			}
		}
	}
	free(q);*/
}
TIPS

BFS和DFS运行结束后记得释放无用的空间:例如visit和q

DFS与BFS的应用

寻找两点之间的所有路径(DFS)

算法描述这与DFS十分相似,就是将起点的visitv保持为1,在递归时先令visit为1,再恢复为0,以便其余路线可以经过该可行点

void findpath(GH *G,char *start,char *end)
{
	int i,j;
	int *path;//存路径结点的序号 
	int *visit;//标志状态 
	i=findvex(start,G);
	j=findvex(end,G);
	path=(int *)malloc(G->vexnum*sizeof(int));
	path[0]=i;//第一个结点加入路径 
	visit=(int *)malloc(G->vexnum*sizeof(int));
	memset(visit,0,G->vexnum*sizeof(int));
	visit[i]=1;//将起点标位不可行 
	allpath(G,i,j,path,1,visit,0); 
}
void allpath(GH *G,int i,int j,int *path,int n,int *visit,int sum)
{
	int k;
	AR *p;
	if(i==j) 
	{
		for(k=0;k<n;k++)
		{
			printf("%s  ",G->vexname[path[k]]);
		}
		printf("路径长度为:%d",sum);
		printf("\n");
	}
	else
	{	
		p=G->N[i].next;//找到下一个可行点 
		while(p)//对所有邻接点尝试访问 
		{
			if(visit[p->index]==0)//如果该点未访问过 
			{
				path[n]=p->index;//将该点的序号加入路径
				visit[p->index]=1;//加入路径以后该点被访问过 
				allpath(G,p->index,j,path,n+1,visit,sum+p->weight);
				visit[p->index]=0;
			}
			p=p->next;//尝试下一个邻接点 
		}
	}
}
寻找所有回路(DFS)

这又与上面的寻找所有路径相似,只是将起点(也就是终点)的visit设为0,以便它能够作为终点被再次选中。

void findcycle(GH *G,char *start) 
{	//找回路,深度优先搜索,起点的visit始终设为0,每次递归完成后都会把1的visit还原为0以找到所有通路 
	int top=-1,sum=0;
	int *path=(int *)malloc(sizeof(int)*G->vexnum);
	int stid=findvex(start,G);
	int *visit=(int *)malloc(sizeof(int)*G->vexnum);
	path[++top]=stid;
	memset(visit,0,sizeof(int)*G->vexnum);
	circle(G,stid,path,top,visit,&sum);
	if(!sum)
		printf("没有回路\n");

}
void circle(GH *G,int start,int *path,int top,int *visit,int *sum)
{	
	int i;
	
	if(start==path[top]&&top>2) 
	{	//注意这里要判断该回路的长度要至少为3,2有可能只是起点与任意相邻点之间的一去一回
		(*sum)++;
		
		for(i=0;i<=top;i++)
		    printf("%s ",G->vexname[path[i]]);
		printf("\n");
	}
	else
	{	
		AR *q=G->N[path[top]].next;
		while(q)
		{	
			if(visit[q->index]==0)
			{
				path[top+1]=q->index;
				visit[q->index]=1;
				circle(G,start,path,top+1,visit,sum);
				visit[q->index]=0;
			}
			q=q->next;
		}
	}
判断图是否联通(DFS)

调用DFS的过程中在主调函数里面加一个记录联通分支数的变量,每进行一次DFS,联通分支数就加一,最后判断联通分支数是否为1即可。

判断无向图中是否有回路(DFS)

同样地记下联通分支数,如果联通分支数+点数>边数,则有回路,否则无回路

无向图中的最短路径(BFS)

从起点开始调用BFS,直到终点入队

typedef struct node
{
	int index,pa;//该点的序号和其前驱的序号 
}QU;
void findmin(GH *G,char *start,char *end)
{
	QU *q=(QU *)malloc(sizeof(QU)*G->vexnum);
	int *path=(int *)malloc(sizeof(int)*G->vexnum);
	int *visit=(int *)malloc(sizeof(int)*G->vexnum);
	memset(visit,0,sizeof(int)*G->vexnum);
	AR *p;
	int rear=-1,front=-1;
	q[++rear].index=findvex(G,start);
	q[rear].pa=front;
	while(front!=rear)
	{
		p=G->N[q[front+1].index].next;
		while(p)
		{
			if(visit[p->index]==0)
			{
				q[++rear].index=p->index;
				q[rear].pa=front+1;
				visit[p->index]=1;
				if(strcmp(G->vexname[p->index],end)==0)
				break;
			}
			p=p->next;
		}
		if(!p) front++;
		else break;
	}
	front=-1;
	int k=q[rear].pa;
	path[++front]=q[rear].index;
	while(k!=-1)
	{
		path[++front]=q[k].index;
		k=q[k].pa;
	}
	for(k=front;k>=0;k--)
	{
		printf("%s ",G->vexname[path[k]]);
	}
	printf("\n");
}
无向图中距离起点最远的那个点(BFS)

从起点开始进行BFS,最后入队的那个点

int findfar(GH *G,char *start)
{
	int *t=(int *)malloc(sizeof(int)*G->vexnum);
	int *visit=(int*)malloc(sizeof(int)*G->vexnum);
	memset(visit,0,sizeof(int)*G->vexnum);
	int front=-1,rear=-1;
	t[++rear]=findvex(G,start);
	visit[rear]=1;
	AR p,*q;
	while(front!=rear)
	{
		p=G->N[t[front+1]];
		q=p.next;
		while(q)
		{	
			if(!visit[q->index])
			{	
				t[++rear]=q->index;
				visit[q->index]=1;
			}
			q=q->next;
		}
		front++;
	 } 
	 return t[rear];
}
tips

所有与距离有关的都可以用BFS来实现,可将BFS想象成一颗树在渐渐生长,这样最长最短路径也就可以比较容易地被解释了。

最小生成树

Prim
算法描述

取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。在添加的顶点 w 和已经在生成树上的顶点v 之间必定存在一条边,并且该边的权值在所有连通顶点 v 和 w 之间的边中取值最小。之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。
时间复杂度为O(n^2),适用于稠密图

手动实现

在这里插入图片描述

Kruskal
算法描述

考虑问题的出发点: 为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能地小。
具体做法: 先构造一个只含 n 个顶点的子图 SG,然后从权值最小的边开始,若它的添加不使SG 中产生回路,则在 SG 上加上这条边,如此重复,直至加上 n-1 条边为止
时间复杂度为O(eloge),适用于稀疏图

手动实现

在这里插入图片描述

####最小生成树程序实现
课程设计

拓扑排序

算法描述

从有向图中选取一个没有前驱的顶点,并输出之;从有向图中删去此顶点以及所有以它为尾的弧;重复上述两步,直至图空,或者图不空但找不到无前驱的顶点为止

代码实现

(考试要求手写)

int TopologicalOrder(GH *G,int *t)//拓扑排序
{
	int tops=-1;
	int topt=-1;
	int k;
	AR *p;
	int *du=(int *)malloc(sizeof(int)*G->vexnum);
	int *st=(int *)malloc(sizeof(int)*G->vexnum);
	memset(du,0,sizeof(int)*G->vexnum);
	memset(st,0,sizeof(int)*G->vexnum);
	//初始化度
	for(int i=0;i<G->vexnum;i++)
	{	
		p=G->N[i].next;
		while(p)
		{
			du[p->index]++;
			p=p->next;
		}
	 } 
	 //源点入zhai 
	 for(int i=0;i<G->vexnum;i++)
	 {
	 	if(du[i]==0)
	 	{
	 		st[++tops]=i;
	 		break;
		 }
	 }
	 //拓扑排序 
	 while(tops>=0)
	 {
	 	k=st[tops];
	 	tops--;
	 	t[++topt]=k;
	 	p=G->N[k].next;
	 	while(p)
	 	{
	 		du[p->index]--;
	 		if(du[p->index]==0)
	 		{
	 			st[++tops]=p->index;
			 }
			 p=p->next;
		 }
	 }
	 if(topt+1==G->vexnum) return 1;
	 else return 0; 
 } 
AOE网

关键路径课设

最短路径

DIJKSTAR
算法描述

路径长度最短的最短路径的特点:在这条路径上,必定只含一条弧,并且这条弧的权值最小。
下一条路径长度次短的最短路径的特点:它只可能有两种情况:或者是直接从源点到该点(只含一条弧); 或者是从源点经过顶点v1,再到达该顶点(由两条弧组成)。
再下一条路径长度次短的最短路径的特点它可能有三种情况:或者是直接从源点到该点(只含一条弧); 或者是从源点经过顶点v1,再到达该顶点(由两条弧组成);或者是从源点经过顶点v2,再到达该顶点。
其余最短路径的特点:它或者是直接从源点到该点(只含一条弧); 或者是从源点经过已求得最短路径的顶点,再到达该顶点。
求最短路径的迪杰斯特拉算法:
设置辅助数组Dist,其中每个分量Dist[k] 表示 当前所求得的从源点到其余各顶点 k 的最短路径。
一般情况下,Dist[k] = <源点到顶点 k 的弧上的权值>或者 = <源点到其它顶点的路径长度>+ <其它顶点到顶点 k 的弧上的权值>
(1)在所有从源点出发的弧中选取一条权值最小的弧,即为第一条最短路径
(2)修改其它各顶点的Dist[k]值。假设求得最短路径的顶点为u,若 Dist[u]+G.arcs[u][k]<Dist[k]则将 Dist[k] 改为 Dist[u]+G.arcs[u][k]。
具体的实现方法实际上和我们最开始的分析是一致的,只不过每次比较起点到终点的连线距离时,已经提前将该数存入Dist

代码实现
typedef struct arc
{
	int index,weight;
	struct arc *next;
}AR;

typedef struct MyGraph
{
	int type;//0,表示无向网,1表示有向网
	int arcnum,vexnum;
	char **vexname;
	AR *N;
	int **A;
}GH;
void dijkstra(GH *G,int start)
{	
	int k;
	int *dis=(int *)malloc(sizeof(int)*G->vexnum); 
	int *path=(int *)malloc(sizeof(int)*G->vexnum);
	int *flag=(int *)malloc(sizeof(int)*G->vexnum);
	memset(flag,0,sizeof(int)*G->vexnum);
	//初始化
	for(int i=0;i<G->vexnum;i++)
	{
		dis[i]=G->A[start][i];
		if(dis[i]<MAX) path[i]=start;
		else path[i]=-1;
	}
	flag[start]=1;
	
	for(int i=0;i<G->vexnum-1;i++)
	{
		k=findmin(G->vexnum,flag,dis);
		if(k>0)
		{
			flag[k]=1;
			for(int h=0;h<G->vexnum;h++)
			{
				if(flag[h]==0 && dis[h]>dis[k]+G->A[k][h])
				{	
					dis[h]=dis[k]+G->A[k][h];
					path[h]=k;
				}
			 } 
		}
	}
	showpath1(G->vexnum,flag,path,dis,start);
	free(dis);
	free(path);
	free(flag);
} 
void showpath1(int n,int *flag,int *path,int *dis,int start)
{
	for(int i=0;i<n;i++)
	{
		if(i!=start)
		{
			int k=i;
			if(path[k]==-1) 
			printf("v[%d]->v[%d]:no path\n",start,k);
			else
			{	int *route=(int *)malloc(sizeof(int)*n);
				int ind=-1;
				while(k!=start)
				{	
					route[++ind]=k;
					k=path[k];		
				}
				printf("v[%d]",start);
				for(int h=ind;h>=0;h--) printf("->v[%d]",route[h]);
				printf("\tlen:%d\n",dis[i]);
			}
			
		}
	}
}
FLOYD
算法描述

从 vi 到 vj 的所有可能存在的路径中,选出一条长度最短的路径
若<vi,vj>存在,则存在路径{vi,vj} // 路径中不含其它顶点
若<vi,v1>,<v1,vj>存在,则存在路径{vi,v1,vj}// 路径中所含顶点序号不大于1
若{vi,…,v2}, {v2,…,vj}存在,则存在一条路径{vi, …, v2, …vj}// 路径中所含顶点序号不大于2
依次类推,则 vi 至 vj 的最短路径应是上述这些路径中,路径长度最小者。

代码实现
void floyd(GH *G)
{
	int **P=(int **)malloc(sizeof(int *)*G->vexnum); 
	int **D=(int **)malloc(sizeof(int *)*G->vexnum);
	for(int i=0;i<G->vexnum;i++)
	{
		P[i]=(int *)malloc(sizeof(int)*G->vexnum);
		D[i]=(int *)malloc(sizeof(int)*G->vexnum);
		for(int j=0;j<G->vexnum;j++)
		{
			D[i][j]=G->A[i][j];
			if(D[i][j]<MAX) P[i][j]=i;
			else P[i][j]=-1;
		}
	 } 
	 for(int k=0;k<G->vexnum;k++)
	 {
	 	for(int i=0;i<G->vexnum;i++)
	 	{
	 		for(int j=0;j<G->vexnum;j++)
	 		{
	 			if(D[i][j]>D[i][k]+D[k][j])
	 			{
	 				D[i][j]=D[i][k]+D[k][j];
	 				P[i][j]=k;
				 }
			 }
		 }
	 }
	 printf("WAY 2:\n");
	 for(int i=0;i<G->vexnum;i++)
	 {
	 	for(int j=0;j<G->vexnum;j++)
	 	{	
		 	if(P[i][j]!=-1&&i!=j)
	 		{
			 printf("len:%d :",D[i][j]);
			 printf("v[%d] ",i);
			 showpath3(G->vexnum,D,P,i,j);
	 		 printf("\n");
			} 
			else if(P[i][j]==-1 && i!=j)
			{
				printf("no path:v[%d]->v[%d]\n",i,j);
			}
		 }
		 
	 }
	 free(P);
	 free(D);
} 
void showpath3(int n,int **D,int **P,int st,int ed)
{
	if(st==P[st][ed])
	printf("->v[%d]",ed);
	else
	{
		showpath3(n,D,P,st,P[st][ed]);
		showpath3(n,D,P,P[st][ed],ed);
	}
}
Floyd算法应用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(这字实在难看,我自己都嫌弃)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值