欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Python Trie樹實現(xiàn)字典排序

 更新時間:2014年03月28日 23:47:20   投稿:mdxy-dxy  
Trie樹是一種很常用的樹結(jié)構(gòu),它被廣泛用于各個方面,比如字符串檢索、中文分詞、求字符串最長公共前綴和字典排序等等,而且在輸入法中也能看到Trie樹的身影

一般語言都提供了按字典排序的API,比如跟微信公眾平臺對接時就需要用到字典排序。按字典排序有很多種算法,最容易想到的就是字符串搜索的方式,但這種方式實現(xiàn)起來很麻煩,性能也不太好。Trie樹是一種很常用的樹結(jié)構(gòu),它被廣泛用于各個方面,比如字符串檢索、中文分詞、求字符串最長公共前綴和字典排序等等,而且在輸入法中也能看到Trie樹的身影。


什么是Trie樹

Trie樹通常又稱為字典樹、單詞查找樹或前綴樹,是一種用于快速檢索的多叉樹結(jié)構(gòu)。如圖數(shù)字的字典是一個10叉樹:
 

同理小寫英文字母或大寫英文字母的字典數(shù)是一個26叉樹。如上圖可知,Trie樹的根結(jié)點是不保存數(shù)據(jù)的,所有的數(shù)據(jù)都保存在它的孩子節(jié)點中。有字符串go, golang, php, python, perl,它這棵Trie樹可如下圖所示構(gòu)造:
 

我們來分析下上面這張圖。除了根節(jié)點外,每個子節(jié)點只存儲一個字符。go和golang共享go前綴,php、perl和python只共用p前綴。為了實現(xiàn)字典排序,每一層節(jié)點上存儲的字符都是按照字典排序的方式存儲(這跟遍歷的方式有關(guān))。我們先來看看對單個字符如何進行字典排序。本文只考慮小寫字母,其它方式類似。'a'在'b'的前面,而'a'的ASCII碼小于'b'的ASCII碼,因此通過它們的ASCII相減就可以得到字典順序。而且python內(nèi)置了字典排序的API,比如:
 

復制代碼 代碼如下:

#!/usr/bin/env python
#coding: utf8

if __name__ == '__main__':
 arr = [c for c in 'python']
 arr.sort()
 print arr

而且也可以使用我之前的一篇文章介紹的bitmap來實現(xiàn):Python: 實現(xiàn)bitmap數(shù)據(jù)結(jié)構(gòu) 。實現(xiàn)代碼如下:

復制代碼 代碼如下:

#!/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]&nbsp;=&nbsp;elem&nbsp;|&nbsp;(1&nbsp;<<&nbsp;byteIndex)

 def&nbsp;clean(self,&nbsp;i):
  elemIndex&nbsp;=&nbsp;self.calcElemIndex(i)
  byteIndex&nbsp;=&nbsp;self.calcBitIndex(i)
  elem&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;self.array[elemIndex]
  self.array[elemIndex]&nbsp;=&nbsp;elem&nbsp;&&nbsp;(~(1&nbsp;<<&nbsp;byteIndex))

 def&nbsp;test(self,&nbsp;i):
  elemIndex&nbsp;=&nbsp;self.calcElemIndex(i)
  byteIndex&nbsp;=&nbsp;self.calcBitIndex(i)
  if&nbsp;self.array[elemIndex]&nbsp;&&nbsp;(1&nbsp;<<&nbsp;byteIndex):
   return&nbsp;True
  return&nbsp;False

if&nbsp;__name__&nbsp;==&nbsp;'__main__':
 MAX&nbsp;=&nbsp;ord('z')
 suffle_array&nbsp;=&nbsp;[c&nbsp;for&nbsp;c&nbsp;in&nbsp;'python']
 result&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;[]
 bitmap&nbsp;=&nbsp;Bitmap(MAX)
 for&nbsp;c&nbsp;in&nbsp;suffle_array:
  bitmap.set(ord(c))
 
 for&nbsp;i&nbsp;in&nbsp;range(MAX&nbsp;+&nbsp;1):
  if&nbsp;bitmap.test(i):
   result.append(chr(i))

 print&nbsp;'原始數(shù)組為:&nbsp;&nbsp;&nbsp;&nbsp;%s'&nbsp;%&nbsp;suffle_array
 print&nbsp;'排序后的數(shù)組為:&nbsp;%s'&nbsp;%&nbsp;result

