[算法入土之路]图

本文包含了图的宽度优先遍历, 深度优先遍历,拓扑排序序列,prim算法, kruskal算法, dijkstra算法 


class Custom(ValueError):
    pass

class GraphQues:
    def __init__(self, graph):
        self.graph = graph

    def BFS(self, node_name):
        '''
        从一个节点出发进行宽度优先遍历
        :param node_name: 开始节点的名称
        :return:
        '''
        # 从图中根据节点的名称获取节点
        node = self.graph.nodes.get(node_name, None)
        if not node: return
        s = set()
        q = queue.Queue()
        q.put(node)
        s.add(node)
        while not q.empty():
            item = q.get()
            print(item.value, end=" ")
            for i in item.nexts:
                if i not in s:
                    q.put(i)
                    s.add(i)
        return

    def DFS(self, node_name):
        """
        深度优先遍历
        :param node_name: 开始节点名称
        :return:
        """
        node = self.graph.nodes.get(node_name, None)
        if not node: return
        stack = Stack()  # 遍历的路径
        s = set()  # 纪录已经走过的节点
        s.add(node)
        stack.push(node)
        while True:
            flag = True
            cur_node: GraphNode = stack.pop()
            for next_node in cur_node.nexts:
                if next_node not in s:
                    stack.push(cur_node)
                    stack.push(next_node)
                    s.add(next_node)
                    flag = False
                    break
            if flag:
                stack.push(cur_node)
                break
        return stack

    def Topological_sort(self):
        '''
        图的拓扑排序序列
        :return: 拓扑排序序列
        '''
        if not self.graph.is_directed:
            raise Custom("无向图没有拓扑排序序列")

        def get_in_0(nodes_dic):
            """
            找到一个入度为0 的节点
            :type nodes_dic: dict[Node.value:Node]
            :return 找到了 返回节点, 没找到 抛出异常
            """
            if not nodes_dic:
                return
            for inner_node in nodes_dic.values():
                if inner_node.in_ == 0:
                    return inner_node
            raise Custom("没有没有入度为0的节点了")

        ans = []
        nodes = self.graph.nodes.copy()
        while node := get_in_0(nodes):  # 获得入度为0 的节点
            ans.append(node)  # 结果序列扩充
            for elem in node.nexts:  # 使该节点的指向节点的入度-1
                elem.in_ -= 1
            del nodes[node.value]  # 从节点序列中删除该节点
        return ans  # 返回节点序列

    def prim(self, node: GraphNode = None):
        """
        普利姆算法
            要求 无向连通图
        :param node: 起始节点 若不填则随即选择一个节点作为起始节点
        :return: 最小生成树序列
        """
        if not node or node.value not in self.graph.nodes:  # 如果没有给初始值或者给的起始节点不再图中
            node = self.graph.nodes.get(choice(self.graph.nodes))  # 随即选择一个节点作为起始节点
        heap = []  # 边的最小堆
        nodes_ = {node}  #
        ans = []  # 结果序列
        while len(ans) < len(self.graph.nodes) - 1:  # 最小生成树的边数应为 节点数 - 1
            # 将当前节点的出边与已经存在的边的最小堆合并, 剔除指向节点已经在结果序列的边, 根据边权值排序
            heap = list(heapq.merge(heap, filter(lambda x: x.to_ not in nodes_, node.edges), key=lambda x: x.weight))
            elem = heap.pop(0)  # 弹出边权值最小的边
            if elem.to_ not in nodes_:  # 查看边的指向节点是否已经找到了
                node = elem.to_  # 节点指针移动到下一个节点
                nodes_.add(elem.to_)  # 节点集合扩充
                ans.append(elem)  # 结果序列扩充
        return ans  # 返回结果序列

    def kruskal(self):
        """
        克鲁斯卡尔算法
        要求 无向图
        :return: 最小生成树 边权序列
        """
        inner_dsu = DSU(list(self.graph.nodes.values()))  # 将所有节点初始化到并查集
        edges = sorted(list(self.graph.edges), key=lambda x: x.weight)  # 根据权值排序
        ans = []  # 结果序列
        for edge in edges:  # 遍历所有边 最坏的结果找到权值最大的那条边
            if not inner_dsu.is_connected(edge.from_, edge.to_):  # 判断俩节点是否已经属于同一棵树
                inner_dsu.union(edge.from_, edge.to_)  # 如果不是连接两节点
                ans.append(edge)  # 结果序列添加该边
            # 最小生成树 边数为节点数 - 1
            if len(ans) >= len(self.graph.nodes) - 1:
                return ans  # 成功找到所有需要的序列 返回

    def Dijkstra(self, begin_node_name):
        """
        迪杰斯特拉算法
            找到从一个起始节点开始到其他节点的距离
        :param begin_node_name: 起始节点名称
        :return:
        """
        if begin_node_name not in self.graph.nodes:
            raise Custom(f"不存在此节点, {begin_node_name}")
        begin_node = self.graph.nodes.get(begin_node_name)
        node_dic = {node: -1 for node in self.graph.nodes.values() if node != begin_node}  # 给节点赋初值
        node_dic[begin_node] = 0  # 将初始节点的距离置为0
        select_nodes = [begin_node]
        active_nodes = {begin_node}

        def update(node: GraphNode):
            """
            更新起始节点到其他节点的距离
            更改 node_dic
            :param node:
            :return:
            """
            nonlocal node_dic, select_nodes, active_nodes  # 本地变量
            for edge in node.edges:  # 遍历出边
                if edge.to_ in select_nodes:
                    continue  # 如果出边是已经确定了最终距离的节点直接跳过
                active_nodes.add(edge.to_)  # 将找到的结点激活
                weight = node_dic.get(edge.from_) + edge.weight  # 获取从本节点到其他节点的距离
                if node_dic.get(edge.to_) == -1 or node_dic[edge.to_] > weight:
                    # 条件1: 未被更新过
                    # 条件2: 更新过但是新的距离更短
                    node_dic[edge.to_] = weight  # 如果符合条件 则更新距离

        while len(select_nodes) < len(self.graph.nodes):
            cur_node = sorted(active_nodes, key=lambda x: node_dic.get(x))[0]  # 在激活的结点集中找一个现有距离最短的结点
            select_nodes.append(cur_node)  # 确定了到该节点的最终最短距离
            update(cur_node)  # 以 cur_node 为起始节点 激活直接相邻的结点
            active_nodes.remove(cur_node)  # 使结点从激活结点集中删除
        return node_dic  # 返回结果序列

