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 调用c语言函数的方法
Sep 29 Python
python爬虫_微信公众号推送信息爬取的实例
Oct 23 Python
Python迭代器与生成器基本用法分析
Jul 26 Python
Python中Proxypool库的安装与配置
Oct 19 Python
pandas进行时间数据的转换和计算时间差并提取年月日
Jul 06 Python
详解Matplotlib绘图之属性设置
Aug 23 Python
Python+Appium实现自动化测试的使用步骤
Mar 24 Python
python数据库编程 ODBC方式实现通讯录
Mar 27 Python
Python实现从N个数中找到最大的K个数
Apr 02 Python
python变量的作用域是什么
May 26 Python
pycharm 实现本地写代码,服务器运行的操作
Jun 08 Python
使用Tensorflow-GPU禁用GPU设置(CPU与GPU速度对比)
Jun 30 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
jQuery EasyUI API 中文文档 - DateBox日期框
2011/10/15 PHP
php实现批量下载百度云盘文件例子分享
2014/04/10 PHP
JavaScript入门教程(11) js事件处理
2009/01/31 Javascript
safari,opera嵌入iframe页面cookie读取问题解决方法
2010/06/23 Javascript
在JavaScript中监听IME键盘输入事件
2011/05/29 Javascript
基于jquery的固定表头和列头的代码
2012/05/03 Javascript
JS实现淘宝幻灯片效果的实现方法
2013/03/22 Javascript
jscript读写二进制文件的方法
2015/04/22 Javascript
浅谈javascript中的DOM方法
2015/07/16 Javascript
jQuery获取某天的农历日期并判断是否除夕或新年的方法
2016/03/01 Javascript
jQuery控制frames及frame页面JS的方法
2016/03/08 Javascript
js实现商品抛物线加入购物车特效
2020/11/18 Javascript
JavaScript浮点数及运算精度调整详解
2016/10/21 Javascript
基于JavaScript实现本地图片预览
2017/02/08 Javascript
Bootstrap输入框组件简单实现代码
2017/03/06 Javascript
React styled-components设置组件属性的方法
2018/08/07 Javascript
vue-swiper的使用教程
2018/08/30 Javascript
[02:47]DOTA2亚洲邀请赛 HR战队出场宣传片
2015/02/07 DOTA
python 不以科学计数法输出的方法
2018/07/16 Python
Python使用sqlalchemy模块连接数据库操作示例
2019/03/13 Python
python实现批量视频分帧、保存视频帧
2019/05/31 Python
linux环境中没有网络怎么下载python
2019/07/07 Python
8种用Python实现线性回归的方法对比详解
2019/07/10 Python
python 根据字典的键值进行排序的方法
2019/07/24 Python
Python中base64与xml取值结合问题
2019/12/22 Python
Python查找不限层级Json数据中某个key或者value的路径方式
2020/02/27 Python
python输出结果刷新及进度条的实现操作
2020/07/13 Python
聊聊python中的异常嵌套
2020/09/01 Python
详解H5本地储存Web Storage
2017/07/03 HTML / CSS
Data URI scheme详解和使用实例及图片base64编码实现方法
2014/05/08 HTML / CSS
浅谈HTML5 FileReader分布读取文件以及其方法简介
2017/11/09 HTML / CSS
Chantelle仙黛尔内衣美国官网:法国第一品牌内衣
2018/07/26 全球购物
生产内勤岗位职责
2013/12/07 职场文书
教师实习自我鉴定
2013/12/11 职场文书
妇女工作先进事迹
2014/08/17 职场文书
委托书英文
2015/01/28 职场文书