1. 程式人生 > >Java實現字典樹TrieTree

Java實現字典樹TrieTree

為了準備阿里的網上筆試,這幾天回顧了資料結構.看到字典樹時,突然發現四六級的高頻詞可以用字典樹找出來的.(應該不會是一個一個數出來的吧....)

構造字典樹的過程如下:

1.首先確定樹節點需要用怎麼樣的資料結構,我是這樣寫的:

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
	}

}
2.通過節點的parent,children屬性,來連線各個節點.每一個單詞開始,都先將root節點作為雙親節點,單詞中的每個字元來構造子節點,如果子節點存在,則直接使用,否則新建.
	/**
	 * 通過給定的字串建立字典樹,暫時只支援英文
	 * @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節點,對它進行遞迴遍歷.

最後,附上測試程式碼和資料:

@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);
		}
	}
File Size:15.6962890625M
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
....由於結果太多,省略.