输出函数

def show_stack(s: Stack):
    if s.is_empty(): return
    while not s.is_empty():
        print(s.pop().show(), end="<-")
    print("\b\b")

def show_node_list(nodes: list):
    for node in nodes:
        print(node.show(), end=" ")
    print()

def show_edge_weight(edges: list):
    # 输出全部边权集合
    print("Print all node_node_weight sets".center(50, "*"))
    index = 0
    for edge in edges:
        if index == 6:
            index = 0
            print()
        index += 1
        print(edge.show(), end="  ")
    print()
    print("End".center(50, "*"))

部分测试函数

if __name__ == '__main__':
    g = ChangeToDefGraph.node_node_weight(src_data, False)
    gq = GraphQues(g)
    # 输出全部边权集合
    show_edge_weight(g.edges)
    # 深度优先遍历
    # s = gq.DFS(0)
    # show_stack(s)
    # 拓扑排序序列
    # res = gq.Topological_sort()
    # show_node_list(res)
    # res = gq.prim(g.nodes.get(0))
    # res = gq.kruskal()
    # show_node_list(res)
    # res = gq.prim(g.nodes.get(0))
    # show_node_list(res)
    res = gq.Dijkstra('g')
    print("---".center(50))
    for key, value in res.items():
        print(key.show(), value, end=" | ")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值