成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

javascript 前綴Trie

xiaochao / 1278人閱讀

摘要:很容易看出,前綴最壞情況下的查找也快過二叉搜索樹。這種壓縮后的被喚作前綴壓縮,或直接叫前綴樹,字典樹。最長(zhǎng)公共前綴和的最長(zhǎng)公共前綴是,遍歷字典樹到字母時(shí),此時(shí)這些單詞的公共前綴是。

引子

前綴Trie, 又叫字符Tire, trie來自單詞retrieval, 一開始念作tree,后來改念try, 畢竟它與樹是不一樣的東西。網(wǎng)上許多文章都搞混了trie與樹。 trie是通過”邊“來儲(chǔ)存字符的一種樹狀結(jié)構(gòu),所謂邊就是節(jié)點(diǎn)與節(jié)點(diǎn)間的連接。trie每條邊只能存放一個(gè)字符。

它與hash樹很相似,或者說它是哈希樹的變種,哈希樹是用邊來存放一個(gè)整數(shù)(可以是一位數(shù)或兩位數(shù))。前樹Tire與哈希樹都是多叉樹,換言之,父節(jié)點(diǎn)有N個(gè)子節(jié)點(diǎn)。

前綴Tire主要用于字符串的快速檢索,查詢效率比哈希表高。

前綴Trie的核心思想是空間換時(shí)間。利用字符串的公共前綴來降低查詢時(shí)間的開銷以達(dá)到提高效率的目的。

前綴Trie樹也有它的缺點(diǎn), 假定我們只對(duì)字母與數(shù)字進(jìn)行處理,那么每個(gè)節(jié)點(diǎn)至少有52+10個(gè)子節(jié)點(diǎn)。為了節(jié)省內(nèi)存,我們可以用鏈表或數(shù)組。在JS中我們直接用數(shù)組,因?yàn)镴S的數(shù)組是動(dòng)態(tài)的,自帶優(yōu)化。

基本性質(zhì)

根節(jié)點(diǎn)不包含字符,除根節(jié)點(diǎn)外的每一個(gè)子節(jié)點(diǎn)都包含一個(gè)字符

從根節(jié)點(diǎn)到某一節(jié)點(diǎn)。路徑上經(jīng)過的字符連接起來,就是該節(jié)點(diǎn)對(duì)應(yīng)的字符串

每個(gè)節(jié)點(diǎn)的所有子節(jié)點(diǎn)包含的字符都不相同

程序?qū)崿F(xiàn)
// by 司徒正美
class Trie {
  constructor() {
    this.root = new TrieNode();
  }
  isValid(str) {
    return /^[a-z1-9]+$/i.test(str);
  }
  insert(word) {
    // addWord
    if (this.isValid(word)) {
      var cur = this.root;
      for (var i = 0; i < word.length; i++) {
        var c = word.charCodeAt(i);
        c -= 48; //減少”0“的charCode
        var node = cur.edges[c];
        if (node == null) {
          var node = (cur.edges[c] = new TrieNode());
          node.value = word.charAt(i);
          node.numPass = 1; //有N個(gè)字符串經(jīng)過它
        } else {
          node.numPass++;
        }
        cur = node;
      }
      cur.isEnd = true; //檣記有字符串到此節(jié)點(diǎn)已經(jīng)結(jié)束
      cur.numEnd++; //這個(gè)字符串重復(fù)次數(shù)

      return true;
    } else {
      return false;
    }
  }
  remove(word){
      if (this.isValid(word)) {
          var cur = this.root;
          var array = [], n = word.length
          for (var i = 0; i < n; i++) {
              var c = word.charCodeAt(i);
              c = this.getIndex(c)
              var node = cur.edges[c];
              if(node){
                  array.push(node)
                  cur = node
              }else{
                  return false
              }
  
          }
          if(array.length === n){
              array.forEach(function(){
                  el.numPass--
              })
              cur.numEnd --
              if( cur.numEnd == 0){
                  cur.isEnd = false
              } 
          }
      }else{
          return false
      }
  }
  preTraversal(cb){//先序遍歷
        function preTraversalImpl(root, str, cb){  
            cb(root, str);
            for(let i = 0,n = root.edges.length; i < n; i ++){
                let node = root.edges[i];
                if(node){
                    preTraversalImpl(node, str + node.value, cb);
                }
            }
        }  
        preTraversalImpl(this.root, "", cb);
   }
  // 在字典樹中查找是否存在某字符串為前綴開頭的字符串(包括前綴字符串本身)
  isContainPrefix(word) {
    if (this.isValid(word)) {
      var cur = this.root;
      for (var i = 0; i < word.length; i++) {
        var c = word.charCodeAt(i);
        c -= 48; //減少”0“的charCode
        if (cur.edges[c]) {
          cur = cur.edges[c];
        } else {
          return false;
        }
      }
      return true;
    } else {
      return false;
    }
  }
  isContainWord(str) {
    // 在字典樹中查找是否存在某字符串(不為前綴)
    if (this.isValid(word)) {
      var cur = this.root;
      for (var i = 0; i < word.length; i++) {
        var c = word.charCodeAt(i);
        c -= 48; //減少”0“的charCode
        if (cur.edges[c]) {
          cur = cur.edges[c];
        } else {
          return false;
        }
      }
      return cur.isEnd;
    } else {
      return false;
    }
  }
  countPrefix(word) {
    // 統(tǒng)計(jì)以指定字符串為前綴的字符串?dāng)?shù)量
    if (this.isValid(word)) {
      var cur = this.root;
      for (var i = 0; i < word.length; i++) {
        var c = word.charCodeAt(i);
        c -= 48; //減少”0“的charCode
        if (cur.edges[c]) {
          cur = cur.edges[c];
        } else {
          return 0;
        }
      }
      return cur.numPass;
    } else {
      return 0;
    }
  }
  countWord(word) {
    // 統(tǒng)計(jì)某字符串出現(xiàn)的次數(shù)方法
    if (this.isValid(word)) {
      var cur = this.root;
      for (var i = 0; i < word.length; i++) {
        var c = word.charCodeAt(i);
        c -= 48; //減少”0“的charCode
        if (cur.edges[c]) {
          cur = cur.edges[c];
        } else {
          return 0;
        }
      }
      return cur.numEnd;
    } else {
      return 0;
    }
  }
}

