数据结构——图的最短路径

一、定义

在图中,求两个不同顶点间的不同路径中,边的权值和最小的那条路径。这条路径就叫做最短路径(Shortest Path),第一个顶点叫做源点(Source),最后一个顶点叫做终点(Destination)

二、分类

单源最短路径问题:从某固定源点出发,求其到所有其他顶点的最短路径。

        包括有权图和无权图

多源最短路径问题:求任意两顶点间的最短路径。

三、算法

1、UNweighted();解决无权图单源最短路径问题。

首先需要解决无权图的单源最短路经问题:用BFS(广度优先搜索)的思想,从源点开始一层一层找出到各顶点的最短路径(逐一增加1),用数组dist[]存放每个顶点的最短路径长度,用数组path[]存放最短路径的前驱顶点,终点的最短路径就等于该路径上终点的前驱顶点的最短路径加上到终点边上的权重。无权图可看做每条边权重均为1的有权图。

数据结构——图的最短路径_第1张图片 引用自中国大学慕课数据结构——陈越老师

2、Dijkstra();解决有权图单源最短路径问题。

掌握了无权图的单源最短路径算法的基础上,用在有权图上会发现最短路径不再是经过顶点的数量,而是路径上各条边权重的和。 也就是,从源点开始逐层扫描各顶点的最短路径,第n层顶点的最短路径可能因n+1层顶点的边上的权重而改变。例如:v1→v6,扫描至第一层v1→v4,最短路径为1,扫描至第二层,v1→v4→v6,最短路径为1+8=9;当扫描至第三层会发现v1→v4→v7→v6这条路径的权重和为1+4+1=6,改变了v1→v6的最短路径。解决方法为,逐层找出该层中路径最短的顶点Vi,Vi的路径长度确定,且为最短。同时影响前一层各顶点的路径长度。

Dijkstra算法算逐层找到该层中最短路径的顶点Vi,通过Vi更新邻接点的最短路径,找到最后一层后这个连通集内所有的顶点的最短路径均已确定。不在同一个连通分量内的顶点用最大值表示不连通。(拒绝负权边)

数据结构——图的最短路径_第2张图片 引用自中国大学慕课数据结构——陈越老师

 3、Floyd();Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似。

百度百科和CSDN里面,有对这个算法很专业解释,我看不太明白。

我理解的这个算法就是——挨个试。

