【圖論】Python [ numpy, pandas] 實現 基礎能力以及基礎演算法 [ dfs bfs spfa ] 經過較為嚴格測試

烏恩大俠發表於2018-12-12

版權

copyright :散哥[tjut],程坦[tju]
轉載請聯絡;或者有想法的找我要markdown檔案。

輸入

有資料檔案輸入處理部分,有比較清楚的結果輸出

實現的功能

  1. add_node 新增點,
  2. remove_node 刪除點,
  3. add_edge 新增邊,
  4. remove_edge 刪除邊,
  5. dgree 計算任意點的度,
  6. connectivity 計算任意兩個點的連通性,
  7. distanc A * spfa k短路
  8. get_node_atr 獲得點的屬性,
  9. get_node_atr 獲得邊的屬性,
  10. get_neighbors_iter 獲得任意點的鄰近節點、鄰近的邊,
  11. has_node 判斷圖是否包含某一個點,
  12. has_edge 判斷圖是否包含某一條邊,
  13. size_edge 獲得邊的數量,
  14. size_node 獲得點的數量
  15. dfs 深度搜尋
  16. bfs 廣度搜尋
  17. spfa 最短路徑搜尋演算法

輸入測試資料

a b c d e
a 0 1 2 5 3
b 1 0 3 5 4
c 2 3 0 inf 5
d 5 5 inf 0 6
e 3 4 5 6 0

實現方法

OOP Python
庫: pandas numpy
創新:使用 numpy.narray 作為資料的儲存物件
難點: 實現了 spfa

程式碼以及解釋

0.匯入庫檔案

import pandas as pd
import numpy as np

1. 建立矩陣

解析: 1)讀取檔案。檔案地址為:file_path ,使用到的pandas函式為:read_csv()(點選跳轉到文件說明)

pd.read_csv(file_path)

2)注意: 讀取後 是float資料型別,由於存在inf。在處理邊權的時候 由於是正數,所以需要轉化一下,用65535來代替inf最大值。實現如下:

inf = 65535
mat.replace(float('inf'), self.inf)

3)把np.float64資料格式就可以轉化為整數int型別了。注意由於存在負邊權,所以要轉化為np.int32的資料型別。用到的實現就是重新建立一下。

pd.DataFrame(self.mat, dtype=np.int32)

4)然後我們的矩陣就建立好了;資料底層的儲存是pd.DataFrame。 那麼後期要是方便處理,我們會繼續把DataFrame物件轉化為numpy裡面的np.narray物件

mat_np = mat_DataFrame.values

2. 獲取圖的一些資訊: 矩陣長度n 和 頂點的名字V

  1. 獲得n的想法就是通過獲取行列的長度
n = mat.shape[0]
  1. 獲取V就是使用現成的函式即可
V = mat.columns.tolist()

0,1,2 綜合

class CGraph:
    def __init__(self, file_path):
        self.inf = 65535                                     # 定義最大值65535
        self.raw_mat = pd.read_csv(file_path)                # 讀檔案
        self.mat = self.raw_mat.iloc[0:, 1:]                 # 方便處理而採取去掉左邊的字母
        self.mat = self.mat.replace(float('inf'), self.inf)  # inf用65535 代替 2^16=65536 uint16
        self.mat = pd.DataFrame(self.mat, dtype=np.int32)    # float轉化int 重新生成一個即可 可以存在負值
        self.n = self.mat.shape[0]                           # n * n 矩陣 : 求n個頂點
        self._is_digraph_undirected()                        # 判斷是否為有向圖 type 記錄
        self.V = self.mat.columns.tolist()                   # 頂點 [ 格式:列表 ] 把名字給列出來 列表的格式

3. 新增點

方法一: 直接操作pd.DataFrame物件。預設新增最後。新增後要更新nV。用到insert函式插入一列以及一個拼裝的函式concat用來把行給拼起來,用這個操作的原因是沒有直接新增行的函式。提取某一行的方法是loc。

class CGraph:
    def add_node(self, vi, location=-1):
        """
        新增點
        預設新增最後一個
        :return:
        """
        if self._not_exist(vi):
            if location == -1 or location == self.n:
                # 插入最後
                self.mat.insert(self.n, vi, 0)
                insert_row = pd.DataFrame([0 for n in range(len(self.mat) + 1)]).T
                insert_row.columns = self.mat.columns.values.tolist()
                self.mat.loc[vi] = 0
            else:
                # 插入指定的地方
                self.mat.insert(location, vi, 0)
                insert_row = pd.DataFrame([0 for n in range(len(self.mat) + 1)]).T
                insert_row.columns = self.mat.columns.values.tolist()
                above = self.mat.loc[:location-1]
                below = self.mat.loc[location:]
                self.mat = pd.concat([above, insert_row, below], ignore_index=True)
        else:
            print('點已經存在列表')
        self.mat.reset_index(drop=True, inplace=True)  # 行 重新排序
        self.n += 1
        self.V = self.mat.columns.tolist()

