【图论】Python [ numpy, pandas] 实现 基础能力以及基础算法 [ dfs bfs spfa ] 经过较为严格测试

版权

copyright :散哥[tjut],程坦[tju]
转载请联系;或者有想法的找我;

输入

有数据文件输入处理部分,有比较清楚的结果输出

实现的功能

  1. add_node 添加点,
  2. remove_node 删除点,
  3. add_edge 添加边,
  4. remove_edge 删除边,
  5. dgree 计算任意点的度,
  6. connectivity 计算任意两个点的连通性,
  7. distanc 计算任意两个点的之间的距离注意区别(有权图和无权图),
  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 最短路径搜索算法

输入测试数据

abcde
a01253
b10354
c230inf5
d55inf06
e34560

实现方法

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-%E6%9C%80%E7%9F%AD%E8%B7%AF%E5%BE%84-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

来源:scdn:【图论】Python [ numpy, pandas] 实现 基础能力以及基础算法 [ dfs bfs spfa ] 经过较为严格测试 https://blog.csdn.net/qq_36666115/article/details/84970644

    原文作者:散人lins
    原文地址: https://www.jianshu.com/p/26917f47a5fd
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