最短路径 Dijkstra算法 python+C++

视频教程:https://www.bilibili.com/video/BV1ts41157Sy
在这里插入图片描述
——图片取自视频截图

过程主要分两部:

  • 1.将与当前点所连通的所有点找出来,并标记距离。
  • 2.从离得最近的点开始重复第一步
  • 3.直到所有的点都遍历完成

算法可视化:https://www.cs.usfca.edu/~galles/visualization/Dijkstra.html

  1. 从起始点开始,寻找所有可能的路径(这里以0为起始点)
    在这里插入图片描述
  2. 将与当前点所连接的所有点都搜索一遍(广度优先遍历),标记各顶点与当前位置的距离,当前点已经来过,将Known标记为True。
    在这里插入图片描述
  3. 在所有连接的顶点中,找最小的边(最短距离)的顶点。从该最小距离的顶点出发重复上述步骤
    在这里插入图片描述
  4. 遍历完所有的顶点后,我们将得到从起始点到所有顶点的最短距离Cost。
    在这里插入图片描述
  5. 根据每个结点Path的依赖关系,我们可以达到某两个顶点之间的最短路径。例如:从0->2的最短路径是Cost为7,而2顶点可以从1顶点到达,则反推值1顶点发现1顶点可以由0顶点达到,最后得到路径为0->1>2。
    在这里插入图片描述

python实现

from typing import Tuple

import math
import heapq

import pandas as pd

graph = {
    "A": {"B":5,"C": 1},
    "B": {"A": 5,"C":2,"D":1},
    "C": {"A":1,"B":2,"D":4,"E": 8},
    "D": {"B": 1, "C":4,"E": 3,"F": 6},
    "E": {"C": 8,"D": 3,"X":7},
    "F": {"D": 6,"Y":3},
    "X": {},
    "Y": {}
}
# graph = {
#     "A": {"B":5,"C": 1},
#     "B": {"A": 5,"C":2,"D":1},
#     "C": {"A":1,"B":2,"D":4,"E": 8},
#     "D": {"B": 1, "C":4,"E": 3,"F": 6},
#     "E": {"C": 8,"D": 3,},
#     "F": {"D": 6,}
# }

def init_distance(graph:dict, s:str)->dict:
    distance = {s:0}
    for vertex in graph:
        if vertex != s:
            distance[vertex] = math.inf
    return distance

def dijkstra(graph:dict, s:str)->Tuple[dict,dict]:
    pqueue = []                     # 数组,模拟优先级队列
    heapq.heappush(pqueue, (0, s))  # 每个结点保存 (边权值 顶点)
    seen = set()                    # 保存已遍历的顶点
    parent = {s:None}               # 顶点-路径关系
    distance = init_distance(graph, s)    # 顶点-距离关系

    while (len(pqueue) > 0):        # while pqueue
        pair = heapq.heappop(pqueue)
        dist = pair[0]
        vertex = pair[1]
        seen.add(vertex)

        nodes = graph[vertex].keys()    # 顶点A所能达到的所有其他顶点
        for w in nodes:
            if w not in seen:           # 该顶点还没有遇见过
                if dist + graph[vertex][w] < distance[w]:
                    heapq.heappush(pqueue, (dist + graph[vertex][w], w))
                    parent[w]  = vertex
                    distance[w] = dist + graph[vertex][w]

    return parent, distance

parent, distance = dijkstra(graph, "A")       # 调用算法
print( pd.DataFrame(                          # 以表格方式输出
    [parent.values(),distance.values()],      # 数据
    index=["parent","distance"],             # 行名
    columns=parent.keys()) )                  # 列名

# 求任意两点之间最短路径
'''
思路:
	1. 如果两点路径不分叉,即对于路径 A->C->B->D->F->Y, 任意 C F 之间的最短路径即为他们之间的差值
		详细path计算,通过parent数组逆推:patent[F]->D, parent[D]->B, parent[B]->C
		
	2. 如果两点路径分叉,则需要查到公共的祖先结点	   ↑→ F->Y
							例如: A->C->B->D->E->X
							求 X Y 最短路径,则需要先计算出公共的祖先结点 -》D
				详细path计算:前缀:X..D + 后缀:D..Y
				最短路径和计算: dist[X] - dist[comm] + dist[Y] - dist[comm]
'''
print("求最短路径: 输入起点,终点:")
while True:
    start, end = input("->:").split()
    if start in distance.keys() and end in distance.keys():
        pre = []   #  前缀
        path = []  #  计算路径,end
        comm, cur = start, end # 如果没有分叉,comm公共祖先结点就是start,有分叉comm更新至公共结点
        while cur and cur != start:
            path.append(cur)
            cur = parent[cur]
        if start == cur:    # 找到最短路径
            path.append(cur)
            path.reverse()
        else:# 没有找到,start与end不在一条路径上,此时需要start也回溯找公共祖先结点
            while comm not in path:
                pre.append(comm)
                comm = parent[comm]
            # comm为公共祖先结点
            path = pre + path[path.index(comm)::-1]

        print("\tpath: ",path)
        length = distance[end] - distance[comm] + distance[start] - distance[comm]
        print("\tlength: ",length)
    else:
        print("输入有误,无此顶点信息")