詳解: pandas.DataFrame.insert(loc, column, value, allow_duplicates=False)
loc:int 插入離子指數。必須驗證0 <= loc <= len(列)
column:字串,數字或可雜湊物件插入列的標籤
value : int,Series或array-like
allow_duplicates : bool,可選

# 底層實現程式碼
    def insert(self, loc, column, value, allow_duplicates=False):
        self._ensure_valid_index(value)
        value = self._sanitize_column(column, value, broadcast=False)
        self._data.insert(loc, column, value,
                          allow_duplicates=allow_duplicates)

4. 刪除點

  1. 標記刪除元素的序號,然後用到pop函式和drop函式,更新序號的reset_index函式
class CGraph:
    def remove_node(self, vi):
        """
        刪除點
        :return:
        """
        if self._not_exist(vi):
            print('列表不存在此點')
        else:
            vi_index = self.V.index(vi)                    # 確定vi在列表的中的index
            self.mat.pop(vi)                               # 刪除 vi 列
            self.mat.drop(vi_index, inplace=True)          # 刪除 對應行
            self.mat.reset_index(drop=True, inplace=True)  # 行 重新排序
        self.n -= 1
        self.V = self.mat.columns.tolist()

5. 新增邊

class CGraph:
    def add_edge(self, vi, vj, val=1):
        """
        新增邊
        :return:
        """
        if self._is_exist(vi) and self._is_exist(vj):
            vi_index = self.V.index(vi)
            vj_index = self.V.index(vj)
            if self._invalid(vi_index) or self._invalid(vj_index):
                if self.type == 0:
                    # 無向圖 加兩個邊
                    self.mat.ix[vi_index, vj_index] = val
                    self.mat.ix[vj_index, vi_index] = val
                else:
                    # 有向圖 加一個邊
                    self.mat.ix[vi_index, vj_index] = val
        else:
            print('定點不存在')

6. 刪除邊

class CGraph:
    def remove_edge(self, vi, vj):
        """
        刪除邊
        :return:
        """
        if self._is_exist(vi) and self._is_exist(vj):
            vi_index = self.V.index(vi)
            vj_index = self.V.index(vj)
            if self._invalid(vi_index) or self._invalid(vj_index):
                if self.type == 0:
                    # 無向圖 加兩個邊
                    self.mat.ix[vi_index - 1, vj_index - 1] = self.inf
                    self.mat.ix[vj_index - 1, vi_index - 1] = self.inf
                else:
                    # 有向圖 加一個邊
                    self.mat.ix[vi_index - 1, vj_index - 1] = self.inf
        else:
            print('定點不存在')

7. 計算任意點的度

class CGraph:
    def degree(self, vi):
        """
        計算任意點的度        :入度 + 出度
        :return:
        """
        vi_index = self.V.index(vi)
        temp = self.mat.copy()
        temp[temp == 65535] = 0                 # 處理最大值為 0
        temp[temp != 0] = 1                      # 別的路徑處理為 1
        x_sum = temp.loc[vi_index].sum()        # 行 求和
        y_sum = temp[vi].sum()                  # 列 求和
        if self.type == 1:
            # 有向圖  返回 [ 總度 入度 出度 ]   行列的數值不同
            mat_sum = x_sum + y_sum
            return [mat_sum, x_sum, y_sum]
        else:
            # 無向圖  行列的數值一樣
            return [x_sum, x_sum, x_sum]

8. 計算任意兩個點的連通性

class CGraph:
    def connectivity(self, vi, vj):
        """
        計算任意兩個點的連通性
        :return:
        """
        if self._is_exist(vi) and self._is_exist(vj):
            vi_index = self.V.index(vi)
            vj_index = self.V.index(vj)
            if self._invalid(vi_index) or self._invalid(vj_index):
                temp = self.mat.values                   # 轉化為 numpy 資料格式
                temp[temp == 65535] = 0
                temp[temp != 0] = 1                       # 把路徑轉化為 1
                for i in range(self.n - 2):
                    temp = np.dot(temp, temp)            # 矩陣運算 n-2 次 求可達矩陣
                return temp[vi_index][vj_index] > 0      # 判斷是否大於 0
        else:
            print('點不存在')
            return False
