无向图割点、最小生成树和欧拉回路的寻找

看数据结构与算法,顺带实现了所有的数据结构,包括堆和无向图,拿来测试用

自己实现的heap,可以使用python的heapq代替
但是 python 的 heapq使用的是 []结构,直接比较元素的大小进行堆排序 参考heapq.py文件中heappush和_siftdown函数的实现
1 当元素是字典结构时候,需要重新定义元素的比较运算。重写函数_siftdown
2 当元素是新建的类的对象时,重新实现 lt gt le ge eq __ne__比较函数即可。可以参考 tornado/asyncio/events.py TimerHandle

# -*- encoding: utf-8 -*-

from graphviz import Digraph
from heap import heap


class NodeWithoutDirection(object):
    def __init__(self, node_name: str):
        self.name = node_name
        self.edges = set()

    def add_edge(self, edge):
        assert edge not in self.edges, "边已经存在"
        self.edges.add(edge)

    def sub_edge(self, edge):
        assert edge in self.edges, "节点没有该边"
        self.edges = self.edges - {edge}

    def get_edge(self, node):
        for edge in self.edges:
            if edge.nodes == {self, node}:
                return edge
        assert False, "与该节点没有边"


class EdgeWithoutDirection(object):
    def __init__(self, node1: NodeWithoutDirection, node2: NodeWithoutDirection, weight):
        self.nodes = {node1, node2}
        self.weight = weight

    def get_another_node(self, node: NodeWithoutDirection):
        assert node in self.nodes, "节点不属于边"
        # (self.nodes - {node}).pop()
        node1, node2 = self.nodes
        if node == node1:
            return node2
        return node1


def pre_order(start_node: NodeWithoutDirection, node_status: dict, pre_order_list: list):
    """
    先序遍历
    :param start_node: 开始节点
    :param node_status: 所有节点的状态
    :param pre_order_list: 先序遍历节点的顺序
    :return: None
    """
    pre_order_list.append(start_node)
    for edge in start_node.edges:
        another_node = edge.get_another_node(node=start_node)
        if not node_status[another_node]["num"]:
            node_status[start_node]["direction_nodes"].append(another_node)
            node_status[another_node]["num"] = len(pre_order_list) + 1
            node_status[another_node]["father"] = start_node
            pre_order(start_node=another_node, node_status=node_status, pre_order_list=pre_order_list)
        elif start_node not in node_status[another_node]["direction_nodes"] and \
                node_status[another_node]["num"] < node_status[start_node]["num"]:
            node_status[start_node]["anti_direction_nodes"].append(another_node)


