1. 程式人生 > >Python廣度優先搜尋得到兩點間最短路徑

Python廣度優先搜尋得到兩點間最短路徑

前言

之前一直寫不出來,這週週日花了一下午終於弄懂了= =|| , 順便放部落格裡,方便以後忘記了再看看
要實現的是輸入一張 圖,起點,終點,輸出起點和終點之間的最短路徑

廣度優先搜尋

適用範圍: 無權重的圖,與深度優先搜尋相比,深度優先搜尋法佔記憶體少但速度較慢,廣度優先搜尋算法佔記憶體多但速度較快

複雜度: 時間複雜度為O(V+E),V為頂點數,E為邊數

思路

廣度優先搜尋是以層為順序,將某一層上的所有節點都搜尋到了之後才向下一層搜尋;
比如下圖:
這裡寫圖片描述

從0結點開始搜尋的話,一開始是0、將0加入佇列中;
然後下一層,0可以到達的有1,2,4,將他們加入佇列中;
接下來是1,1能到達的且未被訪問的是結點3


順序就是 01,2,43,這裡用下劃線表示每一層搜尋得到的結點;

每一次用cur = que[head]取出頭指標指向的結點,並搜尋它能到達的結點;因此,可以用一個佇列que來儲存已經訪問過的結點,佇列有頭指標head以及尾指標tail,起點start與結點i有邊並且結點i未被訪問過,則將該結點加入佇列中,tail指標往後移動;當tail等於頂點數時演算法結束
對於每一次while迴圈,head都加一,也就是往右邊移動,比如一開始head位置是0,下一層的時候head位置元素就為1,也就是搜尋與結點1有邊的且未被訪問的結點
用一個數組book來標識結點i是否已經被訪問過;用字典來儲存起點到各個點的最短路徑;
程式碼如下:

import numpy as np

ini_matrix = [
          [0, 1, 1, 0, 1],
          [1, 0, 0, 1, 0],
          [1, 0, 0, 0, 1],
          [0, 1, 0, 0, 0],
          [1, 0, 1, 0, 0]
         ]


def bfs(matrix_para, start_point_para, end_point_para):
    """
    廣度優先搜尋
    :param matrix_para 圖
    :param start_point_para 起點
    :param end_point_para 終點
    :return: 返回關聯度
    """
matrix = matrix_para start_point = start_point_para end_point = end_point_para vertex_num = len(matrix) # 頂點個數 que = np.zeros(vertex_num, dtype=np.int) # 佇列, 用於儲存遍歷過的頂點 book = np.zeros(vertex_num, dtype=np.int) # 標記頂點i是否已經被訪問,1表被訪問,0表未被訪問 point_step_dict = dict() # key:點,value:起點到該點的步長 # 佇列初始化 head = 0 tail = 0 # 從起點出發,將起點加入佇列 que[tail] = start_point # 等號右邊為頂點號(起點) tail += 1 book[start_point] = 1 # book[i] i為頂點號 while head<tail: cur = que[head] for i in range(vertex_num): # 判斷從頂點cur到頂點i是否有邊,並判斷頂點i是否已經被訪問過 if matrix[cur][i] == 1 and book[i] == 0: que[tail] = i # 將頂點i放入佇列中 tail += 1 # tail指標往後移 book[i] = 1 # 標記頂點i為已經訪問過 point_step_dict[i] = head + 1 # 記錄步長 if tail == vertex_num: # 說明所有頂點都被訪問過 break head += 1 for i in range(tail): print(que[i]) try: relevancy = point_step_dict[end_point] return relevancy except KeyError: # 捕獲錯誤,如果起點不能到達end_point,則字典裡沒有這個鍵,返回None return None result = bfs(ini_matrix, 1, 4) print("result:", result)

錯誤

在經同學的一番調教之後,我深刻意識到了這段程式碼有個問題(不能用head記錄步長),就是對於有環的時候,可能得到的步長(迭代次數)會比最短路徑還大;
比如,起點為4,終點為3:這裡每一遍迭代都是一次while迴圈
第一遍迭代,佇列4,head指向4,步長為0
第二遍迭代,佇列4,0 , 2,head指向0, 步長為1
第三遍迭代,佇列4,0 , 2,1,head指向2,步長為2,
第四遍迭代,對於2,2周圍都被訪問過了,但此時head仍然+=1為3,這就導致了下一次的步長會比實際的步長多1
第五遍迭代, 3,步長為4

糾正

改進的思路:用count記錄步長,flag用於標識當前搜尋能到達的邊的該結點cur = que[head]周圍是否已經被訪問過,False表示沒有,True表示該結點i周圍都被訪問過了;也就是,當flag為False時,表示對於cur周圍已經都訪問過了,此時步長count不需要自增1;

import numpy as np

ini_matrix = [
          [0, 1, 1, 0, 1],
          [1, 0, 0, 1, 0],
          [1, 0, 0, 0, 1],
          [0, 1, 0, 0, 0],
          [1, 0, 1, 0, 0]
         ]


def bfs(matrix_para, start_point_para, end_point_para):
    """
    廣度優先搜尋
    :param matrix_para 圖
    :param start_point_para 起點
    :param end_point_para 終點
    :return: 返回關聯度
    """
    matrix = matrix_para
    start_point = start_point_para
    end_point = end_point_para

    vertex_num = len(matrix) # 頂點個數

    que = np.zeros(vertex_num, dtype=np.int) # 佇列, 用於儲存遍歷過的頂點
    book = np.zeros(vertex_num, dtype=np.int) # 標記頂點i是否已經被訪問,1表被訪問,0表未被訪問

    point_step_dict = dict() # key:點,value:起點到該點的步長

    # 佇列初始化
    head = 0
    tail = 0

    # 迭代次數
    count = 0

    # 從0號頂點出發,將0號頂點加入佇列
    que[tail] = start_point # 等號右邊為頂點號(起點)
    tail += 1
    book[start_point] = 1 # book[i] i為頂點號

    while head<tail:
        flag = False # 用flag標識結點i是否周圍都是被訪問過的
        cur = que[head]
        for i in range(vertex_num):
            # 判斷從頂點cur到頂點i是否有邊,並判斷頂點i是否已經被訪問過
            if matrix[cur][i] == 1 and book[i] == 0:
                que[tail] = i # 將頂點i放入佇列中
                tail += 1 # tail指標往後移
                book[i] = 1 # 標記頂點i為已經訪問過
                point_step_dict[i] = count + 1 # 記錄步長
                flag = True
            if tail == vertex_num: # 說明所有頂點都被訪問過
                break
        if flag:
            count += 1
        head += 1

    for i in range(tail):
        print(que[i])

    try:
        relevancy = point_step_dict[end_point]
        return relevancy
    except KeyError:
        return None

result = bfs(ini_matrix, 3, 4)
print("result:", result)

寫在後面

真的很抱歉, 第一次寫這種演算法部落格結果出了這麼大的問題,之前都是一些記錄BUG的文章,還好同學及時和我說了,主要原因還是自己沒有做那麼多測試的問題。
有什麼可以改進的地方還請批評指正,我會努力改進的!