class TrieNode {
  constructor() {
    this.numPass = 0;//有多少個(gè)單詞經(jīng)過這節(jié)點(diǎn)
    this.numEnd = 0; //有多少個(gè)單詞就此結(jié)束
    this.edges = [];
    this.value = ""; //value為單個(gè)字符
    this.isEnd = false;
  }
}

我們重點(diǎn)看一下TrieNode與Trie的insert方法。 由于字典樹是主要用在詞頻統(tǒng)計(jì),因此它的節(jié)點(diǎn)屬性比較多, 包含了numPass, numEnd但非常重要的屬性。

insert方法是用于插入重詞,在開始之前,我們必須判定單詞是否合法,不能出現(xiàn) 特殊字符與空白。在插入時(shí)是打散了一個(gè)個(gè)字符放入每個(gè)節(jié)點(diǎn)中。每經(jīng)過一個(gè)節(jié)點(diǎn)都要修改numPass。

優(yōu)化

現(xiàn)在我們每個(gè)方法中,都有一個(gè)c=-48的操作,其實(shí)數(shù)字與大寫字母與小寫字母間其實(shí)還有其他字符的,這樣會(huì)造成無謂的空間的浪費(fèi)

// by 司徒正美
getIndex(c){
      if(c < 58){//48-57
          return c - 48
      }else if(c < 91){//65-90
          return c - 65 + 11
      }else {//> 97 
          return c - 97 + 26+ 11
      }
  }

然后相關(guān)方法將c-= 48改成c = this.getIndex(c)即可

測(cè)試
var trie = new Trie();  
    trie.insert("I");  
    trie.insert("Love");  
    trie.insert("China");  
    trie.insert("China");  
    trie.insert("China");  
    trie.insert("China");  
    trie.insert("China");  
    trie.insert("xiaoliang");  
    trie.insert("xiaoliang");  
    trie.insert("man");  
    trie.insert("handsome");  
    trie.insert("love");  
    trie.insert("Chinaha");  
    trie.insert("her");  
    trie.insert("know");  
    var map = {}
    trie.preTraversal(function(node, str){
       if(node.isEnd){
          map[str] = node.numEnd
       }
    })
    for(var i in map){
        console.log(i+" 出現(xiàn)了"+ map[i]+" 次")
    }
    console.log("包含Chin(包括本身)前綴的單詞及出現(xiàn)次數(shù):");  
    //console.log("China")
    var map = {}
    trie.preTraversal(function(node, str){
        if(str.indexOf("Chin") === 0 && node.isEnd){
           map[str] = node.numEnd
        }
     })
    for(var i in map){
        console.log(i+" 出現(xiàn)了"+ map[i]+" 次")
    }

前綴Trie和其它數(shù)據(jù)結(jié)構(gòu)的比較 前綴Trie與二叉搜索樹

