【圖論】Python [ numpy, pandas] 實現 基礎能力以及基礎演算法 [ dfs bfs spfa ] 經過較為嚴格測試
版權
copyright :散哥[tjut],程坦[tju]
轉載請聯絡;或者有想法的找我要markdown檔案。
輸入
有資料檔案輸入處理部分,有比較清楚的結果輸出
實現的功能
- add_node 新增點,
- remove_node 刪除點,
- add_edge 新增邊,
- remove_edge 刪除邊,
- dgree 計算任意點的度,
- connectivity 計算任意兩個點的連通性,
- distanc A * spfa k短路
- get_node_atr 獲得點的屬性,
- get_node_atr 獲得邊的屬性,
- get_neighbors_iter 獲得任意點的鄰近節點、鄰近的邊,
- has_node 判斷圖是否包含某一個點,
- has_edge 判斷圖是否包含某一條邊,
- size_edge 獲得邊的數量,
- size_node 獲得點的數量
- dfs 深度搜尋
- bfs 廣度搜尋
- 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
- 獲得
n
的想法就是通過獲取行列的長度
n = mat.shape[0]
- 獲取
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
物件。預設新增最後。新增後要更新n
和V
。用到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. 刪除點
- 標記刪除元素的序號,然後用到
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 # 無向圖
【參考文獻】
- 基本概念 https://blog.csdn.net/qq_35295155/article/details/78639997
- 實現方式 https://blog.csdn.net/liangjun_feng/article/details/77585298
- 可達矩陣,指的是用矩陣形式來描述有向連線圖各節點之間經過一定長度的通路後可達到的程度。可達矩陣的計算方法是利用布林矩陣的運算性質。1步,2步或者n步可以到達。(A+I)^ n. B=A+A^ 2 + A^ 3+……+A^n
- python中list的四種查詢方法 https://blog.csdn.net/lachesis999/article/details/53185299
- 圖的四種最短路徑演算法 https://blog.csdn.net/qibofang/article/details/51594673
- SPFA 演算法詳解 https://blog.csdn.net/muxidreamtohit/article/details/7894298
- ACM題目:最短路徑 http://hs-yeah.github.io/acm/2014/08/13/01-最短路徑-SPFA
- 【python常見面試題】之python 中對list去重的多種方法 https://www.cnblogs.com/tianyiliang/p/7845932.html
- 單源最短路徑快速演算法(spfa)的python3.x實現 https://blog.csdn.net/ye_xiao_yu/article/details/79441384
獲得授權的轉載:
簡書:
如果有用 歡迎打賞!
相關文章
- Python Numpy基礎教程Python
- python基礎之 python實現PID演算法及測試的例子Python演算法
- Numpy基礎
- 基礎圖論圖論
- 圖論基礎圖論
- Pandas 基礎 (2) - Dataframe 基礎
- NumPy基礎知識圖譜
- Pandas基礎
- 圖論演算法遍歷基礎圖論演算法
- 軟體測試基礎理論
- pandas學習之Python基礎Python
- 深度學習基礎-基於Numpy的卷積神經網路(CNN)實現深度學習卷積神經網路CNN
- 圖論基礎(自認為很全)圖論
- JS 基礎篇(五):JS嚴格模式JS模式
- 測試基礎(四)Jmeter基礎使用JMeter
- 軟體測試理論(1)基礎理論
- python+requests介面測試基礎Python
- 圖論(一)--基礎概念圖論
- 圖論入門基礎圖論
- Web測試基礎-Html基礎知識WebHTML
- Pandas進階貳 pandas基礎
- Python基礎面試題30問!Python基礎教程Python面試題
- pandas - 基礎屬性
- Pandas基礎介紹
- Pandas 基礎 (16) - Holidays
- Pandas基礎學習
- Python基礎為重,成就月薪過萬Python
- 圖論(三)--各種基礎圖演算法總結圖論演算法
- Pytest 實踐:Python 測試技術基礎知識Python
- Python測試框架pytest入門基礎Python框架
- 學會使用 NumPy:基礎、隨機、ufunc 和練習測試隨機
- Data Science | Numpy基礎(一)
- Data Science | Numpy基礎(二)
- 【python基礎】input函式的基礎使用以及進階Python函式
- web基礎(四)嚴格模式與混雜模式Web模式
- python實現圖(基於圖的不同儲存方式)的深度優先(DFS)和廣度(BFS)優先遍歷Python
- Pandas 基礎 (14) - DatetimeIndex and ResampleIndex
- Pandas 基礎 (17) - to_datetime