Python Trie树实现字典排序


Posted in Python onMarch 28, 2014

一般语言都提供了按字典排序的API,比如跟微信公众平台对接时就需要用到字典排序。按字典排序有很多种算法,最容易想到的就是字符串搜索的方式,但这种方式实现起来很麻烦,性能也不太好。Trie树是一种很常用的树结构,它被广泛用于各个方面,比如字符串检索、中文分词、求字符串最长公共前缀和字典排序等等,而且在输入法中也能看到Trie树的身影。

什么是Trie树

Trie树通常又称为字典树、单词查找树或前缀树,是一种用于快速检索的多叉树结构。如图数字的字典是一个10叉树:

Python Trie树实现字典排序

同理小写英文字母或大写英文字母的字典数是一个26叉树。如上图可知,Trie树的根结点是不保存数据的,所有的数据都保存在它的孩子节点中。有字符串go, golang, php, python, perl,它这棵Trie树可如下图所示构造:

Python 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就是起到单词边界作用。具体是存储在单词的最后一个节点上,如图所示:

Python Trie树实现字典排序

而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 相关文章推荐
Python实现生成随机日期字符串的方法示例
Dec 25 Python
jupyter notebook引用from pyecharts.charts import Bar运行报错
Apr 23 Python
windows下python和pip安装教程
May 25 Python
使用python根据端口号关闭进程的方法
Nov 06 Python
python利用百度AI实现文字识别功能
Nov 27 Python
利用python实现短信和电话提醒功能的例子
Aug 08 Python
python写入数据到csv或xlsx文件的3种方法
Aug 23 Python
关于python导入模块import与常见的模块详解
Aug 28 Python
Pytorch中Tensor与各种图像格式的相互转化详解
Dec 26 Python
安装完Python包然后找不到模块的解决步骤
Feb 13 Python
ubuntu16.04升级Python3.5到Python3.7的方法步骤
Aug 20 Python
Python绘画好看的星空图
Mar 17 Python
python实现探测socket和web服务示例
Mar 28 #Python
python实现目录树生成示例
Mar 28 #Python
python改变日志(logging)存放位置的示例
Mar 27 #Python
使用python删除nginx缓存文件示例(python文件操作)
Mar 26 #Python
python实现ip查询示例
Mar 26 #Python
python fabric实现远程操作和部署示例
Mar 25 #Python
python基础教程之数字处理(math)模块详解
Mar 25 #Python
You might like
颠覆常识!无色透明的咖啡诞生了(中日双语)
2021/03/03 咖啡文化
谈谈PHP语法(2)
2006/10/09 PHP
PHP入门速成教程
2007/03/19 PHP
PHP PDOStatement::setFetchMode讲解
2019/02/03 PHP
PHP重载基础知识回顾
2020/09/10 PHP
javascript Onunload与Onbeforeunload使用小结
2009/12/31 Javascript
javascript 获取页面的高度及滚动条的位置的代码
2010/05/06 Javascript
js/html光标定位的实现代码
2013/09/23 Javascript
JS获取各种浏览器窗口大小的方法
2014/01/14 Javascript
深入理解JavaScript中的对象
2015/06/04 Javascript
JavaScript实现带播放列表的音乐播放器实例分享
2016/03/07 Javascript
JavaScript实现页面无操作倒计时退出
2016/10/22 Javascript
JS实现超简单的汉字转拼音功能示例
2016/12/22 Javascript
Angular.js指令学习中一些重要属性的用法教程
2017/05/24 Javascript
webpack之devtool详解
2018/02/10 Javascript
vue.js根据代码运行环境选择baseurl的方法
2018/02/28 Javascript
JS判断浏览器类型与操作系统的方法分析
2020/04/30 Javascript
Vue axios获取token临时令牌封装案例
2020/09/11 Javascript
[51:22]Fnatic vs IG 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/18 DOTA
[01:10:03]OG vs EG 2018国际邀请赛淘汰赛BO3 第三场 8.23
2018/08/24 DOTA
Python 遍历列表里面序号和值的方法(三种)
2017/02/17 Python
Python 编码Basic Auth使用方法简单实例
2017/05/25 Python
让Python更加充分的使用Sqlite3
2017/12/11 Python
Python实现的朴素贝叶斯分类器示例
2018/01/06 Python
Django后端接收嵌套Json数据及解析详解
2019/07/17 Python
Numpy中np.random.rand()和np.random.randn() 用法和区别详解
2020/10/23 Python
python 日志模块logging的使用场景及示例
2021/01/04 Python
幼师专业毕业生自荐信
2013/09/29 职场文书
护理专业大学生自我推荐信
2014/01/25 职场文书
食品安全承诺书
2014/05/22 职场文书
机械电子工程专业自荐书
2014/06/10 职场文书
领导班子四风对照检查材料思想汇报
2014/09/26 职场文书
《浅水洼里的小鱼》教学反思
2016/02/16 职场文书
python保存大型 .mat 数据文件报错超出 IO 限制的操作
2021/05/10 Python
Python合并pdf文件的工具
2021/07/01 Python
JavaScript 中for/of,for/in 的详细介绍
2021/11/17 Javascript