二叉搜索樹應(yīng)該是我們最早接觸的樹結(jié)構(gòu)了,我們知道,數(shù)據(jù)規(guī)模為n時(shí),二叉搜索樹插入、查找、刪除操作的時(shí)間復(fù)雜度通常只有O(log n),最壞情況下整棵樹所有的節(jié)點(diǎn)都只有一個(gè)子節(jié)點(diǎn),退變成一個(gè)線性表,此時(shí)插入、查找、刪除操作的時(shí)間復(fù)雜度是O(n)。

通常情況下,前綴Trie的高度n要遠(yuǎn)大于搜索字符串的長(zhǎng)度m,故查找操作的時(shí)間復(fù)雜度通常為O(m),最壞情況下的時(shí)間復(fù)雜度才為O(n)。很容易看出,前綴Trie最壞情況下的查找也快過二叉搜索樹。

文中前綴Trie都是拿字符串舉例的,其實(shí)它本身對(duì)key的適宜性是有嚴(yán)格要求的,如果key是浮點(diǎn)數(shù)的話,就可能導(dǎo)致整個(gè)前綴Trie巨長(zhǎng)無比,節(jié)點(diǎn)可讀性也非常差,這種情況下是不適宜用前綴Trie來保存數(shù)據(jù)的;而二叉搜索樹就不存在這個(gè)問題。

前綴Trie與Hash表

? ?考慮一下Hash沖突的問題。Hash表通常我們說它的復(fù)雜度是O(1),其實(shí)嚴(yán)格說起來這是接近完美的Hash表的復(fù)雜度,另外還需要考慮到hash函數(shù)本身需要遍歷搜索字符串,復(fù)雜度是O(m)。在不同鍵被映射到“同一個(gè)位置”(考慮closed hashing,這“同一個(gè)位置”可以由一個(gè)普通鏈表來取代)的時(shí)候,需要進(jìn)行查找的復(fù)雜度取決于這“同一個(gè)位置”下節(jié)點(diǎn)的數(shù)目,因此,在最壞情況下,Hash表也是可以成為一張單向鏈表的。

? ?前綴Trie可以比較方便地按照key的字母序來排序(整棵樹先序遍歷一次就好了),這跟絕大多數(shù)Hash表是不同的(Hash表一般對(duì)于不同的key來說是無序的)。

? ?在較理想的情況下,Hash表可以以O(shè)(1)的速度迅速命中目標(biāo),如果這張表非常大,需要放到磁盤上的話,Hash表的查找訪問在理想情況下只需要一次即可;但是Trie樹訪問磁盤的數(shù)目需要等于節(jié)點(diǎn)深度。

? ?很多時(shí)候前綴Trie比Hash表需要更多的空間,我們考慮這種一個(gè)節(jié)點(diǎn)存放一個(gè)字符的情況的話,在保存一個(gè)字符串的時(shí)候,沒有辦法把它保存成一個(gè)多帶帶的塊。前綴Trie的節(jié)點(diǎn)壓縮可以明顯緩解這個(gè)問題,后面會(huì)講到。

前綴Trie樹的改進(jìn) 按位Trie樹(Bitwise Trie)

原理上和普通Trie樹差不多,只不過普通Trie樹存儲(chǔ)的最小單位是字符,但是Bitwise Trie存放的是位而已。位數(shù)據(jù)的存取由CPU指令一次直接實(shí)現(xiàn),對(duì)于二進(jìn)制數(shù)據(jù),它理論上要比普通Trie樹快。

節(jié)點(diǎn)壓縮

分支壓縮:將一些連結(jié)線與節(jié)點(diǎn)進(jìn)行合并,比如i-n-n可以合并成inn。這種壓縮后的Tire被喚作前綴壓縮Tire,或直接叫前綴樹, 字典樹。

節(jié)點(diǎn)映射表:這種方式也是在前綴Trie的節(jié)點(diǎn)可能已經(jīng)幾乎完全確定的情況下采用的,針對(duì)前綴Trie中節(jié)點(diǎn)的每一個(gè)狀態(tài),如果狀態(tài)總數(shù)重復(fù)很多的話,通過一個(gè)元素為數(shù)字的多維數(shù)組(比如Triple Array Trie)來表示,這樣存儲(chǔ)Trie樹本身的空間開銷會(huì)小一些,雖說引入了一張額外的映射表。

前綴Trie的應(yīng)用

前綴樹還是很好理解,它的應(yīng)用也是非常廣的。

(1)字符串的快速檢索

字典樹的查詢時(shí)間復(fù)雜度是O(logL),L是字符串的長(zhǎng)度。所以效率還是比較高的。字典樹的效率比hash表高。

(2)字符串排序

從上圖我們很容易看出單詞是排序的,先遍歷字母序在前面。減少了沒必要的公共子串。

(3)最長(zhǎng)公共前綴

