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实现爬虫统计学校BBS男女比例之数据处理(三)
Dec 31 Python
Python实现Linux的find命令实例分享
Jun 04 Python
Python 网页解析HTMLParse的实例详解
Aug 10 Python
分享一下如何编写高效且优雅的 Python 代码
Sep 07 Python
numpy实现合并多维矩阵、list的扩展方法
May 08 Python
利用Python将数值型特征进行离散化操作的方法
Nov 06 Python
Python在图片中插入大量文字并且自动换行
Jan 02 Python
python3中property使用方法详解
Apr 23 Python
python yield关键词案例测试
Oct 15 Python
Python函数参数定义及传递方式解析
Jun 10 Python
PyCharm 安装与使用配置教程(windows,mac通用)
May 12 Python
Python道路车道线检测的实现
Jun 27 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
业余方法DIY电子管FM收音机
2021/03/02 无线电
php学习之 循环结构实现代码
2011/06/09 PHP
php学习笔记 数组的常用函数
2011/06/13 PHP
thinkPHP实现的省市区三级联动功能示例
2017/05/05 PHP
Locate a File Using a File Open Dialog Box
2007/06/18 Javascript
img标签中onerror用法
2009/08/13 Javascript
jquery模拟按下回车实现代码
2011/09/20 Javascript
window.requestAnimationFrame是什么意思,怎么用
2013/01/13 Javascript
JS删除数组元素的函数介绍
2013/03/27 Javascript
JS仿iGoogle自定义首页模块拖拽特效的方法
2015/02/13 Javascript
Node.js中的流(Stream)介绍
2015/03/30 Javascript
JavaScript 表单处理实现代码
2015/04/13 Javascript
Vue实现百度下拉提示搜索功能
2017/06/21 Javascript
vue-lazyload使用总结(推荐)
2018/11/01 Javascript
mpvue全局引入sass文件的方法步骤
2019/03/06 Javascript
微信小程序如何调用图片接口API并居中显示
2019/06/29 Javascript
vue的滚动条插件实现代码
2019/09/07 Javascript
vue 解决addRoutes多次添加路由重复的操作
2020/08/04 Javascript
[01:55]2014DOTA2国际邀请赛快报:国土生病 紧急去医院治疗
2014/07/10 DOTA
[05:11]TI9战队采访——VIRTUSPRO
2019/08/22 DOTA
Python版微信红包分配算法
2015/05/04 Python
Python3处理HTTP请求的实例
2018/05/10 Python
django 自定义过滤器的实现
2019/02/26 Python
linux下安装python3和对应的pip环境教程详解
2019/07/01 Python
wxpython绘制音频效果
2019/11/18 Python
Python使用os.listdir和os.walk获取文件路径
2020/05/21 Python
python简单实现9宫格图片实例
2020/09/03 Python
Python装饰器如何实现修复过程解析
2020/09/05 Python
教育课题研究自我鉴定范文
2013/12/28 职场文书
公司离职证明范本(汇总)
2014/09/10 职场文书
2014领导班子四风问题对照检查材料思想汇报
2014/09/21 职场文书
政协会议宣传标语
2014/10/09 职场文书
监理中标通知书
2015/04/16 职场文书
大学生逃课检讨书
2015/05/04 职场文书
优秀共产党员主要事迹材料
2015/11/05 职场文书
创业计划书之溜冰场
2019/10/25 职场文书