Java實現字典樹TrieTree
阿新 • • 發佈:2019-01-02
為了準備阿里的網上筆試,這幾天回顧了資料結構.看到字典樹時,突然發現四六級的高頻詞可以用字典樹找出來的.(應該不會是一個一個數出來的吧....)
構造字典樹的過程如下:
1.首先確定樹節點需要用怎麼樣的資料結構,我是這樣寫的:
2.通過節點的parent,children屬性,來連線各個節點.每一個單詞開始,都先將root節點作為雙親節點,單詞中的每個字元來構造子節點,如果子節點存在,則直接使用,否則新建.public class TrieTreeNode { /** * 節點深度 */ public short depth; /** * 存放當前節點的所有子節點 */ public Map<Integer, TrieTreeNode> children = new HashMap<Integer, TrieTreeNode>(); /** * 是否為單詞的結尾 */ public boolean isTail = false; /** * 雙親節點 */ public TrieTreeNode parent; /** * 可以是a-z中的任意字母 */ public char value; /** * 當單詞相同時,wordCount++,用於計算相同的單詞個數 */ public int wordCount = 0; /** * 存放一整個單詞 */ public StringBuilder word = new StringBuilder(); public TrieTreeNode() { // TODO Auto-generated constructor stub } }
/** * 通過給定的字串建立字典樹,暫時只支援英文 * @param data 用於建立字典樹的字串,暫不支援中文. */ public void createTrieTree(String data) { //分解出文字中的單詞 String[] strArr = data.split(regex); //建立字典樹的根節點 //將split後的str陣列讀入字典樹 for (String s : strArr) { //單個的字母不考慮 if (s.length() > 1) { //每一個單詞開始,都以root作為第一個雙親節點 TrieTreeNode parent = root; //開始迴圈每一個單詞 for (short i = 0; i < s.length(); i++) { char c = s.charAt(i); //這裡不考慮大寫,只管小寫. c = (char) (c < 'a' ? c + 32 : c); //建立子節點 TrieTreeNode child = createChildTrieTree(c, i, parent); int key = child.value * 100 + child.depth; parent.children.put(key, child); parent = child; } //每一個單詞迴圈完畢後,都將最後一個節點作為尾部. parent.isTail = true; //每次遇到相同的單詞都將wordCount++ int wordCount = ++parent.wordCount; String word = parent.word.toString(); //將單詞和單詞出現次數放入map wordCountMap.put(word, new WordAndCount(word, wordCount)); } } } /** * 用於建立子節點 * @param c * @param depth * @param parent * @return */ private TrieTreeNode createChildTrieTree(char c, short depth, TrieTreeNode parent) { //通過雙親節點,數的深度和字元c,3個條件判斷是否已經存在該子節點,如果存在,直接獲取. TrieTreeNode child = getChild(c, depth, parent); //如果不存在,就新建一個,並將引數賦給它. if (child == null) { child = new TrieTreeNode(); child.depth = depth; child.parent = parent; child.value = c; //用於儲存單詞,通過isTail的配合,可以省去搜索單詞的過程. child.word.append(parent.word).append(c); } return child; } /** * 根據字元c,樹深度和雙親節點,獲取子節點. * @param c * @param depth * @param parent * @return */ private TrieTreeNode getChild(char c, short depth, TrieTreeNode parent) { // TODO Auto-generated method stub //每一個雙親節點,都有一個HashMap來存放所有的子節點,key是根據c和depth來合成的. Map<Integer, TrieTreeNode> children = parent.children; //因為字典樹的深度不超過26,所以十位和個位讓depth來存放,百位以上讓字元c來存放,這就可以構成一個獨一無二的key來對應一個value int key = c * 100 + depth; return children.get(key); }
3.通過如上步驟,字典樹已經構造完成了.接下來是讀值了,通過maker.getWordCountMap(),即可獲得所有單詞和它的出現次數.如果想排序顯示,可以呼叫TrieTreeMaker中的sort方法進行排序.如果還有更特別的需求,可以通過get方法獲取root節點,對它進行遞迴遍歷.
最後,附上測試程式碼和資料:
File Size:15.6962890625M@Test public void test() { String filePath = "C:\\logs\\file.log"; File srcFile = new File(filePath); System.out.println("File Size:" + srcFile.length() / 1024 / 1024.0 + "M"); long end2, end, start = System.nanoTime(); TrieTreeMaker maker = new TrieTreeMaker(); maker.setCapacity(1024 * 1024 * 20); //通過給定的檔案來構造字典樹,也可通過呼叫createTrieTree("xxxx")實現. maker.obtainDataAndCreateTrieTree(srcFile); end = System.nanoTime(); System.out.println("After Creation is accomplished:" + BigDecimal.valueOf(end - start, 9)); //true代表升序,false代表降序 List<Entry<String, WordAndCount>> list = maker.sort( maker.getWordCountMap(), true); end2 = System.nanoTime(); System.out.println("After Sort is accomplished:" + BigDecimal.valueOf(end2 - end, 9)); //結果輸出 for (Entry<String, WordAndCount> entry : list) { System.out.println(entry.getValue().word + ":" + entry.getValue().count); } }
After Creation is accomplished:2.812941204s
After Sort is accomplished:0.004912021s
org:113792
java:74116
at:71064
springframework:68740
security:62020
web:53172
apache:37100
struts:33516
....由於結果太多,省略.