class GraphWithoutDirection(object):
    def __init__(self):
        self.nodes = []
        self.node_names = set()
        self.node_name_2_node_index = {}
        self.edges = []

    def add_node(self, node_name: str, near_node_names: list, weights: list = None):
        """
        添加节点
        :param node_name: 新节点名
        :param near_node_names: 新节点相邻的节点名列表
        :param weights: 对应的权重值
        :return: None
        """
        if weights is None:
            weights = [1 for _ in near_node_names]
        assert len(near_node_names) == len(weights), "参数个数不一致"
        node_names = [node_name] + near_node_names
        weights = [0] + weights
        for index, name in enumerate(node_names):
            if name not in self.node_names:
                new_node = NodeWithoutDirection(node_name=name)
                self.node_names.add(name)
                self.nodes.append(new_node)
                self.node_name_2_node_index[name] = len(self.nodes) - 1
            if name != node_name:
                node1 = self.nodes[self.node_name_2_node_index[node_name]]
                node2 = self.nodes[self.node_name_2_node_index[name]]
                new_edge = EdgeWithoutDirection(node1=node1, node2=node2, weight=weights[index])
                self.edges.append(new_edge)
                node1.add_edge(edge=new_edge)

    def show(self, file_name):
        """
        将无向图转化为pdf 好检查结果
        :param file_name: pdf名字
        :return: None
        """
        d = Digraph(filename=file_name, directory="./pdf_data")
        d.clear()
        edges_known = []
        # 将所有节点 画出
        for name in self.node_names:
            d.node(name=name, label=name)
        for node in self.nodes:
            for edge in node.edges:
                node1, node2 = edge.nodes
                if edge.nodes not in edges_known:
                    edges_known.append(edge.nodes)
                    d.edge(tail_name=node1.name, head_name=node2.name, label=str(edge.weight))
        d.view()

    def min_span_tree_by_prim(self):
        """
        最少生成树
        所有已知点 和 未知点的最小权重边加到图中,直到所有的节点都加到图中为止
        :return: None
        """
        new_graph = GraphWithoutDirection()
        node_names_known = {min(self.node_names)}
        while len(node_names_known) != len(self.node_names):
            all_min_weight_edge = None
            for node_name_known in node_names_known:
                node_known = self.nodes[self.node_name_2_node_index[node_name_known]]
                min_weight_edge = None
                for edge in node_known.edges:
                    another_node = (edge.nodes - {node_known}).pop()
                    if another_node.name not in node_names_known and (min_weight_edge is None or min_weight_edge.weight > edge.weight):
                        min_weight_edge = edge
                if min_weight_edge is None:
                    continue
                if all_min_weight_edge is None or all_min_weight_edge.weight > min_weight_edge.weight:
                    all_min_weight_edge = min_weight_edge
            all_min_weight_edge_node_names = {node.name for node in all_min_weight_edge.nodes}
            node_names_known = node_names_known.union(all_min_weight_edge_node_names)
            node_name_1 = all_min_weight_edge_node_names.pop()
            node_name_2 = all_min_weight_edge_node_names.pop()
            weight = all_min_weight_edge.weight
            new_graph.add_node(node_name=node_name_1, near_node_names=[node_name_2], weights=[weight])
            new_graph.add_node(node_name=node_name_2, near_node_names=[node_name_1], weights=[weight])
        new_graph.show(file_name="Prim最小生成树")

    def min_span_tree_by_kruskal(self):
        """
        最小生成树  寻找最小权重的边加到图中
        条件就是 不能形成环,直到所有节点都被连接停止
        未使用 Find/Join 模型
        :return: None
        """
        new_graph = GraphWithoutDirection()
        edge_heap = heap.Heap(len(self.edges))
        edge_heap_list = []
        for edge in self.edges:
            value = edge.weight
            heap_node = heap.HeapNode(value=value, info=edge)
            edge_heap_list.append(heap_node)
        edge_heap.build_heap(edge_heap_list)
        node_names_known = [i for i in range(len(self.node_names))]
        step = 0
        while edge_heap and step < len(self.node_names) - 1:
            edge = edge_heap.pop().info
            node_1, node_2 = edge.nodes
            index_1 = self.node_name_2_node_index[node_1.name]
            index_2 = self.node_name_2_node_index[node_2.name]
            group_id_1, group_id_2 = node_names_known[index_1], node_names_known[index_2]
            if group_id_1 != group_id_2:
                step += 1
                node_name_1 = node_1.name
                node_name_2 = node_2.name
                weight = edge.weight
                new_graph.add_node(node_name=node_name_1, near_node_names=[node_name_2], weights=[weight])
                new_graph.add_node(node_name=node_name_2, near_node_names=[node_name_1], weights=[weight])
                all_index = sorted([group_id_1, group_id_2])
                for index, group_id in enumerate(node_names_known):
                    if group_id == all_index[1]:
                        node_names_known[index] = all_index[0]
        new_graph.show(file_name="Kruskal最小生成树")

    def depth_first_search(self, start_node: NodeWithoutDirection = None, connect_check: bool = True):
        """
        先序遍历和后续遍历 所有节点 计算 Num值和Low值
        :param start_node: 开始节点
        :param connect_check: 是否检查图的连通性(默认是检查的,但是寻找欧拉路径是可以不连通的)
        :return: 返回所有节点的信息 和 截点的列表
        """
        status = {}
        pre_order_node_list = []
        cut_vertex_list = []
        if start_node is None:
            start_node = self.nodes[0]
        for node in self.nodes:
            status[node] = {"num": 0 if node != start_node else 1,
                            "low": len(self.nodes) if node != start_node else 1,
                            "direction_nodes": [],
                            "anti_direction_nodes": [],
                            "father": None
                            }
        pre_order(start_node=start_node, node_status=status, pre_order_list=pre_order_node_list)
        if connect_check:
            assert len(pre_order_node_list) == len(self.nodes), "图不连通"
        for node in pre_order_node_list[::-1]:
            anti_direction_nodes = status[node].get("anti_direction_nodes")
            direction_nodes = status[node].get("direction_nodes")
            if anti_direction_nodes:
                for anti_direction_node in anti_direction_nodes:
                    if status[anti_direction_node].get("num") < status[node]["low"]:
                        status[node]["low"] = status[anti_direction_node].get("num")
            elif not direction_nodes:
                status[node]["low"] = status[node]["num"]
                continue
            if direction_nodes:
                for direction_node in direction_nodes:
                    if status[direction_node]["low"] < status[node]["low"]:
                        status[node]["low"] = status[direction_node]["low"]
            if node == pre_order_node_list[0]:
                cut_vertex_list += [node.name] if len(status[node]["direction_nodes"]) > 1 else []
                continue
            for direction_node in direction_nodes:
                if status[direction_node]["low"] >= status[node]["num"]:
                    cut_vertex_list.append(node.name)
                    break
        return status, cut_vertex_list

    def find_cut_vertex(self):
        """
        借助 深度优先搜索 查询 割点
        :return: 割点列表
        """
        _, cut_vertex_list = self.depth_first_search()
        return cut_vertex_list

    def check_euler_circuit(self):
        """
        检验 欧拉回路是否存在 三种情况
        1. 节点的度 全部为偶数 True
        2. 节点的度 为 奇数的个数 大于 2 False
        3. 节点的度 为 奇数的个数 等于 2 Half True(严格意义上不是欧拉回路但是可以经过所有的边,但是回不到初始的节点)
        :return: bool, None or List
        """
        edges_num = [len(node.edges) % 2 for node in self.nodes]
        odd_edges_nodes = []
        for index, edge_num in enumerate(edges_num):
            if edge_num == 1:
                odd_edges_nodes.append(self.nodes[index])
            if len(odd_edges_nodes) > 2:
                return False, None
        if not odd_edges_nodes:
            return True, None
        if len(odd_edges_nodes) == 2:
            return True, odd_edges_nodes

    def find_euler_circuit(self):
        """
        寻找欧拉回路
        :return: 欧拉回路的节点名
        """
        status, nodes = self.check_euler_circuit()
        if not status:
            return []
        if not nodes:
            start_node = self.nodes[0]
            return self._find_euler_circuit(start=start_node)
        base_path = self._find_path_by_topology(start=nodes[0], end=nodes[1])
        return self._find_euler_circuit(start=nodes[0], euler_circuit=base_path)

    def _find_euler_circuit(self, start: NodeWithoutDirection, euler_circuit: list = None):
        """
        :param start: 从哪个节点开始 寻找欧拉路径。
        如果所有节点的度都是 偶数 那么 开始的节点随意
        但是 仅有两个节点的度是 奇数 那么 开始的节点必须是这两个节点的其中一个
        :param euler_circuit: 基础的欧拉回路
        如果所有节点的度都是 偶数 那么 开始的欧拉回路的列表是空
        但是 仅有两个节点的度是 奇数 那么 开始的欧拉回路是这两个节点的通路路径
        :return: 欧拉回路
        """
        if euler_circuit is None:
            euler_circuit = []
        index = 0
        while index >= 0:
            circle = self._find_path_by_topology(start=start)
            euler_circuit = euler_circuit[:index] + circle + euler_circuit[index + 1:]
            for i in range(index, len(euler_circuit) + 1):
                if i == len(euler_circuit):
                    index = -1
                elif len(euler_circuit[i].edges) >= 2:
                    index = i
                    start = euler_circuit[index]
                    break
        return euler_circuit

    def _find_path_by_topology(self, start: NodeWithoutDirection, end: NodeWithoutDirection = None):
        """
        寻找从开始节点到结束节点的路径
        当结束节点为空时 寻找开始节点到本身的闭环路径
        并将路径所经过的边 在图中删除 防止重复使用
        :param start: 开始节点
        :param end: 结束节点
        :return: 路径
        """
        status, _ = self.depth_first_search(start_node=start, connect_check=False)
        target_node, target_num = None, 1
        for node, info in status.items():
            node_num = info.get("num")
            node_low = info.get("low")
            if end and node == end:
                target_node = node
                break
            if node_num > target_num and node_low == 1:
                target_node = node
                target_num = node_num
        path = []
        last_node = target_node
        while target_node != start:
            path.append(target_node)
            father_node = status[target_node].get("father")
            edge = target_node.get_edge(node=father_node)
            target_node.sub_edge(edge=edge)
            edge = father_node.get_edge(node=target_node)
            father_node.sub_edge(edge=edge)
            target_node = father_node
        path.append(start)
        path = list(reversed(path))
        if end is None:
            path.append(start)
            edge = last_node.get_edge(node=start)
            last_node.sub_edge(edge=edge)
            edge = start.get_edge(node=last_node)
            start.sub_edge(edge=edge)
        return path
