什么是Trie树
Trie树通常又称为字典树、单词查找树或前缀树,是一种用于快速检索的多叉树结构。如图数字的字典是一个10叉树:
同理小写英文字母或大写英文字母的字典数是一个26叉树。如上图可知,Trie树的根结点是不保存数据的,所有的数据都保存在它的孩子节点中。有字符串go, golang, php, python, perl,它这棵Trie树可如下图所示构造:
我们来分析下上面这张图。除了根节点外,每个子节点只存储一个字符。go和golang共享go前缀,php、perl和python只共用p前缀。为了实现字典排序,每一层节点上存储的字符都是按照字典排序的方式存储(这跟遍历的方式有关)。我们先来看看对单个字符如何进行字典排序。本文只考虑小写字母,其它方式类似。'a'在'b'的前面,而'a'的ASCII码小于'b'的ASCII码,因此通过它们的ASCII相减就可以得到字典顺序。而且python内置了字典排序的API,比如:
复制代码 代码如下:
#!/usr/bin/env python
#coding: utf8
if __name__ == '__main__':
arr = [c for c in 'python']
arr.sort()
print arr
而且也可以使用我之前的一篇文章介绍的bitmap来实现:Python: 实现bitmap数据结构 。实现代码如下:
复制代码 代码如下:
#!/usr/bin/env python
#coding: utf8
class Bitmap(object):
def __init__(self, max):
self.size = self.calcElemIndex(max, True)
self.array = [0 for i in range(self.size)]
def calcElemIndex(self, num, up=False):
'''up为True则为向上取整, 否则为向下取整'''
if up:
return int((num + 31 - 1) / 31) #向上取整
return num / 31
def calcBitIndex(self, num):
return num % 31
def set(self, num):
elemIndex = self.calcElemIndex(num)
byteIndex = self.calcBitIndex(num)
elem = self.array[elemIndex]
self.array[elemIndex] = elem | (1 << byteIndex)
def clean(self, i):
elemIndex = self.calcElemIndex(i)
byteIndex = self.calcBitIndex(i)
elem = self.array[elemIndex]
self.array[elemIndex] = elem & (~(1 << byteIndex))
def test(self, i):
elemIndex = self.calcElemIndex(i)
byteIndex = self.calcBitIndex(i)
if self.array[elemIndex] & (1 << byteIndex):
return True
return False
if __name__ == '__main__':
MAX = ord('z')
suffle_array = [c for c in 'python']
result = []
bitmap = Bitmap(MAX)
for c in suffle_array:
bitmap.set(ord(c))
for i in range(MAX + 1):
if bitmap.test(i):
result.append(chr(i))
print '原始数组为: %s' % suffle_array
print '排序后的数组为: %s' % result
bitmap的排序不能有重复字符。其实刚才所说的基于ASCII码相减的方式进行字典排序,已经有很多成熟算法了,比如插入排序、希尔排序、冒泡排序和堆排序等等。本文为了图简单,将使用Python自带的sorted方法来进行单字符的字典排序。如果读者自行实现单字符数组的排序也可以,而且这样将可以自定义字符串的排序方式。
实现思路
整个实现包括2个类:Trie类和Node类。Node类表示Trie树中的节点,由Trie类组织成一棵Trie树。我们先来看Node类:
复制代码 代码如下:
#!/usr/bin/env python
#coding: utf8
class Node(object):
def __init__(self, c=None, word=None):
self.c = c # 节点存储的单个字符
self.word = word # 节点存储的词
self.childs = [] # 此节点的子节点
Node包含三个成员变量。c为每个节点上存储的字符。word表示一个完整的词,在本文中指的是一个字符串。childs包含这个节点的所有子节点。既然在每个节点中存储了c,那么存储word有什么用呢?并且这个word应该存在哪个节点上呢?还是用刚才的图举例子:比如go和golang,它们共用go前缀,如果是字符串搜索倒好办,因为会提供原始字符串,只要在这棵Trie树上按照路径搜索即可。但是对于排序来说,不会提供任何输入,所以无法知道单词的边界在哪里,而Node类中的word就是起到单词边界作用。具体是存储在单词的最后一个节点上,如图所示:
而Node类中的c成员如果这棵树不用于搜索,则可以不定义它,因为在排序中它不是必须的。
接下来我们看看Trie类的定义:
复制代码 代码如下:
#!/usr/bin/env python
#coding: utf8
'''Trie树实现字符串数组字典排序'''
class Trie(object):
def __init__(self):
self.root = Node() # Trie树root节点引用
def add(self, word):
'''添加字符串'''
node = self.root
for c in word:
pos = self.find(node, c)
if pos < 0:
node.childs.append(Node(c))
#为了图简单,这里直接使用Python内置的sorted来排序
#pos有问题,因为sort之后的pos会变掉,所以需要再次find来获取真实的pos
#自定义单字符数组的排序方式可以实现任意规则的字符串数组的排序
node.childs = sorted(node.childs, key=lambda child: child.c)
pos = self.find(node, c)
node = node.childs[pos]
node.word = word
def preOrder(self, node):
'''先序输出'''
results = []
if node.word:
results.append(node.word)
for child in node.childs:
results.extend(self.preOrder(child))
return results
def find(self, node, c):
'''查找字符插入的位置'''
childs = node.childs
_len = len(childs)
if _len == 0:
return -1
for i in range(_len):
if childs[i].c == c:
return i
return -1
def setWords(self, words):
for word in words:
self.add(word)
Trie包含1个成员变量和4个方法。root用于引用根结点,它不存储具体的数据,但是它拥有子节点。setWords方法用于初始化,调用add方法来初始化Trie树,这种调用是基于每个字符串的。add方法将每个字符添加到子节点,如果存在则共用它并寻找下一个子节点,依此类推。find是用于查找是否已经建立了存储某个字符的子节点,而preOrder是先序获取存储的word。树的遍历方式有三种:先序遍历、中序遍历和后序遍历,如果各位不太明白,可自行Google去了解。接下我们测试一下:
复制代码 代码如下:
#!/usr/bin/env python
#coding: utf8
'''Trie树实现字符串数组字典排序'''
class Trie(object):
def __init__(self):
self.root = Node() # Trie树root节点引用
def add(self, word):
'''添加字符串'''
node = self.root
for c in word:
pos = self.find(node, c)
if pos < 0:
node.childs.append(Node(c))
#为了图简单,这里直接使用Python内置的sorted来排序
#pos有问题,因为sort之后的pos会变掉,所以需要再次find来获取真实的pos
#自定义单字符数组的排序方式可以实现任意规则的字符串数组的排序
node.childs = sorted(node.childs, key=lambda child: child.c)
pos = self.find(node, c)
node = node.childs[pos]
node.word = word
def preOrder(self, node):
'''先序输出'''
results = []
if node.word:
results.append(node.word)
for child in node.childs:
results.extend(self.preOrder(child))
return results
def find(self, node, c):
'''查找字符插入的位置'''
childs = node.childs
_len = len(childs)
if _len == 0:
return -1
for i in range(_len):
if childs[i].c == c:
return i
return -1
def setWords(self, words):
for word in words:
self.add(word)
class Node(object):
def __init__(self, c=None, word=None):
self.c = c # 节点存储的单个字符
self.word = word # 节点存储的词
self.childs = [] # 此节点的子节点
if __name__ == '__main__':
words = ['python', 'function', 'php', 'food', 'kiss', 'perl', 'goal', 'go', 'golang', 'easy']
trie = Trie()
trie.setWords(words)
result = trie.preOrder(trie.root)
print '原始字符串数组: %s' % words
print 'Trie树排序后: %s' % result
words.sort()
print 'Python的sort排序后: %s' % words
结束语
树的种类非常之多。在树结构的实现中,树的遍历是个难点,需要多加练习。上述代码写得比较仓促,没有进行任何优化,但在此基础上可以实现任何方式的字符串排序,以及字符串搜索等。
Python,Trie
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新动态
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]