无向图:
在这里插入图片描述

运行效果:
在这里插入图片描述

c++版,这里使用数字代替英文字母顶点
在这里插入图片描述

class dijkstra {
private:
	int n;
	vector<int>& vertex;					// 顶点
	vector<vector<pair<int, int>>> e;	// 邻接表
	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> q;	// 优先级队,每次取最近的顶点
	vector<bool> vis;
	vector<int> path;				// path
	vector<int> distance;			// distance
public:
	dijkstra(vector<int>& _vertex, vector<vector<int>>& edges, int begin)
		:n(_vertex.size()),
		vertex(_vertex),
		e(n),
		vis(n),
		path(n),
		distance(n)

	{
		// 邻接表建图
		for (auto& ed : edges) {
			e[ed[0]].emplace_back(pair(ed[1], ed[2]));
			e[ed[1]].emplace_back(pair(ed[0], ed[2]));	// 无向图
		}
		start(begin);
	}
	void showDijkstraFrom(int begin, int end) {
		if (begin < 0 || begin >= n || end < 0 || end >= n)
			throw "begin或end范围有误,请输入范围[0~" + to_string(n) + ")";

		int comm = begin, cur = end;
		// 计算路径
		if (begin > end) {
			swap(comm, cur);
		}

		vector<int> resPath;
		while (cur != -1 && cur != comm) {
			resPath.emplace_back(cur);
			cur = path[cur];
		}
		if (cur != -1) {
			resPath.emplace_back(comm);
		}
		else {// 找不到begin,则说明 begin与end是分叉路
			vector<int> prePath;
			auto it = std::find(resPath.rbegin(), resPath.rend(), comm);
			while (resPath.rend() == it) {	// 没找到
				prePath.emplace_back(comm);
				comm = path[comm];
				it = std::find(resPath.rbegin(), resPath.rend(), comm);
			}
			// 找到公共祖先顶点 begin,迭代器 res
			while (it != resPath.rend()) {
				prePath.emplace_back(*it);
				it++;
			}
			resPath.swap(prePath);	// 统一通过resPath存储详细路径
		}


		if (resPath.front() != begin) {
			std::reverse(resPath.begin(), resPath.end());
		}
		cout << "\t路径:";
		for (auto res : resPath) {
			cout << res << " ";
		}
		int dist = distance[begin] - distance[comm] + distance[end] - distance[comm];
		cout << "\n\t距离:" << dist;

		

		cout << endl;
	}
	void show_table() {

		cout << "vertex:\t";
		for (auto v : vertex) {
			cout << (char)(v + 'A') << ",";
		}

		cout << "\npath:\t";
		for (auto p : path) {
			cout << (char)(p + 'A') << ",";
		}

		cout << "\nparent:\t";
		for (auto dist : distance) {
			cout << dist << ",";
		}
		cout << endl;
	}
private:
	void start(int begin) {

		q.emplace(begin, 0);
		vis[begin] = true;
		path[begin] = -1;

		// 广度优先搜索
		while (!q.empty()) {
			auto [dist, cur] = q.top();
			q.pop();
			vis[cur] = true;

			for (auto [nxt, ndist] : e[cur]) {	/* nxt 下一个顶点, ndist 到下一个顶点的距离 */
				if (!vis[nxt]) {    // nxt未访问过
					if (distance[nxt] == 0 || dist + ndist < distance[nxt]) {
						q.emplace(dist + ndist, nxt);	// 将nxt结点入队
						path[nxt] = cur;
						distance[nxt] = dist + ndist;
					}

				}
			}
		}

	}
};


int main()
{
	vector<int> vertex = { 0,1,2,3,4,5,6, 7 };	// A,B,C,D,E,F
	vector<vector<int>> edges{
		{0,1,5},{0,2,1},
		{1,2,2},{1,3,1},
		{2,3,4},{2,4,8},
		{3,4,3},{3,5,6},
		{5,7,3},{4,6,7}
	};
	dijkstra dij(vertex, edges, 0);
	dij.show_table();

	cout << "求最短路径\n->";
	int begin, end;
	while ((cin >> begin), begin != -1) {	// 输入-1结束
		cin >> end;
		try
		{
			dij.showDijkstraFrom(begin, end);	// ...
		}
		catch (string& e)
		{
			cout << " error:" << e << endl;
		}
		cin.clear();  //清除cin的错误状态
		cin.ignore(); //忽略掉缓冲区的内容
	}
	return 0;
}

在这里插入图片描述