用二维数组储存多源最短路径,任意两个顶点i、j,通过任意顶点k,如果能缩短路径,那就更新最短路径。也就是   if(dist[i][k]+dist[k][j]

数据结构——图的最短路径_第3张图片

数据结构——图的最短路径_第4张图片

数据结构——图的最短路径_第5张图片

图片来源:参考文章传送门——图的最短路径算法-Floyd算法-弗洛伊德算法 - 腾讯云开发者社区-腾讯云

四、代码

邻接表适合用于稀疏图;邻接矩阵适合用于稠密图。

lgraph.h 

#ifndef LGRAPH_H
#define LGRAPH_H
#include 
#include 

/*声明图的一些要素*/
#define MaxVertexNum 100
typedef int Vertex;				//顶点类型 
typedef int WeighType;			//权重类型 
typedef char VertexData;		//顶点数据 
typedef struct toadjv{			//邻接表结构体 
	Vertex v;
	WeighType weigh;
	struct toadjv *next;
}PtrToAdjv;

typedef struct vertex_node{		//顶点结构体 
	PtrToAdjv *firstedge;		//指向邻接表 
	VertexData data;
}AdjList; 						

typedef struct graph_node{		//声明图结构体 
	int VertexNum;
	int EdgeNum;
	AdjList G[MaxVertexNum];	//顶点数组 
}LGraph;

/*邻接矩阵*/
typedef struct mgraph_node{
	int VertexNum;
	int EdgeNum;
	WeighType graph[MaxVertexNum][MaxVertexNum];
}MGraph;

typedef struct edge_node{		//边结构体,主要方便插入操作 
	Vertex v1,v2;
	WeighType weigh;
}Edge;

/*建图 邻接表*/
LGraph *init_graph(int VertexNum);				//初始化有VertexNum个顶点的图 
void insert_edge(LGraph *pGraph,Edge *pEdge);	//向图中插入边 
LGraph *biuld_graph(int VertexNum);				//建图 

/*建图 邻接矩阵*/
MGraph *init_mgraph(int VertexNum);				//初始化有VertexNum个顶点的图(邻接矩阵)
void minsert_edge(MGraph *pGraph,Edge *pEdge);	//向图中插入边 
MGraph *biuld_mgraph(int VertexNum);			//建图 

/*遍历*/
void init_visited(int VertexNum);
void DFS(LGraph *pGraph,Vertex v);				//邻接表 
void MDFS(MGraph *pGraph,Vertex v);				//邻接矩阵 
void BFS(LGraph *pGraph,Vertex v);				//邻接表 
void MBFS(MGraph *pGraph,Vertex v);				//邻接矩阵 

/*队列,广度优先遍历使用*/
#define ERROR MaxVertexNum
typedef Vertex Element;
typedef struct queue_node{
	Element data;
	struct queue_node *next;
}Qnode;

typedef struct queue{
	Qnode *head;
	Qnode *rear;
}Que;
Que *init_queue();
int isempty_queue(Que *pQ);
void in_queue(Que *pQ,Element data);
Element out_queue(Que *pQ);

/*单源最短路径*/
#define MaxPathFlag 65535
void init_path(int VertexNum);						//初始化最短路径 
void init_dist(int VertexNum);						//初始化最短路径长度 
void UNweighted(LGraph *pGraph,Vertex v);			//单源最短路径算法
void Dijkstra(LGraph *pGraph,Vertex v);				//迪杰斯特拉算法,解决有权图的单源最短路径问题 
void Floyd(MGraph *pGraph);							//弗洛伊德算法,解决有权图的多源最短路径问题 
#endif

lgraph.c 

#include "lgraph.h"
/*初始化一个有VertexNum个顶点的图*/
LGraph *init_graph(int VertexNum){ 
	LGraph *pGraph=(LGraph*)malloc(sizeof(LGraph));
	pGraph->EdgeNum=0;
	pGraph->VertexNum=VertexNum;
	for(Vertex v=0;vG[v].firstedge=NULL;
	}
	return pGraph;
}
MGraph *init_mgraph(int VertexNum){		//邻接矩阵 
	MGraph *pGraph=(MGraph*)malloc(sizeof(MGraph));
	pGraph->VertexNum=VertexNum;
	pGraph->EdgeNum=0;
	for(Vertex v=0;vgraph[v][w]=MaxPathFlag;
			}else{
				pGraph->graph[v][w]=0;
			}
		}
	}
	return pGraph;
} 
/*向图中插入边*/
void insert_edge(LGraph *pGraph,Edge *pEdge){		//邻接表 
	PtrToAdjv *ptrtoadjv=(PtrToAdjv*)malloc(sizeof(PtrToAdjv));
	ptrtoadjv->v=pEdge->v2;
	ptrtoadjv->weigh=pEdge->weigh;
	ptrtoadjv->next=pGraph->G[pEdge->v1].firstedge;
	pGraph->G[pEdge->v1].firstedge=ptrtoadjv;
	/*无向图需要反向链接*/
	PtrToAdjv *ptrtoadjv1=(PtrToAdjv*)malloc(sizeof(PtrToAdjv));
	ptrtoadjv1->v=pEdge->v1;
	ptrtoadjv1->weigh=pEdge->weigh;
	ptrtoadjv1->next=pGraph->G[pEdge->v2].firstedge;
	pGraph->G[pEdge->v2].firstedge=ptrtoadjv1;
	free(pEdge);
}
void minsert_edge(MGraph *pGraph,Edge *pEdge){		//邻接矩阵 
	pGraph->graph[pEdge->v1][pEdge->v2]=pEdge->weigh;
	pGraph->graph[pEdge->v2][pEdge->v1]=pEdge->weigh;//无向图反向链接
	pGraph->EdgeNum+=2;
	free(pEdge); 
} 
/*建图*/
LGraph *biuld_graph(int VertexNum){					//邻接表 
	LGraph *pGraph=init_graph(VertexNum);
	for(Vertex v=0;vv1=v;pEdge->v2=v+1;
		if(v+1==VertexNum) pEdge->v2=0;
		pEdge->weigh=v+1;			
		insert_edge(pGraph,pEdge);
	}
	return pGraph;
}
MGraph *biuld_mgraph(int VertexNum){				//邻接矩阵
	MGraph *pGraph=init_mgraph(VertexNum);
	for(Vertex v=0;vv1=v;pEdge->v2=v+1;
		if(v+1==VertexNum) pEdge->v2=0;
		pEdge->weigh=v+1;
		minsert_edge(pGraph,pEdge);
	}
	return pGraph;
}
/*声明全局变量,判断访问记录,主要用于DFS算法和BFS算法*/
int visited[MaxVertexNum];							//用来判断是否已访问过
void init_visited(int VertexNum){					//初始化访问记录 
	for(Vertex v=0;vG[v].firstedge;w;w=w->next){
		if(visited[w->v]==0){
			DFS(pGraph,w->v);
		}
	}
}
void MDFS(MGraph *pGraph,Vertex v){					//邻接矩阵 
	visited[v]=1;
	printf("%d ",v);
	for(Vertex w=0;wVertexNum;w++){
		if(visited[w]==0&&pGraph->graph[v][w]G[v].firstedge;w;w=w->next){
			if(visited[w->v]==0){
				visited[w->v]=1;
				printf("%d ",w->v);
				in_queue(pQ,w->v);
			}
		}
	}
	free(pQ);
}
void MBFS(MGraph *pGraph,Vertex v){					//邻接矩阵 
	Que *pQ=init_queue();
	visited[v]=1;
	printf("%d ",v);
	in_queue(pQ,v);
	while(!isempty_queue(pQ)){
		v=out_queue(pQ);
		for(Vertex w=0;wVertexNum;w++){
			if(visited[w]==0&&pGraph->graph[v][w]head=pQ->rear=NULL;
	return pQ;
}
int isempty_queue(Que *pQ){							//队列判空 
	return pQ->head==NULL;
}
void in_queue(Que *pQ,Element data){				//入队 
	Qnode *pQnode=(Qnode*)malloc(sizeof(Qnode));
	pQnode->data=data;
	pQnode->next=NULL;
	if(isempty_queue(pQ)){							//需要注意队空情况 
		pQ->head=pQ->rear=pQnode;
	}else{
		pQ->rear->next=pQnode;
		pQ->rear=pQnode;
	}
}
Element out_queue(Que *pQ){							//出队 
	Element temp=ERROR;
	if(!isempty_queue(pQ)){
		temp=pQ->head->data;
		Qnode *ptemp=pQ->head;
		pQ->head=pQ->head->next;
		if(pQ->head==NULL) 							//同样需要注意队空情况 
			pQ->rear=NULL;
		free(ptemp);
	}
	return temp;
}

/*无权图单源最短路径算法*/
Vertex path[MaxVertexNum]; 							//用数组保存最短路径中下标顶点的前一个顶点 
int dist[MaxVertexNum];								//用数组保存源点到其他顶点的最短路径 
void init_path(int VertexNum){						//初始化最短路径 
	for(Vertex v=0;vVertexNum);
	init_dist(pGraph->VertexNum);
	Vertex tempv=v;
	Que *pQ=init_queue();
	dist[v]=0;
	in_queue(pQ,v);
	while(!isempty_queue(pQ)){
		v=out_queue(pQ);
		for(PtrToAdjv *p=pGraph->G[v].firstedge;p;p=p->next){
			if(dist[p->v]==-1){
				dist[p->v]=dist[v]+1;
				path[p->v]=v;
				in_queue(pQ,p->v);
			}
		}
	}
	for(Vertex v=0;vVertexNum;v++){
		printf("%d ",dist[v]);
		if(v==pGraph->VertexNum-1) printf("\n");
	}
	scanf("%d",&v);
	while(v!=tempv){
		printf("%d ",v);
		v=path[v];
		if(v==tempv) printf("%d\n",tempv);
	}
}
/*有权图的单源最短路径*/
/*杰斯特拉算法,解决有权图的单源最短路径问题。整体思想是:利用BFS的思路,逐层解决。
首先访问源点visited[v0]=1,将第一层每个顶点的路径保存在数组dist[]中,找到第一层顶点中的最短路径mindist和顶点minv,visited[minv]标记确定;
其次访问minv,同时将第二层每个未标记确定的顶点的最短路径保存在数组dist[]中,找出最小值mindist和顶点minv,确定第二层的minv,以此类推可以将
一个连通分量里面所有顶点的最短路径确定。而其他连通分量与源点因为未连通,最短路径为MaxDist,表示不存在路径。 
*/

void Dijkstra(LGraph *pGraph,Vertex v){
	int dist[pGraph->VertexNum];				
	int visited[pGraph->VertexNum];
    Vertex path[pGraph->VertexNum];
	for(Vertex v=0;vVertexNum;v++){	//初始化最短路径长度为MaxPathFlag,表示未连通 
		visited[v]=0;							//初始化已确定最短路径长度的顶点,均为0表示均为确定 
		dist[v]=MaxPathFlag;
        path[v]=-1;
	}
	dist[v]=0;									//初始化起点的最短路径为0 
	while(!visited[v]){							//当所有点均已标记,循环退出 
		visited[v]=1;							//标记已确定最短路径的顶点v 
		for(PtrToAdjv *p=pGraph->G[v].firstedge;p;p=p->next){
/*通过v,如果有其他顶点的最短路径更短,更新。本质上是更新了v的邻接点的长度*/
			if(dist[p->v]>dist[v]+p->weigh&&!visited[p->v]){
                dist[p->v]=dist[v]+p->weigh;
                path[p->v]=v;
            }   
		}
		Vertex minv;
		int mindist=MaxPathFlag;
		for(Vertex v=0;vVertexNum;v++){//找出下一个最短路径的顶点v 
			if(dist[v]VertexNum;v++){
		printf("%d ",dist[v]);
	}
}
void Floyd(MGraph *pGraph){
	WeighType dist[pGraph->VertexNum][pGraph->VertexNum];
	Vertex    path[pGraph->VertexNum][pGraph->VertexNum];
	for(Vertex v=0;vVertexNum;v++){
		for(Vertex w=0;wVertexNum;w++){
			if(v!=w) dist[v][w]=pGraph->graph[v][w];
			else dist[v][w]=0;
			path[v][w]=-1;
		}
	}
	for(Vertex k=0;kVertexNum;k++){
		for(Vertex i=0;iVertexNum;i++){
			for(Vertex j=0;jVertexNum;j++){
				if(dist[i][k]+dist[k][j]VertexNum;i++){
		for(Vertex j=0;jVertexNum;j++){
			printf("%4d",dist[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	for(Vertex i=0;iVertexNum;i++){
		for(Vertex j=0;jVertexNum;j++){
			printf("%4d",path[i][j]);
		}
		printf("\n");
	}
	
} 

main.c

#include 
#include 
#include "lgraph.h"
/* run this program using the console pauser or add your own getch, system("pause") or input loop */

int main(int argc, char *argv[]) {
	int VertexNum;
	scanf("%d",&VertexNum);
	LGraph *pGraph=biuld_graph(VertexNum);
	init_visited(VertexNum);
	DFS(pGraph,4);printf("\n");
	init_visited(VertexNum);
	BFS(pGraph,0);printf("\n");
	UNweighted(pGraph,9);
	Dijkstra(pGraph,0);


//	MGraph *pGraph=biuld_mgraph(VertexNum);
//	init_visited(VertexNum);
//	MDFS(pGraph,4);printf("\n\n");
//	init_visited(VertexNum);
//	MBFS(pGraph,0);printf("\n\n");
//	Floyd(pGraph);
	return 0;
}

你可能感兴趣的:(数据结构,c语言,图论)