# -*- encoding: utf-8 -*-
import random

from graphviz import Digraph


class HeapNode(object):
    def __init__(self, value, info):
        self.value = value
        self.info = info


class Heap(object):
    def __init__(self, cap):
        self.cap = cap
        self.size = 0
        self.heap = [None]

    def show(self, file_name=None):
        d = Digraph(filename=file_name, directory="./pdf_data")
        d.clear()
        node_name = []
        for node in self.heap:
            if node is None:
                node_name.append(None)
                continue
            name = str(id(node))
            d.node(name=name, label=str(node.value))
            node_name.append(name)
        max_father_index = self.size // 2
        for father_index in range(1, max_father_index + 1):
            left_son_index = father_index * 2
            right_son_index = father_index * 2 + 1
            if left_son_index <= self.size:
                d.edge(head_name=node_name[left_son_index], tail_name=node_name[father_index])
            if right_son_index <= self.size:
                d.edge(head_name=node_name[right_son_index], tail_name=node_name[father_index])
        d.view()

    def insert(self, node: HeapNode):
        self.heap.append(None)
        self.size += 1
        index = self.size
        while index > 1:
            father_index = index // 2
            if self.heap[father_index].value > node.value:
                self.heap[index] = self.heap[father_index]
                index = father_index
            else:
                break
        self.heap[index] = node
        return 1

    def pop(self):
        assert self.size > 0, "空堆"
        first_node = self.heap[1]
        last_node = self.heap.pop()
        self.size -= 1
        if first_node == last_node:
            return first_node
        index = 1
        while index <= self.size // 2:
            left_son = self.heap[index * 2]
            father_index = index
            right_son_index = index * 2 + 1
            self.heap[index] = left_son
            if left_son.value < last_node.value:
                index *= 2
            if right_son_index <= self.size and self.heap[right_son_index].value < last_node.value and self.heap[right_son_index].value < self.heap[father_index].value:
                self.heap[father_index] = self.heap[right_son_index]
                index = right_son_index
            if index == father_index:
                break
        self.heap[index] = last_node
        return first_node

    def get_value(self, key):
        index = self.find_node_index(key=key)
        return self.heap[index].value

    def swap_two_node(self, index1, index2):
        self.heap[index1], self.heap[index2] = self.heap[index2], self.heap[index1]

    def keep_father_lt_son(self, father_index):
        """
        下滤 操作
        :param father_index: 父节点下标
        :return: None
        """
        if father_index > self.size // 2:
            return
        left_index = father_index * 2
        right_index = father_index * 2 + 1
        index = father_index
        if self.heap[left_index].value < self.heap[father_index].value:
            index = left_index
        if right_index <= self.size and self.heap[right_index].value < self.heap[father_index].value and self.heap[right_index].value < self.heap[left_index].value:
            index = right_index
        if index == father_index:
            return
        self.swap_two_node(index1=index, index2=father_index)
        self.keep_father_lt_son(father_index=index)

    def build_heap(self, n: list):
        assert len(n) <= self.cap, "堆超限"
        self.heap.extend(n)
        self.size = len(n)
        father_index = self.size // 2
        for index in range(father_index, 0, -1):
            self.keep_father_lt_son(father_index=index)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值