inn和int的最長(zhǎng)公共前綴是in,遍歷字典樹到字母n時(shí),此時(shí)這些單詞的公共前綴是in。

(4)自動(dòng)匹配前綴顯示后綴

我們使用辭典或者是搜索引擎的時(shí)候,輸入appl,后面會(huì)自動(dòng)顯示一堆前綴是appl的東東吧。那么有可能是通過前綴Trie實(shí)現(xiàn)的,前面也說了前綴Trie可以找到公共前綴,我們只需要把剩余的后綴遍歷顯示出來即可。

參考鏈接

http://blog.csdn.net/abcd_d_/...

http://blog.csdn.net/u0139490...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/92837.html

相關(guān)文章

  • 208-實(shí)現(xiàn) Trie (前綴樹)

    摘要:前言前綴樹是一種很常用的數(shù)據(jù)結(jié)構(gòu),例如我們常用的數(shù)據(jù)庫索引。而關(guān)于前綴樹的介紹,由于中國(guó)有關(guān)于前綴樹的教程,我就不班門弄斧了,我的答案也是參考教程的思路去解答,希望可以給大家一個(gè)參考。下面是原題目實(shí)現(xiàn)一個(gè)前綴樹,包含和這三個(gè)操作。 前言 前綴樹是一種很常用的數(shù)據(jù)結(jié)構(gòu),例如我們常用的數(shù)據(jù)庫索引。而關(guān)于前綴樹的介紹,由于LeetCode中國(guó)有關(guān)于前綴樹的教程,我就不班門弄斧了,我的答案也是...

    antyiwei 評(píng)論0 收藏0
  • [Leetcode] Implement Trie 實(shí)現(xiàn)前綴

    摘要:壓縮前綴樹其實(shí)就是將所有只有一個(gè)子節(jié)點(diǎn)的節(jié)點(diǎn)合并成一個(gè),以減少?zèng)]有意義的類似鏈表式的鏈接。然后我們開始遍歷這個(gè)前綴樹。 Implement Trie Implement a trie with insert, search, and startsWith methods. Note: You may assume that all inputs are consist of lowe...

    jsliang 評(píng)論0 收藏0
  • 以太坊數(shù)據(jù)結(jié)構(gòu)MPT

    摘要:是以太坊存儲(chǔ)數(shù)據(jù)的核心數(shù)據(jù)結(jié)構(gòu),它是由和結(jié)合的一種樹形結(jié)構(gòu),理解有助于我們更好的理解以太坊的數(shù)據(jù)存儲(chǔ)。所以就有了樹壓縮前綴樹,后面會(huì)介紹到,也被稱為,中文名稱默克爾樹,主要用于數(shù)據(jù)集較大時(shí)的文件校驗(yàn)。 ??MPT(Merkle Patricia Tries)是以太坊存儲(chǔ)數(shù)據(jù)的核心數(shù)據(jù)結(jié)構(gòu),它是由Merkle Tree和Patricia Tree結(jié)合的一種樹形結(jié)構(gòu),理解MPT有助于我們更...

    Honwhy 評(píng)論0 收藏0
  • 深入探究immutable.js的實(shí)現(xiàn)機(jī)制(一)

    摘要:本文會(huì)集合多方資料以及我自己的一些理解,深入一些探究實(shí)現(xiàn)機(jī)制。位分區(qū)實(shí)際上是數(shù)字分區(qū)的一個(gè)子集,所有以的整數(shù)次冪,,,,為基數(shù)的數(shù)字分區(qū)前綴樹,都可以轉(zhuǎn)為位分區(qū)。其實(shí)舉個(gè)例子最好理解比如數(shù)字的二進(jìn)制形式是,這是一個(gè)位的二進(jìn)制數(shù)。 Immutable.js 采用了持久化數(shù)據(jù)結(jié)構(gòu)和結(jié)構(gòu)共享,保證每一個(gè)對(duì)象都是不可變的,任何添加、修改、刪除等操作都會(huì)生成一個(gè)新的對(duì)象,且通過結(jié)構(gòu)共享等方式大幅...

    zhangfaliang 評(píng)論0 收藏0
  • [LintCode/LeetCode] Implement Trie

    摘要:首先,我們應(yīng)該了解字典樹的性質(zhì)和結(jié)構(gòu),就會(huì)很容易實(shí)現(xiàn)要求的三個(gè)相似的功能插入,查找,前綴查找。既然叫做字典樹,它一定具有順序存放個(gè)字母的性質(zhì)。所以,在字典樹的里面,添加,和三個(gè)參數(shù)。 Problem Implement a trie with insert, search, and startsWith methods. Notice You may assume that all i...

    付永剛 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<