python算法复习(三)----图和前缀树

# 本章重点讲的是图结构,顺带一点前缀树的内容,这两个结构题目的解答比较偏向于套路解答
# 前缀树节点结构:
class TrieNode(object):
    def __init__(self):
        self.transfer = 0  # 这个字符的经过次数
        self.end = 0  # 这个字符是多少个次的结尾
        self.nexts = [None] * 26  # 有通往"a""b""c"...的路吗


# 图的存储方式:
#     1.邻接表 (大多是以点作为单位,标出它直接相邻的其他点)
#     2.邻接矩阵 (矩阵中的值用来表示点于点边的距离)
# 图分为有向图的无向图,有向图就是有方向的,无向图就是没有方向的(个人理解为双向的haha)
# 如果能把题目中给出的图结构转化为自己最熟悉的结构,剩下的就是简单的套路了。
# 下面展示一种很全面的结构,后续的算法都是基于这个结构完成的
import sys


class My_graphic_structure(object):
    def __init__(self):
        self.nodes = {}  # {编号:点}
        self.edges = set()  # 边

    def popNode(self, node_key):
        node = self.nodes[node_key]
        self.nodes.pop(node_key)
        for edge in node.edges:
            self.edges.discard(edge)
        for n in node.nexts:
            n.in_degree -= 1


class graphic_node(object):
    def __init__(self, value):
        self.value = value
        self.in_degree = 0  # 入度
        self.out_degree = 0  # 出度
        self.nexts = list()  # 这个点的直接邻居有哪些
        self.edges = list()  # 从这个点出去的边有哪些

    def __repr__(self):
        return str(self.value)


class graphic_edge(object):
    def __init__(self, lenght, origin, destination):
        self.lenght = lenght  # 长度
        self.origin = origin  # 起点
        self.destination = destination  # 终点

    def __repr__(self):
        return str(self.lenght)


# 把一个给定的图结构转化为上述的结构
# 例: [[5, 0, 1], [3, 1, 2], [7, 0, 2]],参数分别表示[边长,起点,终点]
def graphic_convert(oldstructure):
    graph = My_graphic_structure()
    for edge in oldstructure:
        lenght = edge[0]
        origin = edge[1]
        destination = edge[2]

        if origin not in graph.nodes:
            # 加入点集
            graph.nodes[origin] = graphic_node(origin)
        if destination not in graph.nodes:
            graph.nodes[destination] = graphic_node(destination)

        originNode = graph.nodes.get(origin)
        destinationNode = graph.nodes.get(destination)
        newEdge = graphic_edge(lenght, originNode, destinationNode)

        # 起点邻接表加上终点、出度+1
        originNode.nexts.append(destinationNode)
        originNode.out_degree += 1
        # 终点入度+1
        destinationNode.in_degree += 1
        # 图和起点的边集加上边
        originNode.edges.append(newEdge)
        graph.edges.add(newEdge)
    return graph


# 图的宽度优先遍历
# 宽度优先遍历其实就是先把一个点的所有nexts都遍历完,再遍历next的nexts
def graphic_Width_first(graph):
    queue = []
    res = []
    traversal = set()
    for key in graph.nodes:
        _ = key
        break
    first_node = graph.nodes[key]
    queue.append(first_node)
    traversal.add(first_node)
    while queue != []:
        cur = queue.pop(0)
        res.append(cur)
        for node in cur.nexts:
            if node not in traversal:
                traversal.add(node)
                queue.append(node)
    return res


# 图的深度优先遍历
# 从一个点一直往下遍历,直到这个点的nexts都被遍历过,然后再往回看之前的点的其他nexts
def graphic_depth_first(graph):
    stack = []
    res = []
    traversal = set()
    for key in graph.nodes:
        _ = key
        break
    first_node = graph.nodes[key]
    stack.append(first_node)
    traversal.add(first_node)
    res.append(first_node)
    while stack != []:
        cur = stack.pop()
        for node in cur.nexts:
            if node not in traversal:
                res.append(node)
                stack.append(cur)
                stack.append(node)
                traversal.add(node)
                break
    return res


# 拓扑排序
# 找到入度为0的点,然后把和这个点有关的点的入度-1.直到找到所有的点
def topological_sorting(graph):
    res = []
    while graph.nodes != {}:
        for nodeKey in graph.nodes:
            if graph.nodes[nodeKey].in_degree == 0:
                res.append(graph.nodes[nodeKey])
                graph.popNode(nodeKey)
                break
    return res


