Dijkstra | RIP | OSFP - I: Dijkstra 算法
这三个算法搁一块,主要因为 Dijkstra 是路由选择算法。后两者是路由转发算法。
01 Dijkstra 算法
该算法复杂度 O(mn), O(mlogn)
其中 n=#vertex, m=#edge
-------------- 分割线 ---------------
-------------- 分割线 ---------------
来源:知乎 https://zhuanlan.zhihu.com/p/129373740
02 基本思想
1. Dijkstra 是有向的,不是 SET。这意味着,每次计算路径的时候,必须考虑起点,最后构成最短路径。
2. 分成两个子集 Selected & Unselected , 记录为 S & U。其中,S含有起点到每个点的距离,而U记录着 S 到每个点的距离,即集合到点的长度 + 集合内部路径长度。
3. 由于每轮选择都是选择最小的外延路径,因此,最终获得的即最短路径。
4. 参与该算法的数据集,点和边,是连通无环的。
这张图我直接引用过来了。
资料来源:知乎 https://zhuanlan.zhihu.com/p/346558578
BTW, 在学习过程中,发现一些好玩的。 国际型比赛中,就是这些从业者吧,发表了很有哲理的话 “不仅要使用工具,还要创造工具”。
用计算机找出 最短路径 有时候还会有些抽象,对不对?
如果用绳子表达所有路径呢?
我左手右手,两边一拉,是不是就是最短路径!很天才有木有?
代码思路:DijkPriUpdate
Graph - Vertex operate
王道关于 Dijkstra
用三个向量,O(e^2) or O(v^2)
王道的方案比较简单,就是用 3个list 就解决算法问题。
而且从解释来看,暂时不考虑网络内部加入新的节点问题。因此,解决的问题是,在网络内部,任意 launch 某个节点,则产生最短路径问题。
邻接矩阵
邻接表
综上,似乎 【邻接矩阵】 结构稍微清晰一些。
选定存储结构之后,就能开始写算法了。王道采用的是三个向量记录节点选取情况。学堂在线是更新节点情况,坦率滴说,学堂在线的算法,说得很模糊,留给学生动手的余地比较多。
03 开始动手工作
准备工作
'''---------------------------------------------------- 路由算法 Dijkstra -- 王道解法 -------------------------------------------------------''' import numpy as np import pandas as pd # -- display the graph from IPython.display import Image Image("https://userspace1.oss-cn-beijing.aliyuncs.com/article/news_20231103_2_7.png",width=400,height=500)
运行结果
Graph 数据结构
针对每个节点, columns={0:"wt", 1:"i_ind", 2:"j_ind"}, i_ind 为起点坐标, j_ind 为终点坐标。
递归基的设计是这样的,认为 5 个节点的图,最多也就是递归 5 次 —— 这应该是 makes sense 的。
# --- 邻接矩阵构造 def is_elem_exist(elem, vec_in): out=0 for i in range(len(vec_in)): if( elem==vec_in[i] ): out=1 break return out def ind_link(vec_in): out_vec=[] for i in range(len(vec_in)): if(vec_in[i] > 0): out_vec.append( i ) return out_vec class MGraph(object): # val权重、i_ind, j_ind为横纵坐标 # On Edge def __init__(self): self.edge_df = [-1,-1,-1] self.edge_df = pd.DataFrame(self.edge_df).T.rename(columns={0:"wt", 1:"i_ind", 2:"j_ind"}) def add_edge(self, wt, i_ind, j_ind): tmp_node = [wt, i_ind, j_ind] tmp_node = pd.DataFrame(tmp_node).T.rename(columns={0:"wt", 1:"i_ind", 2:"j_ind"}) self.edge_df = pd.concat([self.edge_df, tmp_node]).reset_index(drop=True) def edge_num(self): tmp_len = len(self.edge_df) - 1 return tmp_len def vertex_num(self): tmp_vec_1 = self.edge_df['i_ind'] tmp_vec_2 = self.edge_df['j_ind'] # --- deduplicate and output out_vec=[] for i in range(len(tmp_vec_1)): tmp_elem = tmp_vec_1[i] if (i==1): out_vec.append(tmp_elem) elif(tmp_elem>0): tmp_out = is_elem_exist(tmp_elem, out_vec) if(tmp_out==0): out_vec.append(tmp_elem) for i in range(len(tmp_vec_2)): tmp_elem = tmp_vec_2[i] if (tmp_elem>0 ): tmp_out = is_elem_exist(tmp_elem, out_vec) if(tmp_out==0): out_vec.append(tmp_elem) out_vec_num = len(out_vec) return out_vec_num
运行结果
# -- 构造邻接矩阵 tmp_graph = MGraph() tmp_graph.add_edge(10, 0, 1) tmp_graph.add_edge(5, 0, 4) tmp_graph.add_edge(2, 1, 4) tmp_graph.add_edge(3, 4, 1) tmp_graph.add_edge(2, 4, 3) tmp_graph.add_edge(9, 4, 2) tmp_graph.add_edge(1, 1, 2) tmp_graph.add_edge(4, 2, 3) tmp_graph.add_edge(6, 3, 2) tmp_graph.add_edge(7, 3, 0) print("Number of Edge: ", tmp_graph.edge_num()) print("Number of Vertex: ", tmp_graph.vertex_num()) tmp_graph.edge_df
04 关于 Dijkstra Algorithm
基本思想,每层计算出向量权重,加上前一层 WT,选择 smaller WT path
递归基: 迭代次数 == 节点个数
外层循环: 用于 Layer Iteration
函数 cal_one_layer() 参数:图的实例, 起点编号, 之前累计距离, 输入路径向量, 迭代轮数
函数 recursive_path() 参数:图的实例, 之前累计距离, 输入路径 向量, 迭代轮数
函数 dijkstra_alg() 参数:图的实例, 起点编号, 迭代轮数
# --- 一层的活动 def cal_one_layer(g_in, vnum_start_point, dist_in, path_in, round_in): # --- 递归基 v_num = g_in.vertex_num() if( round_in >= v_num ): return dist_in, path_in, round_in # --- 函数内部构造 if(dist_in[vnum_start_point]>=0): accu_wt=dist_in[vnum_start_point] else: accu_wt=0 # --- 本层输出 final=[]; dist=[]; path=[]; for i in range(v_num): final.append(0) dist.append(dist_in[i]) path.append(path_in[i]) # -- 向量均初始化 ind_vec=[] # 与起点对应的 j_ind tmp_edge = g_in.edge_df[g_in.edge_df.wt>0].reset_index(drop=True) for i in range(len(tmp_edge)): if( tmp_edge.i_ind[i]==vnum_start_point ): tmp_j_ind = tmp_edge.j_ind[i] if ( len(ind_vec)>0 ): tmp = is_elem_exist( tmp_j_ind, ind_vec ) dist[vnum_start_point]=accu_wt path[vnum_start_point]=path_in[vnum_start_point] if (len(ind_vec)==0 or tmp==0): ind_vec.append(tmp_j_ind) # -- UPDATE dist and path will be outputed if( dist_in[tmp_j_ind]<0 ): dist[tmp_j_ind]=tmp_edge.wt[i]+accu_wt path[tmp_j_ind]=str(vnum_start_point)+str(tmp_j_ind) elif( dist_in[tmp_j_ind]>tmp_edge.wt[i]+accu_wt ): dist[tmp_j_ind]=tmp_edge.wt[i]+accu_wt path[tmp_j_ind]=path_in[tmp_j_ind]+str(tmp_j_ind) else: dist[tmp_j_ind]=dist_in[tmp_j_ind] path[tmp_j_ind]=path_in[tmp_j_ind] print("The ", round_in, "th Iter \n") print("dist input: ", dist_in) print("dist output:", dist) print("path input: ", path_in) print("path output: ", path,"\n -------- \n") return dist, path, round_in+1 def recursive_path(g_in, tmp_dist, tmp_path, iter_round=0): v_num = g_in.vertex_num() if( iter_round >= v_num ): print("Recursive End on path searching: ", iter_round, "th Round! \n" ) return tmp_dist, tmp_path, iter_round else: # -- 获取能获取到距离的 tmp_ind_arr=ind_link(tmp_dist) dist_arr=[]; path_arr=[] for i in range(len(tmp_ind_arr)): tmp_dist_1, tmp_path_1, iter_round= cal_one_layer(g_in, tmp_ind_arr[i], tmp_dist, tmp_path, iter_round) dist_arr.append(tmp_dist_1) path_arr.append(tmp_path_1) # print(tmp_ind_arr[i]) # print(tmp_dist_1,"\n", tmp_path_1, "\n\n") # -- 逐一比较大小,按照元素位置比较 dist_min = dist_arr[0] path_min = path_arr[0] for i in range(1,len(dist_arr)): dist_min_1 = dist_arr[i] path_min_1 = path_arr[i] for j in range(len(dist_min_1)): if( dist_min[j]> dist_min_1[j] and dist_min_1[j]>0 or dist_min[j]<0 and dist_min_1[j]>0): dist_min[j] = dist_min_1[j] path_min[j] = path_min_1[j] print("The Ultimate Weight: ", np.sum(dist_min)) print("Final Distance: ", dist_min) print("Final Path: ", path_min, "\n\n") return dist_min, path_min, iter_round
def dijkstra_alg(g_in, vnum_start, iter_round=0): # -- 递归基 v_num = g_in.vertex_num() if( iter_round >= v_num ): return tmp_dist, tmp_path # -- 定义算法向量 初始化 final=[]; dist=[]; path=[]; ind_vec=[]; cnt=0 for i in range(v_num): final.append(0) dist.append(-1) path.append("") # -- 计算一层 tmp_dist, tmp_path, iter_round = cal_one_layer(g_in, vnum_start, dist, path, iter_round) # -- 递归 if( iter_round >= v_num ): return tmp_dist, tmp_path else: while( iter_round<v_num and cnt < 10000): if (cnt==0): print("Path Searching - 0") tmp_dist_1, tmp_path_1, iter_round= recursive_path(g_in, tmp_dist, tmp_path, iter_round) else: print("Path Searching - 1") tmp_dist_1, tmp_path_1, iter_round= recursive_path(g_in, tmp_dist_1, tmp_path_1, iter_round) cnt=cnt+1
运行结果
# --- AOV --- dijkstra_alg(tmp_graph, 0, iter_round=0) print("上述 Path 向量中 011 表示在 V1 节点 重新选择过路径")
为了清晰展示,这里 Start Point = 0,搜遍全网,得出长度 dist ,以及路径 path,权重 29。
对于整个图而言,所有节点具有同等被选择的概率。
起点编号 |
最短路径权重 (从起点到所有其他点) |
V0 | 29 |
V1 | 18 |
V2 | 52 |
V3 | 42 |
V4 | 18 |
从测试结果看,起点为 V1 或者 V4 时候,最短路径权重最小。
''' ------------------------------------------------------- The Ultimate Weight: 18 Final Distance: [11, 0, 1, 4, 2] Final Path: ['30', '', '12', '43', '14'] The 1 th Point input. The Ultimate Weight: 18 Final Distance: [9, 3, 4, 2, 0] Final Path: ['30', '41', '422', '43', ''] The 4 th Point input. 上述 Path 向量中 422 表示在 V4 节点 重新选择过路径 注:结果是复制黏贴的,所以用了“引用”。 ---------------------------------------------------------'''
简单起见,我们还是用 V0 展示, Dijkstra Algorithm 选择过程,主要是路径替代过程。
V1 为起点,最短路径构造过程。
V4为起点,最短路径构造过程。
05 考察更多节点的图
笔者从知乎找到一张图,重新测试算法。这种图美中不足的是,以有向图来说,是不连通的。
来源:https://zhuanlan.zhihu.com/p/140058333
我自己赋的权重
针对非连通情况需要做一定的处理,即 dijkstra_alg() 外部再套一层循环。BTW 本来 Dijkstra 也是 “连通假设”。
# --- AOV --- vnum_in = 0 is_visit_vec=[]; dist_sector_arr=[]; path_sector_arr=[] dist_output=[]; path_output=[]; vnum_arr=[] for i in range(tmp_graph.vertex_num()): is_visit_vec.append(-1) while (count_undetected(is_visit_vec) > 0 ): print("The ", vnum_in, "th Point input. ") dist_1, path_1, round_1 = dijkstra_alg(tmp_graph, vnum_in, iter_round=0) dist_sector_arr.append(dist_1) path_sector_arr.append(path_1) vnum_arr.append(vnum_in) # -- 更新 dist_vec for i in range(len(is_visit_vec)): if ( is_visit_vec[i]<0 and dist_1[i]>=0 ): is_visit_vec[i]=1 # -- 确定新一轮 点 for i in range(len(is_visit_vec)): if(is_visit_vec[i] < 0): vnum_in=i break print("And Vertex coverage: ", is_visit_vec, "\n ------- END ------- \n") # -- 非连通图 for i in range(len(dist_sector_arr)): print(dist_sector_arr[i]) print(path_sector_arr[i], "\n\n") # -- 选择最短路径 len_arr = len(dist_sector_arr) for i in range(len(is_visit_vec)): tmp_v=[] tmp_p=[] for j in range(len_arr): tmp_v.append(dist_sector_arr[j][i]) tmp_p.append(path_sector_arr[j][i]) if( dist_sector_arr[j][i]==0 ): k1=j # -- 选择 if( np.sum(tmp_v)== -1*(len_arr-1) ): tmp_out=0 tag=k1 else: tmp_out=tmp_v[0] tag=0 for k in range(1,len(tmp_v)): if( tmp_v[k]>=0 and (tmp_v[k]<tmp_out or tmp_out<0) ): tmp_out=tmp_v[k] tag=k
dist_output.append(tmp_out) path_output.append(str(vnum_arr[tag]) + path_sector_arr[tag][i]) # -- 输出 print("\n --------- Algorithm END --------- \n") print("Algorithm Final Distance Vector: ", dist_output) print("Algorithm Final Path Vector: ", path_output) print("And Vertex coverage: ", is_visit_vec, "\n ------- END ------- \n")
运行结果
''' --------- Algorithm END --------- Algorithm Final Distance Vector: [0, 0, 1, 0, 1, 3, 2, 4, 6, 6, 14, 12, 11, 15] Algorithm Final Path Vector: ['0', '1', '112', '3', '114', '125', '126', '147', '118', '129', '1910', '0011', '1512', '3313'] And Vertex coverage: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] ------- END ------- '''
含义
为了简要显示,笔者还是画图了。
欢迎关注 ShoelessCai.com 。