数据结构:字典树的基本使用
概述:
? 說來也奇怪,最近碰到的很多問題都需要用字典樹來解決,索性就來研究一番。在這篇博客中,我會通過一些實例來講解一下字典樹的一些基本使用。例如:創(chuàng)建、添加、查找、按字典序排序、按數(shù)值大小進行排序(對于一些數(shù)值序列的排序)等等。
?
關(guān)于字典的實際應(yīng)用實例,請參見本人的另一篇博客:《算法:兩種對拼音進行智能切分的方法》
?
本文鏈接:http://blog.csdn.net/lemon_tree12138/article/details/49177509 -- 編程小笙
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?--轉(zhuǎn)載請注明出處
?
版權(quán)說明
著作權(quán)歸作者所有。
商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
本文作者:Q-WHai
發(fā)表日期: 2015年10月19日
本文鏈接:https://qwhai.blog.csdn.net/article/details/49177509
來源:CSDN
更多內(nèi)容:分類 >> 算法與數(shù)學(xué)
?
基本使用:
0.要點說明:
? 為了便于以下對字典樹的說明,我這里的節(jié)點Node可能會有一些對于讀者而并不必要的成員。例如,fre, visited, minLength, prefixCount等等。這里讀者可以根據(jù)自己的需求自行增減。
?
String name; // 結(jié)點的字符名稱int fre; // 單詞的詞頻boolean end; // 是否是單詞結(jié)尾boolean root; // 是否是根結(jié)點Node[] children; // 子節(jié)點信息boolean visited; // 是否已經(jīng)遍歷過了int minLength; // 通過該節(jié)點的最小的數(shù)字長度int prefixCount = 0; // 有多少單詞通過這個節(jié)點,即節(jié)點字符出現(xiàn)的次數(shù)Node parent; // 當(dāng)前節(jié)點的父節(jié)點?
?
?
1.創(chuàng)建一棵新的字典樹
? 對于創(chuàng)建一棵空的字典樹,其實相對來說是比較容易的。因為,我們不需要對樹進行一些元素新增或是移除。我們只是對字典樹中的一些必要的成員進行了一些初始化的工作。下面是代碼部分:
?
Node root;int depth;public TrieTree(String name) {root = new Node(name);root.setFre(0);depth = 0;root.setEnd(false);root.setRoot(true);}?
?
?
2.插入一個新的節(jié)點元素
? 對于在字典樹的新增元素的關(guān)鍵地方,應(yīng)該就是我們要在何是停止,即新增完成的條件是什么?
? 我們元素新增完成的條件是,我們在對新增的元素(如:"12345")進行遍歷,直到遍歷到字符串的末尾。
? 在我們對新增的字符串str進行插入字典樹的過程中,比如說已經(jīng)遍歷到位置i,如果str.chartAt(i)在字典樹中已經(jīng)存在,則我們可以直接pass,繼續(xù)遍歷i+1的位置;如果str.chartAt(i)在字典樹中是不存在的,那么我們就必須新增此節(jié)點,再將新增的此節(jié)點掛載到上一個點的后面,然后繼續(xù)遍歷i+1位置上元素str.chartAt(i+1)。然后在插入的字符串最后的位置上設(shè)置此節(jié)點為結(jié)束節(jié)點(即在此位置可以構(gòu)成單詞,或是完整添加的數(shù)字)。具體代碼如下:
?
public void insert(String number) {Node node = root;char[] numberCells = number.toCharArray();for (int i = 0; i < numberCells.length; i++) {int num = Integer.parseInt(String.valueOf(numberCells[i]));if (node.getChildren()[num] != null) {if (numberCells.length < node.getChildren()[num].getMinLength()) {node.getChildren()[num].setMinLength(numberCells.length);}if (i == numberCells.length - 1) {Node endNode = node.getChildren()[num];endNode.setFre(endNode.getFre() + 1);endNode.setEnd(true);}node.getChildren()[num].prefixCountIncrement();} else {Node newNode = new Node(numberCells[i] + "");newNode.setParent(node);if (i == numberCells.length - 1) {newNode.setFre(1);newNode.setEnd(true);newNode.setRoot(false);}newNode.setMinLength(numberCells.length);node.getChildren()[num] = newNode;depth = Math.max(i + 1, depth);}node = node.getChildren()[num];}}?
?
?
3.返回Trie中某一節(jié)點被添加的次數(shù)
? 此功能的應(yīng)用點在于,詞頻統(tǒng)計。我們在每次新增一個元素時都會在原來的基本上,對詞頻進行自增處理。如果新增的詞在之前的字典樹中是不存在的,就設(shè)置初始值為1,如果原本有這個節(jié)點,就在原來的詞頻上+1.在上一步(插入一個新的節(jié)點元素)中可以看到具體操作。那么這里介紹一下查詢詞頻的操作。代碼如下:
?
public int searchFre(String number) {int fre = -1;Node node = root;char[] numberCells = number.toCharArray();for (int i = 0; i < numberCells.length; i++) {int num = Integer.parseInt(String.valueOf(numberCells[i]));if (node.getChildren()[num] != null) {node = node.getChildren()[num];fre = node.getFre();} else {fre = -1;break;}}return fre;}?
?
?
4.計算有多少個單詞以prefix為前綴
? 對于前綴統(tǒng)計的操作,我們也需要在插入的過程中進行統(tǒng)計。這是因為,如果我們在插入的時候不進行統(tǒng)計,那么我們就必須在每次查詢一個前綴的時候,去遍歷前綴結(jié)束節(jié)點以下的所有子節(jié)點。這樣勢必會增加時間上的復(fù)雜度,是一種不理想的方式。不過,因為有時,我們并不會只是要求計算有多少以prefix為前綴的串。所以,可能遍歷是在所難免。還是要看需求吧。以下代碼是查詢過程:
?
public int countPrefix(String prefix) {if (prefix == null || prefix.length() == 0) {return -1;}Node node = root;char[] letters = prefix.toCharArray();for (int i = 0; i < prefix.length(); i++) {if (node.getChildren()[Integer.parseInt(String.valueOf(letters[i]))] == null) {return 0;} else {node = node.getChildren()[Integer.parseInt(String.valueOf(letters[i]))];}}return node.getPrefixCount();}?
?
?
5.獲得trie的深度
? 對于樹深度的問題,對于其實際的應(yīng)用點,我目前還未知曉。只是在寫其他功能的時候想到了,就附帶了吧。
? 這個深度,也是要在新增節(jié)點的時候去實時更新的。這樣可以減小查詢時的時間復(fù)雜度。查詢代碼如下:
?
public int depth() {return depth;}It's too easy, isn't it?
?
6.對字典樹進行字典序排序(即深度優(yōu)先搜索)
? 就以我們的數(shù)字字典樹為例。因為我們在構(gòu)造樹的過程就是一個以字典序為基礎(chǔ)的過程,所以我們的遍歷就可以直接對樹進行順序遍歷就Ok。對于字典樹而言,順序遍歷的過程,其實就是對樹的深度遍歷。如果大家還記得深度遍歷的過程,相信大家可以很容易地寫出此代碼。我的編碼過程如下(使用了遞歸):
?
public void dictOrder(Node node, String prefix) {if (node != null) {if (node.isEnd()) {System.out.println(prefix + node.getName());}for (Node children : node.getChildren()) {if (children == null) {continue;}dictOrder(children, prefix + (node.isRoot() ? "" : node.getName()));}}}?
?
?
7.對數(shù)字字典樹按實際數(shù)值大小排序(即廣度優(yōu)先搜索)
? 在第6步中,我們看到對樹深度優(yōu)先搜索的過程就是對樹進行按字典序排序。那么可能你也會問另一個問題,那么是廣度優(yōu)先搜索又會是怎么樣的結(jié)果呢?廣度優(yōu)先搜索的另一個叫法,我們可以說是對樹的分層遍歷。既然是對樹進行分層,那么就是說"123"要排在"1234"的前面。而在第6步中我們也說到了,字典樹本身就是以字典序為基礎(chǔ)進行新增。也就是"123"必然是在"124"的前面。Ok,基于這樣的分析,我們可以得到一個很容易理解的結(jié)論:對字典樹進行廣度優(yōu)先搜索的過程就是對字典樹進行按數(shù)值大小進行排序。具體的實現(xiàn)代碼如下:
?
/*** 對數(shù)字字典樹按實際數(shù)值大小排序(即分層打印)* TrieTree*/public void sortNumberOrder(Node node, String prefix) {Queue<Node> queuing = new LinkedList<Node>();queuing.offer(node);while (!queuing.isEmpty()) {Node currentNode = queuing.poll();if (currentNode.isEnd()) {System.out.println(getNodePath(currentNode));}Node[] children = currentNode.getChildren();for (Node sonNode : children) {if (sonNode != null) {queuing.offer(sonNode);}}}}/*** 獲得某一節(jié)點的上層節(jié)點,即前綴字符串* @param node* @return*/public String getNodePath(Node node) {StringBuffer path = new StringBuffer();Node currentNode = node;while (currentNode.getParent() != null) {path.append(currentNode.getName());currentNode = currentNode.getParent();}return path.reverse().toString();}?
?
Github
https://github.com/qwhai/simple-tree
總結(jié)
以上是生活随笔為你收集整理的数据结构:字典树的基本使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法:程序设计之并查集
- 下一篇: 大数据算法:对5亿数据进行排序