# 最小生成树
# 最小生成树就是怎么保证节点之间的连通性的情况下,边长总和最小
# 有三种该方法,分别是kruskal算法和prim算法和Di jkstra算法
# kruskal算法:把所有边长排序,再依次加上,看看有没有形成环,如果形成了环就不要.运用类似于并查集的结构,并查集在后面内容中介绍,这里只是用到它的思想
def kruskal(graph):
    res = []
    edges = sorted(graph.edges, key=lambda x: x.lenght, reverse=False)
    nodeMap = {}
    for node in graph.nodes:
        nodeMap[node] = set({node})
    while edges != []:
        edge = edges.pop(0)
        if nodeMap[str(edge.destination)] != nodeMap[str(edge.origin)]:
            # 每次连上这条边,就把这一团对应的集合都要更新
            nodeMap[str(edge.destination)].update(nodeMap[str(edge.origin)])
            nodeMap[str(edge.origin)] = nodeMap[str(edge.destination)]
            for node in nodeMap[str(edge.origin)]:
                nodeMap[node] = nodeMap[str(edge.origin)]
            res.append(edge)
    return res


# prim算法:从一个点出发,解锁所有的相邻边,从中选择一条最短的,把新节点记录,如果最小的那条左右两边的点都被记录过了,选择第二短的
def prim(graph):
    edges = []
    res = []
    traversal = set()
    for node in graph.nodes.values():
        if node not in traversal:
            traversal.add(node)
            for edge in node.edges:
                edges.append(edge)
            while edges != []:
                edges = sorted(edges, key=lambda x: x.lenght, reverse=False)
                edge = edges.pop(0)
                if edge.destination not in traversal:
                    traversal.add(edge.destination)
                    res.append(edge)
                    for edge in edge.destination.edges:
                        if edge.destination not in traversal:
                            edges.append(edge)
        return res


# Di jkstra算法
# 找一个节点作为起始点,用map记录这个点和其他点的距离
# 删除map中当前所在的点位,以当前点位的值为基准选出能减小到其他点距离的边
def di_jkstra(ghaph):
    pathMap = {}
    res = {}

    for node in ghaph.nodes.values():
        # 设定初始距离
        pathMap[node] = sys.maxsize
    for node in ghaph.nodes.values():
        pathMap[node] = 0

        while len(pathMap) != 0:
            for edge in node.edges:
                # 如果边的终点在map(还没确定过)里面且新路径可以让路径更短
                if edge.destination in pathMap and edge.lenght + pathMap[node] < pathMap[edge.destination]:
                    pathMap[edge.destination] = edge.lenght + pathMap[node]

            # 记录并弹出当前点, 找到当前map中路径最短的点,再以这个点为基准
            res[node] = pathMap[node]
            pathMap.pop(node)
            themin = sys.maxsize
            for k, v in pathMap.items():
                if v < themin:
                    themin = v
                    node = k
        return res


'''
    下面是前缀树的内容,内容比较少,所以就和图放到一起了
'''


# 什么是前缀树?如何生成前缀树?两个字符列表,
#   arr2中有那些字符是arr1中出现的?
#   arr2中哪些字符是作为arr1中某个字符串前缀出现的?
#   arr2中那些字符是作为arr1中某个字符串前缀出现的?
#   arr2中出现最多的前缀是?
class Trie(object):
    def __init__(self):
        self.root = TrieNode()

    def insert(self, arr):
        cur = self.root
        for chr in arr:
            cur.transfer += 1
            chr = list(chr)
            for i in chr:
                index = ord(i) - ord("a")
                if cur.nexts[index] == None:
                    cur.nexts[index] = TrieNode()
                cur = cur.nexts[index]
                cur.transfer += 1
            cur.end += 1
            cur = self.root

    def search(self, chr):
        # 查询出现了几次
        chr = list(chr)
        cur = self.root
        for i in chr:
            index = ord(i) - ord("a")
            if cur.nexts[index] == None:
                return 0
            cur = cur.nexts[index]
        return cur.end

    def prefixCount(self, pre):
        pre = list(pre)
        cur = self.root
        for i in pre:
            index = ord(i) - ord("a")
            if cur.nexts[index] == None:
                return 0
            cur = cur.nexts[index]
        return cur.transfer

    def delete(self, chr):
        if self.search(chr) == 0:
            return False
        chr = list(chr)
        cur = self.root
        cur.transfer -= 1
        for i in chr:
            index = ord(i) - ord("a")
            if cur.nexts[index].transfer == 1:
                cur.nexts[index] == None
                return
            cur = cur.nexts[index]
            cur.transfer -= 1
        cur.end -= 1


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值