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 。

 

    

posted on 2023-11-03 05:15  Mira_2019  阅读(24)  评论(0编辑  收藏  举报