图的最短路径

图的最短路径



前言

在这里插入图片描述
在这里插入图片描述
最短路径分为两种:
(1)单源路径:从某顶点出发,到其他全部顶点的最短路径
(2)顶点间的最短路径:任意两个顶点之间的最短路径
最短路径的结果主要有两个方面:
(1)顶点之间最短路径的长度
(2)从源顶点到目标顶点的路径


一、BFS

注意:BFS只适用于无权图。,即所有边的长度都是1
在这里插入图片描述
在这里插入图片描述

#include<iostream>
#include<vector>
#include<queue>
#include<set>

using namespace std;

const int n = 7; 

// 邻接表 
vector<vector<int> > graph;

// from结点到该点的最短路径
vector<int> dist(n); 

// from结点到该点的前驱结点,通过前驱节点可以获取到from到该点的路径 
vector<int> pre(n); 

// 是否已经遍历过
vector<int> isvisited(n);

void DFS(int from){
	// 初始化最小路径
	for(int i = 0;i < n;i++){
		dist[i] = -1;
		isvisited[i] = false;
		pre[i] = -1;
	} 
	// 顶点from到form的路径长度为0
	dist[from] = 0;
	// 前驱节点为自己。 
	pre[from] = from;
	// 当前节点设置为访问
	isvisited[from] = true;
	
	queue<int> m_queue;
	m_queue.push(from);
	while(!m_queue.empty()){
		from = m_queue.front();
		m_queue.pop();
		// 遍历from的所有边
		for(int i = 0;i < graph[from].size();i++){
			if(isvisited[graph[from][i]]){continue;	}
			// 该边的最短路径等于上一层结点的level + 1 
			dist[graph[from][i]] = dist[from] + 1;
			// 当前节点设置为已经访问过 
			isvisited[graph[from][i]] = true;
			// 当前节点加入队列 
			m_queue.push(graph[from][i]);
			// 前驱结点设置为from 
			pre[graph[from][i]] = from;
		}	
	}
}

int main(){
	graph.push_back({1,2});
	graph.push_back({0,3});
	graph.push_back({0,3,4,5});
	graph.push_back({1,2,4});
	graph.push_back({2,3,5});
	graph.push_back({2,4,6});
	graph.push_back({5});
	cout<<"从节点0开始找去其他节点的最短路径:"<<endl; 
	DFS(0);
	cout<<"最短路径长度为:";
	for(int i = 0;i < n;i++){
		cout<<dist[i]<<" ";
	}
	return 0;
} 

在这里插入图片描述

二、Dijkstra

在这里插入图片描述
https://zhuanlan.zhihu.com/p/129373740
初始化:
(1)出发点是第一个已经确定最短路径的顶点
(2)更新与出发点有路径的顶点的最短路径和前驱顶点
循环
(1)从未确定最短路径的顶点中选取最短路径最小的顶点为新确定最短路径的顶点
(2)更新与新确认的顶点有路径的顶点的最短路径和前驱点(如果新路径更短就更长,新路径更新则不更新)

在这里插入图片描述

代码如下:

#include<iostream>
#include<vector>
#include<limits.h>
 
using namespace std;

const int n = 6;

// 定义有权图 
struct Graph{
	
	int vec_num; //顶点个数
	
	vector<vector<int>> edges = vector<vector<int>>(n, vector<int>(n,0)); 
}; 

// 定义确定最短路径的顶点集合
bool set[n];

// 定义前驱节点。
int pre[n]; 
 
// 定义最短路径的和
int dest[n]; 

void dijkstra(Graph &graph,int from){
	// 初始化与from节点相连的节点的路径 
	for(int i = 0;i < graph.vec_num;i++){
		// 设置节点为未确定最短路径 
		set[i] = false;
		if(i == from) { pre[i] = -1; continue;}
		
		// from到当前节点的距离为0,就代表没有路径 
		if(graph.edges[from][i] == 0){
			pre[i] = -1;
			dest[i] = INT_MAX;
		}else{
			// 有路径的话,前驱节点就是from 
			pre[i] = from; 
			// 设置最短路径为当前啊节点到i的距离 
			dest[i] = graph.edges[from][i]; 
		}
	} 
	// from的最短路径已经确定 
	set[from] = true;
	
	// 下面开始循环
	for(int i = 0;i < graph.vec_num;i++){
		int min = INT_MAX;
		// 循环所有的dest,寻找最小并且没有被确定路径的值
		for(int j = 0;j < graph.vec_num;j++){
			if(!set[j]&&dest[j] < min){
				min = dest[j];
				from = j;
			}
		}
		// 从未确定路径的所有dest中找到最小的,确定其路径 
		set[from] = true;
		// 从from开始,计算与from相连的边的最小路径 
		for(int j = 0;j < graph.vec_num;j++){
			if(!set[j]&&graph.edges[from][j]&&dest[from] + graph.edges[from][j] < dest[j]){
				dest[j] = dest[from] + graph.edges[from][j];
				pre[j] = from;
			} 
		} 
	}	
}

