赞
踩
Trie树,又叫做字典树,顾名思义它是一种树形的数据结构。Trie树可以做到在一个字符串集合中快速匹配字符串,在构建之后也可以用于词频统计等。
有这样一份数据,它包含了一系列Mac OS系统中的文件路径(实际上记录的是一个进程对于某个文件的操作)
/Users/xxxx/Documents/lll.mp4
/Applications/WeChat.app/Contents/Resources/Compose_Video.svg
/Applications/WeChat.app/Contents/Resources/Compose_Collapsed.svg
/private/var/folders/xxxx
很容易想到的是文件的路径都是由‘/’分割,因此可以把一个文件路径看作Trie树上的一个分支,而文件路径的每一部分都是Trie树中的一个节点,这样我们可以对这份文件路径数据进行路径频次统计,也可以通过绘制整棵Trie树的方式直观的看到当前系统中文件的操作都集中在哪块。
虽然场景里并没有用到Trie树的搜索,但是毕竟Trie树的本职还是字符串匹配,这里还是给出完整的Trie树构建和字符串匹配方法。
from collections import defaultdict from graphviz import Digraph class TrieNode: def __init__(self, data, row): self.data = data self.children = defaultdict(TrieNode) self.count = 1 self.ending = False # 节点ending为True则表示当前节点存在数据 self.id = '' # 用于标识graphviz图中节点 def plus(self): """ 用于词频统计 对于已经存在的数据对节点的count++ """ self.count += 1 class Trie: def __init__(self): self.root = TrieNode('/') # 跟节点存储无意义的字符 def insert(self, items): """ items为要插入trie树的数据,可以是列表或者字符串 """ p = self.root for item in items: if item not in p.children: # item首次出现,创建新节点 newNode = TrieNode(item) p.children[item] = newNode else: p.children[item].plus() # 修改当前路径的出现次数 p = p.children[item] # 指针指向下一个节点 p.ending = True def find(self, items): """ 在Trie树中查找一个字符串/路径 """ p = self.root for item in items: if item not in p.children: return False p = p.children[item] if p.ending: return True else: return False # 节点ending为False则表示此处无数据 paths = ['/Users/xxxx/Documents/lll.mp4', '/Applications/WeChat.app/Contents/Resources/Compose_Video.svg', '/Applications/WeChat.app/Contents/Resources/Compose_Collapsed.svg', '/private/var/folders/xxxx'] trie_data = [path[1:].split('/') for path in paths] tree = Trie() for path in trie_data: tree.insert(path) print(tree.find(['Users', 'xxxx', 'Documents', 'lll.mp4'])) # True print(tree.find(['Users', 'xxxx', 'Documents'])) # False 注意这里可能会对/Users/xxxx/Documents是否存在有歧义,这里认为中间的路径算作不存在
这里省略上面的基本Trie树,仅实现绘制部分的函数。首先使用graphviz绘制Trie树,我们需要绘制所有节点,并连接全部父子节点,这里采用层序遍历的方式来遍历整棵树。
def level_order(self): level_order_res = [] # 层序遍历结果,用于后面连接graphviz图中的节点 root = self.root if root is None: return g = Digraph('文件路径trie树', node_attr={'color': 'lightblue2'}) # 初始化一张图 queue = [root] # 根节点进入队列 level = 'A' # 第几层 idx = 0 # 某层中节点的索引,level与idx一起组成trie树节点的id while len(queue) != 0: length = len(queue) list = [] for i in range(length): id = '{}{}'.format(level, idx) # 拼接节点id node = queue.pop(0) node.id = id list.append(node) # 放入当前层的列表中 g.node(name=id, label=node.data) # 绘制一个节点 for key, item in node.children.items(): queue.append(item) # 将node的孩子节点都进入队列 idx += 1 # idx标记向后移动一位 level_order_res.append(list) level = chr(ord(level) + 1) # level标记进入下一层 # 绘制边 for level_nodes in level_order_res: for node in level_nodes: for key, child_node in node.children.items(): g.edge(node.id, child_node.id, str(child_node.count)) g.view() # 省略上面的Trie树的构建tree tree.level_order()
其实一个节点不仅可以展示一个路径,也包含了很多其他的数据,我们可以在TrieNode中记录这些数据,并在绘制Trie树时更加多样化,如将更多信息加入节点的label中,也可以通过节点的颜色,节点的形状等来区分。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。