这里在提供一个顶点值为string类型的版本

class dijkstra {
	const string null = " ";	// 起始顶点无parent
private:
	int n;
	unordered_set<string> vertex;					// 顶点
	unordered_map<string, int> mp;			// 辅助表:例如 xiaoming放在邻接表第0行,xiaoqiang --- 1 ...
	vector<unordered_map<string,int>> e;	// 邻接表
	priority_queue<pair<int, string>, vector<pair<int, string>>, greater<>> q;	// 优先级队,每次取最近的顶点
	vector<bool> vis;
	unordered_map<string,pair<string,int>> path_dist;				// path and distance
public:
	dijkstra(vector<string>& _vertex, vector<vector<string>>& edges, string begin)
		:n(_vertex.size()),
		vertex(_vertex.begin(), _vertex.end()),
		e(n),
		vis(n)
	{
		// 初始化mp,将每个名字对应到邻接表的每一行
		int i = 0;
		for (const auto s : vertex) {
			mp[s] = i++;
		}
		// 邻接表建图
		for (auto& ed : edges) {
			e[mp[ed[0]]].emplace(make_pair(ed[1], stoi(ed[2])));
			e[mp[ed[1]]].emplace(make_pair(ed[0], stoi(ed[2])));	// 无向图
		}
		start(begin);
	}
	void showDijkstraFrom(string& begin, string& end) {
		if (!vertex.count(begin) || !vertex.count(end))
			throw "begin或end范围有误,请输入范围[0~" + to_string(n) + ")";

		string comm = begin, cur = end;
		// 计算路径
		if (begin > end) {
			swap(comm, cur);
		}

		vector<string> resPath;
		while (cur != null && cur != comm) {
			resPath.emplace_back(cur);
			cur = path_dist[cur].first;
		}
		if (cur != null) {
			resPath.emplace_back(comm);
		}
		else {// 找不到begin,则说明 begin与end是分叉路
			vector<string> prePath;
			auto it = std::find(resPath.rbegin(), resPath.rend(), comm);
			while (resPath.rend() == it) {	// 没找到
				prePath.emplace_back(comm);
				comm = path_dist[comm].first;
				it = std::find(resPath.rbegin(), resPath.rend(), comm);
			}
			// 找到公共祖先顶点 begin,迭代器 res
			while (it != resPath.rend()) {
				prePath.emplace_back(*it);
				it++;
			}
			resPath.swap(prePath);	// 统一通过resPath存储详细路径
		}


		if (resPath.front() != begin) {
			std::reverse(resPath.begin(), resPath.end());
		}
		cout << "\t路径:";
		for (auto res : resPath) {
			cout << res << " ";
		}
		int dist = path_dist[begin].second - path_dist[comm].second + path_dist[end].second - path_dist[comm].second;
		cout << "\n\t距离:" << dist;



		cout << endl;
	}
	void show_table() {

		cout << "\nvertex:   ";
		for (const auto& ver : path_dist) {
			cout << ver.first << ",  ";
		}
		cout << "\npath:     ";
		for (const auto& path : path_dist) {
			cout << path.second.first << ",  ";
		}
		cout << "\ndistance: ";
		for (const auto& path : path_dist) {
			cout << path.second.second << ",  ";
		}
		cout << endl;
	}
private:
	void start(string begin) {

		q.emplace(make_pair(0,begin));
		vis[mp[begin]] = true;
		path_dist[begin] = make_pair(null, 0);

		// 广度优先搜索
		while (!q.empty()) {
			auto [dist, cur] = q.top();
			q.pop();
			vis[mp[cur]] = true;

			for (auto [nxt, ndist] : e[mp[cur]]) {	/* nxt 下一个顶点, ndist 到下一个顶点的距离 */
				if (!vis[mp[nxt]]) {    // nxt未访问过
					if(path_dist[nxt].second == 0 || dist + ndist < path_dist[nxt].second){
						q.emplace(dist + ndist, nxt);	// 将nxt结点入队
						path_dist[nxt] = make_pair(cur, dist + ndist);
					}

				}
			}
		}

	}
};

int main()
{
	vector<string> vertex = { "A","B","C","D","E","F" };	// A,B,C,D,E,F
	vector<vector<string>> edges{
		{"A","B","5"},{"A","C","1"},
		{"B","C","2"},{"B","D","1"},
		{"C","D","4"},{"C","E","8"},
		{"D","E","3"},{"D","F","6"}
	};
	dijkstra dij(vertex, edges, "A");
	dij.show_table();

	cout << "求最短路径\n->";
	string begin, end;
	while ((cin >> begin), begin != "exit()") {	// 输入exit()结束
		cin >> end;
		try
		{
			dij.showDijkstraFrom(begin, end);	// ...
		}
		catch (string& e)
		{
			cout << " error:" << e << endl;
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我叫RT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值