int main(){
	Graph graph;
	graph.vec_num = 6;
	
	graph.edges[0][1] = graph.edges[1][0] = 5;
	graph.edges[0][3] = graph.edges[3][0] = 10;
	graph.edges[0][2] = graph.edges[2][0] = 8;
	graph.edges[1][3] = graph.edges[3][1] = 4;
	graph.edges[3][4] = graph.edges[4][3] = 2;
	graph.edges[1][4] = graph.edges[4][1] = 9;
	graph.edges[1][2] = graph.edges[2][1] = 2;
	graph.edges[4][2] = graph.edges[2][4] = 3;
	graph.edges[4][5] = graph.edges[5][4] = 2;
	graph.edges[2][5] = graph.edges[5][2] = 6;
	dijkstra(graph,0);
	cout<<"最短路径:"<<endl;
	for(int i = 0;i < n;i++){
		cout<<dest[i]<<" ";
	}
	cout<<endl; 
	cout<<"前驱节点:"<<endl;
	for(int i = 0;i < n;i++){
		cout<<pre[i]<<" ";
	}
	
} 

在这里插入图片描述
需要注意的是:Dijkstra和Prim算法生成的最小生成树是不一样的
因为Dijkstra到顶点4的时候,发现0到4的路径和是4,而0到7的路径和是3.
在这里插入图片描述
另外:Dijkstra算法不适用于权值为负数的图
在这里插入图片描述
1到2的最短路径应该是10-5等于5.然而Dijkstra算法会先确定1,在确定2,然后寻找2到其他顶点的最小路径,这里面2确定的时候,确定为7,所以 Dijkstra算法不适用于权值为负数的图

三、Floyd

求图中任意两点的最小路径,用Dijkstra需要跑N/2次。Floyd用于求任意两点的最小路径
在这里插入图片描述
核心观点就是,初始化之后,不断往现有的最短路径里面添加节点作为中转点,再求一次最短路径。

在这里插入图片描述

#include<iostream>
#include<vector>
#include<limits.h>
 
using namespace std;

const int n = 5;

// 定义有权图 
struct Graph{
	
	int vec_num; //顶点个数
	
	vector<vector<int>> edges = vector<vector<int>>(n, vector<int>(n,0)); 
}; 


// 定义前驱节点。
vector<vector<int>> pre(n,vector<int>(n,-1)); 
 
// 定义最短路径的和
vector<vector<int>> dest(n,vector<int>(n,-1)); 

void Floyd(Graph &graph){
	// 1、初始化;即将现有的边加到路径和中去 
	for(int i = 0;i < graph.vec_num;i++){
		for(int j = 0;j < graph.vec_num;j++){
			if(i==j) dest[i][j] = 0;
			else if(graph.edges[i][j]){
				dest[i][j] = graph.edges[i][j];	
			}
		}
	}
	
	for(int i = 0;i < graph.vec_num;i++){
		for(int j = 0;j < graph.vec_num;j++){
			cout<<dest[i][j]<<"	"; 
		}
		cout<<endl; 
	}
	
	// 2、逐渐往里面加点,尝试
	for(int k = 0;k < graph.vec_num;k++){
		for(int i = 0;i < graph.vec_num;i++){
			for(int j = 0;j < graph.vec_num;j++){
				// i-k、k-j 之间有边才行,这里适用于负权重的情况
				if(graph.edges[i][k]&&graph.edges[k][j]&&dest[i][k] + dest[k][j] < dest[i][j]){
					dest[i][j] = dest[i][k] + dest[k][j];
					pre[i][j] = k;
				}
			}
		}
	} 
}

int main(){
	Graph graph;
	graph.vec_num = n;
	
	graph.edges[0][1] = graph.edges[1][0] = 5;
	graph.edges[0][2] = graph.edges[2][0] = 10;
	graph.edges[0][4] = graph.edges[4][0] = 7;
	
	graph.edges[1][2] = graph.edges[2][1] = 2;
	graph.edges[1][3] = graph.edges[3][1] = 9;
	graph.edges[1][4] = graph.edges[4][1] = 2;
	
	graph.edges[3][2] = graph.edges[2][3] = 6;
	graph.edges[3][4] = graph.edges[4][3] = 6;
	Floyd(graph);
	cout<<"最短路径:"<<endl;
	for(int i = 0;i < graph.vec_num;i++){
		for(int j = 0;j < graph.vec_num;j++){
			cout<<dest[i][j]<<"	"; 
		}
		cout<<endl; 
	}	
	
	cout<<"前驱节点:"<<endl;
	for(int i = 0;i < graph.vec_num;i++){
		for(int j = 0;j < graph.vec_num;j++){
			cout<<pre[i][j]<<"	"; 
		}
		cout<<endl; 
	}
} 

在这里插入图片描述


总结

图的最短路径,两种:(1)单源最短路径,就是从顶点到其他任意顶点的最短路径(2)任意两点的最短路径;单源最短路径用BFS和Dijkstra。BFS只适用于无权图Dijkstra既适用于无权图又适用与有权图,但是不适用于路径为负的情况。Floyd用于求任意两点的最小路径,而且使用于负权重的问题。

  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值