bitmap的排序不能有重復字符。其實剛才所說的基于ASCII碼相減的方式進行字典排序,已經(jīng)有很多成熟算法了,比如排序、希爾排序、冒泡排序和堆排序等等。本文為了圖簡單,將使用Python自帶的sorted方法來進行單字符的字典排序。如果讀者自行實現(xiàn)單字符數(shù)組的排序也可以,而且這樣將可以自定義字符串的排序方式。

實現(xiàn)思路

整個實現(xiàn)包括2個類:Trie類和Node類。Node類表示Trie樹中的節(jié)點,由Trie類組織成一棵Trie樹。我們先來看Node類:

復制代碼 代碼如下:

#!/usr/bin/env&nbsp;python
#coding:&nbsp;utf8

class&nbsp;Node(object):
 def&nbsp;__init__(self,&nbsp;c=None,&nbsp;word=None):
  self.c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;c&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;節(jié)點存儲的單個字符
  self.word&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;word&nbsp;#&nbsp;節(jié)點存儲的詞
  self.childs&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;[]&nbsp;&nbsp;&nbsp;#&nbsp;此節(jié)點的子節(jié)點

Node包含三個成員變量。c為每個節(jié)點上存儲的字符。word表示一個完整的詞,在本文中指的是一個字符串。childs包含這個節(jié)點的所有子節(jié)點。既然在每個節(jié)點中存儲了c,那么存儲word有什么用呢?并且這個word應該存在哪個節(jié)點上呢?還是用剛才的圖舉例子:比如go和golang,它們共用go前綴,如果是字符串搜索倒好辦,因為會提供原始字符串,只要在這棵Trie樹上按照路徑搜索即可。但是對于排序來說,不會提供任何輸入,所以無法知道單詞的邊界在哪里,而Node類中的word就是起到單詞邊界作用。具體是存儲在單詞的最后一個節(jié)點上,如圖所示:

而Node類中的c成員如果這棵樹不用于搜索,則可以不定義它,因為在排序中它不是必須的。

接下來我們看看Trie類的定義:

復制代碼 代碼如下:

#!/usr/bin/env&nbsp;python
#coding:&nbsp;utf8

'''Trie樹實現(xiàn)字符串數(shù)組字典排序'''

class&nbsp;Trie(object):
 def&nbsp;__init__(self):
  self.root&nbsp;&nbsp;=&nbsp;Node()&nbsp;#&nbsp;Trie樹root節(jié)點引用

 def&nbsp;add(self,&nbsp;word):
  '''添加字符串'''
  node&nbsp;=&nbsp;self.root
  for&nbsp;c&nbsp;in&nbsp;word:
   pos&nbsp;=&nbsp;self.find(node,&nbsp;c)
   if&nbsp;pos&nbsp;<&nbsp;0:
    node.childs.append(Node(c))
    #為了圖簡單,這里直接使用Python內(nèi)置的sorted來排序
    #pos有問題,因為sort之后的pos會變掉,所以需要再次find來獲取真實的pos
    #自定義單字符數(shù)組的排序方式可以實現(xiàn)任意規(guī)則的字符串數(shù)組的排序
    node.childs&nbsp;=&nbsp;sorted(node.childs,&nbsp;key=lambda&nbsp;child:&nbsp;child.c)
    pos&nbsp;=&nbsp;self.find(node,&nbsp;c)
   node&nbsp;=&nbsp;node.childs[pos]
  node.word&nbsp;=&nbsp;word

 def&nbsp;preOrder(self,&nbsp;node):
  '''先序輸出'''
  results&nbsp;=&nbsp;[]
  if&nbsp;node.word:
   results.append(node.word)
  for&nbsp;child&nbsp;in&nbsp;node.childs:
   results.extend(self.preOrder(child))
  return&nbsp;results

 def&nbsp;find(self,&nbsp;node,&nbsp;c):
  '''查找字符的位置'''
  childs&nbsp;=&nbsp;node.childs
  _len&nbsp;&nbsp;&nbsp;=&nbsp;len(childs)
  if&nbsp;_len&nbsp;==&nbsp;0:
   return&nbsp;-1
  for&nbsp;i&nbsp;in&nbsp;range(_len):
   if&nbsp;childs[i].c&nbsp;==&nbsp;c:
    return&nbsp;i
  return&nbsp;-1

 def&nbsp;setWords(self,&nbsp;words):
  for&nbsp;word&nbsp;in&nbsp;words:
   self.add(word)

