# 本章重点讲的是图结构,顺带一点前缀树的内容,这两个结构题目的解答比较偏向于套路解答
# 前缀树节点结构:
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
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)