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基础教程之自定义函数介绍
Aug 29 Python
Python中的闭包详细介绍和实例
Nov 21 Python
python中的变量如何开辟内存
Jun 26 Python
使用Python的toolz库开始函数式编程的方法
Nov 15 Python
python获取url的返回信息方法
Dec 17 Python
python对常见数据类型的遍历解析
Aug 27 Python
PYTHON EVAL的用法及注意事项解析
Sep 06 Python
python pygame实现挡板弹球游戏
Nov 25 Python
Python中的X[:,0]、X[:,1]、X[:,:,0]、X[:,:,1]、X[:,m:n]和X[:,:,m:n]
Feb 13 Python
Python内置方法和属性应用:反射和单例(推荐)
Jun 19 Python
使用tensorflow进行音乐类型的分类
Aug 14 Python
Jupyter notebook 更改文件打开的默认路径操作
May 21 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
php随机生成数字字母组合的方法
2015/03/18 PHP
详解WordPress中创建和添加过滤器的相关PHP函数
2015/12/29 PHP
php如何控制用户对图片的访问 PHP禁止图片盗链
2016/03/25 PHP
thinkPHP中钩子的使用方法实例分析
2017/11/16 PHP
javascript获取dom的下一个节点方法
2014/09/05 Javascript
一个检测表单数据的JavaScript实例
2014/10/31 Javascript
jQuery实现动态表单验证时文本框抖动效果完整实例
2015/08/21 Javascript
jQuery+PHP+MySQL二级联动下拉菜单实例讲解
2015/10/27 Javascript
详解AngularJS实现表单验证
2015/12/10 Javascript
Javascript 链式作用域详细介绍
2017/02/23 Javascript
浅谈jQuery的bind和unbind事件(绑定和解绑事件)
2017/03/02 Javascript
详释JavaScript执行环境与执行栈
2019/04/02 Javascript
实现elementUI表单的全局验证的方法步骤
2019/04/29 Javascript
js实现飞机大战游戏
2020/08/26 Javascript
H5+css3+js搭建带验证码的登录页面
2020/10/11 Javascript
JS hasOwnProperty()方法检测一个属性是否是对象的自有属性的方法
2021/01/29 Javascript
python实现网站的模拟登录
2016/01/04 Python
python批量图片处理简单示例
2019/08/06 Python
Python列表推导式实现代码实例
2020/09/09 Python
Python实现敏感词过滤的4种方法
2020/09/12 Python
HTML5拖放API实现拖放排序的实例代码
2017/05/11 HTML / CSS
英国家庭、花园、汽车和移动解决方案:Easylife Group
2018/05/23 全球购物
Skyscanner新西兰:全球领先的旅游搜索网站
2019/08/26 全球购物
What's the difference between Debug and Trace class? (Debug类与Trace类有什么区别)
2013/09/10 面试题
出国留学介绍信
2014/01/13 职场文书
趣味运动会策划方案
2014/06/02 职场文书
现场活动策划方案
2014/08/22 职场文书
避暑山庄导游词
2015/02/04 职场文书
现场施工员岗位职责
2015/04/11 职场文书
装饰施工员岗位职责
2015/04/11 职场文书
车间安全生产管理制度
2015/08/06 职场文书
Ajax是什么?Ajax高级用法之Axios技术
2021/04/21 Javascript
Vue实现导入Excel功能步骤详解
2021/07/03 Vue.js
php实现自动生成验证码的实例讲解
2021/11/17 PHP
使用Django框架创建项目
2022/06/10 Python
springboot实现string转json json里面带数组
2022/06/16 Java/Android