Trie包含1個成員變量和4個方法。root用于引用根結(jié)點,它不存儲具體的數(shù)據(jù),但是它擁有子節(jié)點。setWords方法用于初始化,調(diào)用add方法來初始化Trie樹,這種調(diào)用是基于每個字符串的。add方法將每個字符添加到子節(jié)點,如果存在則共用它并尋找下一個子節(jié)點,依此類推。find是用于查找是否已經(jīng)建立了存儲某個字符的子節(jié)點,而preOrder是先序獲取存儲的word。樹的遍歷方式有三種:先序遍歷、中序遍歷和后序遍歷,如果各位不太明白,可自行Google去了解。接下我們測試一下:

復制代碼 代碼如下:

#!/usr/bin/env&nbsp;python
#coding:&nbsp;utf8

'''Trie樹實現(xiàn)字符串數(shù)組字典排序'''

class&nbsp;Trie(object):
 def&nbsp;__init__(self):
  self.root&nbsp;&nbsp;=&nbsp;Node()&nbsp;#&nbsp;Trie樹root節(jié)點引用

 def&nbsp;add(self,&nbsp;word):
  '''添加字符串'''
  node&nbsp;=&nbsp;self.root
  for&nbsp;c&nbsp;in&nbsp;word:
   pos&nbsp;=&nbsp;self.find(node,&nbsp;c)
   if&nbsp;pos&nbsp;<&nbsp;0:
    node.childs.append(Node(c))
    #為了圖簡單,這里直接使用Python內(nèi)置的sorted來排序
    #pos有問題,因為sort之后的pos會變掉,所以需要再次find來獲取真實的pos
    #自定義單字符數(shù)組的排序方式可以實現(xiàn)任意規(guī)則的字符串數(shù)組的排序
    node.childs&nbsp;=&nbsp;sorted(node.childs,&nbsp;key=lambda&nbsp;child:&nbsp;child.c)
    pos&nbsp;=&nbsp;self.find(node,&nbsp;c)
   node&nbsp;=&nbsp;node.childs[pos]
  node.word&nbsp;=&nbsp;word

 def&nbsp;preOrder(self,&nbsp;node):
  '''先序輸出'''
  results&nbsp;=&nbsp;[]
  if&nbsp;node.word:
   results.append(node.word)
  for&nbsp;child&nbsp;in&nbsp;node.childs:
   results.extend(self.preOrder(child))
  return&nbsp;results

 def&nbsp;find(self,&nbsp;node,&nbsp;c):
  '''查找字符的位置'''
  childs&nbsp;=&nbsp;node.childs
  _len&nbsp;&nbsp;&nbsp;=&nbsp;len(childs)
  if&nbsp;_len&nbsp;==&nbsp;0:
   return&nbsp;-1
  for&nbsp;i&nbsp;in&nbsp;range(_len):
   if&nbsp;childs[i].c&nbsp;==&nbsp;c:
    return&nbsp;i
  return&nbsp;-1

 def&nbsp;setWords(self,&nbsp;words):
  for&nbsp;word&nbsp;in&nbsp;words:
   self.add(word)

class&nbsp;Node(object):
 def&nbsp;__init__(self,&nbsp;c=None,&nbsp;word=None):
  self.c&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;c&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;節(jié)點存儲的單個字符
  self.word&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;word&nbsp;#&nbsp;節(jié)點存儲的詞
  self.childs&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&nbsp;[]&nbsp;&nbsp;&nbsp;#&nbsp;此節(jié)點的子節(jié)點

if&nbsp;__name__&nbsp;==&nbsp;'__main__':
 words&nbsp;=&nbsp;['python',&nbsp;'function',&nbsp;'php',&nbsp;'food',&nbsp;'kiss',&nbsp;'perl',&nbsp;'goal',&nbsp;'go',&nbsp;'golang',&nbsp;'easy']
 trie&nbsp;=&nbsp;Trie()
 trie.setWords(words)
 result&nbsp;=&nbsp;trie.preOrder(trie.root)
 print&nbsp;'原始字符串數(shù)組:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;%s'&nbsp;%&nbsp;words
 print&nbsp;'Trie樹排序后:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;%s'&nbsp;%&nbsp;result
 words.sort()
 print&nbsp;'Python的sort排序后:&nbsp;%s'&nbsp;%&nbsp;words

結(jié)束語

樹的種類非常之多。在樹結(jié)構(gòu)的實現(xiàn)中,樹的遍歷是個難點,需要多加練習。上述代碼寫得比較倉促,沒有進行任何優(yōu)化,但在此基礎(chǔ)上可以實現(xiàn)任何方式的字符串排序,以及字符串搜索等。

相關(guān)文章

最新評論