```python

9. 獲得點的屬性 臨近點

class CGraph:
    def get_node_atr(self, vi):
        """
        獲得點的屬性 臨近點
        :return:
        """
        if self._is_exist(vi):
            vi_index = self.V.index(vi)
            temp = self.mat.copy()
            temp[temp == 65535] = 0
            temp[temp != 0] = 1
            temp_bool = temp.values[vi_index] > 0
            return np.array(self.V)[temp_bool].tolist()

10. 獲得邊的屬性 共點邊

class CGraph:
    def get_edge_atr(self, vi, vj):
        """
        獲得邊的屬性 共點邊
        :return:
        """
        if self._is_exist(vi) and self._is_exist(vj):
            v_name = [vi, vj]
            temp_mat = self.mat.values
            temp_mat[temp_mat == 65535] = 0
            edge_atr = []
            for i in v_name:
                temp_name = self.get_node_atr(i)
                for j in temp_name:
                    i_index = self.V.index(i)
                    j_index = self.V.index(j)
                    if self.type == 1:
                        if temp_mat[i_index][j_index] != 0 or temp_mat[j_index][i_index] != 0:
                            edge_atr.append([i, j])
                    else:
                        if temp_mat[i_index][j_index] != 0:
                            edge_atr.append([i, j])
            return edge_atr
        else:
            print('邊不存在')
            return 0

11. 獲得任意點的鄰近節點、鄰近的邊

class CGraph:
    def get_neighbors_iter(self, vi):
        """
         獲得任意點的鄰近節點、鄰近的邊
        :param vi:  任意點 vi
        :return:
        """
        if self._is_exist(vi):
            neighbors = {}
            neighbors['node'] = self.get_node_atr(vi)
            temp_mat = self.mat.values
            temp_mat[temp_mat == 65535] = 0
            edge_atr = []
            temp_name = self.get_node_atr(vi)
            i_index = self.V.index(vi)
            for j in temp_name:
                j_index = self.V.index(j)
                if self.type == 1:
                    if temp_mat[i_index][j_index] != 0 or temp_mat[j_index][i_index] != 0:
                        edge_atr.append([vi, j])
                else:
                    if temp_mat[i_index][j_index] != 0:
                        edge_atr.append([vi, j])
            neighbors['edge'] = edge_atr
            return neighbors

12. 判斷圖是否包含某一個點

class CGraph:
    def has_node(self, vi):
        """
        判斷圖是否包含某一個點
        :return:
        """
        return self._is_exist(vi)

13. 判斷圖是否包含某一條邊

class CGraph:
    def has_edge(self, vi, vj, val=65535):
        """
         判斷圖是否包含某一條邊
        :return:
        """
        if self._is_exist(vi) and self._is_exist(vj):
            vi_index = self.V.index(vi)
            vj_index = self.V.index(vj)
            if self._invalid(vi_index) or self._invalid(vj_index):
                return self.mat.ix[vi_index, vj_index] == val
        else:
            print('邊不存在')
            return False

14. 獲得邊的數量

class CGraph:
    def size_edge(self):
        """
        獲得邊的數量
        :return:
        """
        temp = self.mat.copy()
        temp[temp == 65535] = 0  # 處理最大值為 0
        temp[temp != 0] = 1  # 別的路徑處理為 1
        if self.type == 1:
            return temp.sum().sum()
        else:
            return temp.sum().sum()/2

15. 獲得點的數量 直接返回即可

class CGraph:
    def size_node(self):
        """
        :return: 獲得點的數量 直接返回即可
        """
        return self.n

16. 求最短路徑 spfa

class CGraph:
    def spfa(self, vi, vj):
        """
        求最短路徑
        :param vi:  頂點 vi
        :param vj:  頂點 vj
        :return:    距離 路徑
        """
        if self._is_exist(vi) and self._is_exist(vj):
            vi_index = self.V.index(vi)
            vj_index = self.V.index(vj)
            if vi_index != vj_index:
                v_min = min(vi_index, vj_index)
                v_max = max(vi_index, vj_index)
                # 核心演算法
                mat = self.mat.values          # 生成 二維矩陣 鄰接矩陣 方便操作
                queue = Queue()                # 優化演算法 佇列
                d = [self.inf] * self.n        # 距離 陣列
                flag = [False] * self.n        # 標記 是否在佇列
                count = [0] * self.n           # 判斷 是否存在負環
                pre_node = -np.ones(self.n, dtype=np.int8)  # 記錄前驅節點,沒有則用-1表示
                # 初始化
                d[v_min] = 0
                queue.put(v_min)
                flag[v_min] = True
                u = 0
                # 寬鬆開始
                while not queue.empty():
                    u = queue.get()                           # 出隊
                    flag[u] = False                           # 出佇列 改變標記
                    for i in range(self.n):
                        if mat[u][i] != self.inf:             # 判斷是否 有連線
                            temp_d = d[u] + mat[u][i]
                            if temp_d < d[i]:
                                d[i] = temp_d                  # 鬆弛
                                pre_node[i] = u                # 鬆弛後就更新前驅節點
                                if not flag[i]:                # 查詢是否入隊
                                    queue.put(i)               # 入隊
                                    flag[i] = True             # 標記入隊
                                    count[i] += 1              # 入隊 計數器 加一
                                    if count[i] > self.n:      # 如果計數器 大於 n 那麼認為存在負環
                                        print('存在負環')
                                        return False
                pro_node = pre_node.tolist()
                pro_node.reverse()
                road_pro = [self.V[v_min]]
                for ij in pre_node:
                    if ij == -1:
                        break
                    else:
                        road_pro.append(self.V[ij])
                road_pro.append(self.V[v_max])
                new_li = list(set(road_pro))
                new_li.sort(key=road_pro.index)
                re = {'min_value': d[v_max], 'road': new_li}
                return re
            else:
                print('頂點相同')
                return False
        else:
            print('點不存在')
            return False

17. 廣度搜尋 bfs

class CGraph:
    def bfs(self, vi):
        """
        廣度搜尋
        :param vi:
        :return:
        """
        if self._is_exist(vi):
            iter_list = [vi]
            father_node = [vi]
            father_node_temp = []
            son_node = []
            for x in range(self.n):
                for i in father_node:
                    son_node = self.get_node_atr(i)
                    for j in son_node:
                        iter_list.append(j)
                        if iter_list.count(j) == 0:
                            father_node_temp.append(j)
                father_node = father_node_temp.copy()
                father_node_temp.clear()
            return iter_list
        else:
            print('頂點不存在')
            return False

18. 深度搜尋 dfs

class CGraph:
    def dfs(self, mat, vi, visited):
        """
        dfs 遞迴呼叫
        :param mat:  鄰接矩陣
        :param vi:   訪問頂點
        :param visited:  訪問列表
        :return:
        """
        visited[vi] = True
        print(self.V[vi])
        for i in range(self.n):
            if mat[vi][i] != self.inf and not visited[i]:
                self.dfs(mat, i, visited)
class CGraph:
    def dfs_traverse(self):
        """
        DFS 入口
        :return:
        """
        visited = [False] * self.n
        mat = self.mat.values
        print("--- DFS ---")
        for i in range(self.n):
            if not visited[i]:
                self.dfs(mat, i, visited)
        print("-----------")

19. 檢驗輸入的結點是否合法

class CGraph:
    def _invalid(self, v):
        """
        檢驗輸入的結點是否合法
        :param v: 頂點 v
        :return: bool 型別
        """
        return v > 0 or v >= self.n

20. 檢驗輸入 屬性名字是否存在

class CGraph:
    def _not_exist(self, vi):
        # 檢驗輸入 屬性名字是否存在
        if self.V.count(vi) == 0:
            return True             # 新增點 用到
        else:
            return False

21. 判斷 定點是否存在

class CGraph:
    def _is_exist(self, vi):
        # 用於 邊 操作             判斷 定點是否存在
        if vi in self.V:
            return True
        else:
            return False

22. 判斷 有向圖 無向圖 演算法

class CGraph:
    def _is_digraph(self):
        # 判斷 有向圖 無向圖 演算法
        for i in range(self.n):
            for j in range(i, self.n):
                if self.mat.ix[i, j] != self.mat.ix[j, i]:
                    return False
        return True
class CGraph:
    def _is_digraph_undirected(self):
        # 記錄 是否為 有向圖 或者 無向圖 為   1 或者 0
        if not self._is_digraph():
            self.type = 1            # 有向圖
        else:
            self.type = 0            # 無向圖

【參考文獻】

  1. 基本概念 https://blog.csdn.net/qq_35295155/article/details/78639997
  2. 實現方式 https://blog.csdn.net/liangjun_feng/article/details/77585298
  3. 可達矩陣,指的是用矩陣形式來描述有向連線圖各節點之間經過一定長度的通路後可達到的程度。可達矩陣的計算方法是利用布林矩陣的運算性質。1步,2步或者n步可以到達。(A+I)^ n. B=A+A^ 2 + A^ 3+……+A^n
  4. python中list的四種查詢方法 https://blog.csdn.net/lachesis999/article/details/53185299
  5. 圖的四種最短路徑演算法 https://blog.csdn.net/qibofang/article/details/51594673
  6. SPFA 演算法詳解 https://blog.csdn.net/muxidreamtohit/article/details/7894298
  7. ACM題目:最短路徑 http://hs-yeah.github.io/acm/2014/08/13/01-最短路徑-SPFA
  8. 【python常見面試題】之python 中對list去重的多種方法 https://www.cnblogs.com/tianyiliang/p/7845932.html
  9. 單源最短路徑快速演算法(spfa)的python3.x實現 https://blog.csdn.net/ye_xiao_yu/article/details/79441384

獲得授權的轉載:
簡書:

如果有用 歡迎打賞!
在這裡插入圖片描述

相關文章