字典樹---Trie

一、字典樹簡介

        Trie也叫做字典樹、前綴樹、單詞查找樹。

        Trie搜索字符串的效率主要跟字符串的長度有關。

        前綴搜索的時候,用Trie就是典型的空間換時間,差不多一個字符需要一個結點來存儲。



二、實現

1、寫代碼注意點

怎麼維護每個結點的多個兒子?
用HashMap維護兒子結點,每個字符對應一個結點即可。

怎麼判斷Trie中是否包含某個字符串?
每個結點中設置一個標記位即可,當是字符串結尾的時候,設置標記位,否則就是默認的。

怎麼刪除結點?
先判斷要刪除的字符串在不在字典樹中,如果在的話,再判斷要刪除的結點是否有兒子,如果有兒子直接將標記位重置成默認的即可。沒有兒子的話,從字符串的最後個字符開始從對應父結點的HashMap中刪除,如果刪除後父結點有孩子或者標記位不是默認值,跳出循環。



2、代碼

	public class Trie<V> {
	    private int size = 0;
	    Node<V> root;
	
	    private static class Node<V> {
	        Node<V> parent;
	        HashMap<Character, Node<V>> children;
	        boolean isWord;
	        V value;
	
	        public Node(Node<V> parent) {
	            this.parent = parent;
	        }
	
	    }
	
	    public Trie() {
	    }
	
	    public int size() {
	        return this.size;
	    }
	
	    public boolean isEmpty() {
	        return this.size == 0;
	    }
	
	    public void clear() {
	        size = 0;
	        root = null;
	    }
	
	    /**
	     * 判斷Trie中是否包含某個字符串
	     *
	     * @param key
	     * @return
	     */
	    public boolean contains(String key) {
	        return get(key) == null ? false : true;
	    }
	
	    /**
	     * 判斷Trie中是否包含以某個字符串爲前綴的字符串
	     *
	     * @param prefix
	     * @return
	     */
	    public boolean startsWith(String prefix) {
	        Node<V> node = root;
	        for (int i = 0; i < prefix.length(); i++) {
	            Character c = prefix.charAt(i);
	            node = node.children.get(c);
	            if (node == null)
	                return false;
	        }
	        return true;
	    }
	
	    /**
	     * 添加一對元素, 如果key已存在就用新的value覆蓋老的value
	     *
	     * @param str
	     * @param value
	     * @return
	     */
	    public V add(String str, V value) {
	        V old = null;
	        if (root == null)
	            root = new Node<V>(null);
	        Node<V> node = root;
	        for (int i = 0; i < str.length(); i++) {
	            Character c = str.charAt(i);
	            if (node.children == null)
	                node.children = new HashMap<>();
	
	            Node<V> child = node.children.get(c);
	            if (child != null) {    //兒子結點已存在就繼續遍歷孫子結點
	                old = node.children.get(c).value;
	                node = child;
	            } else {     //兒子不存在就new結點
	                Node<V> newNode = new Node<V>(node);
	                node.children.put(c, newNode);
	                node = newNode;
	            }
	        }
	        node.isWord = true;
	        node.value = value;
	        size++;
	        return old;
	    }
	
	    public V remove(String str) {
	        if (!contains(str))
	            throw new IllegalArgumentException("被刪除的字符串不存在!");
	
	        V old = null;
	        Node<V> node = getNode(str);
	        if (node.children != null) {        //被刪除的結點還有兒子結點直接刪除value和重置爲默認標記位
	            node.isWord = false;
	            old = node.value;
	            node.value = null;
	            size--;
	            return old;
	        }
	
	        for (int i = str.length() - 1; i >= 0; i--) {
	            Character c = str.charAt(i);
	            System.out.println(c);
	            if (i == str.length() - 1) {
	                old = node.value;
	                node.value = null;      //清空標記位上的value
	
	            }
	            node = node.parent;
	            node.children.remove(c);
	            if (node.children.size() != 0 || node.isWord)
	                break;
	        }
	        size--;
	        return old;
	    }
	
	    public V get(String key) {
	        Node<V> node = getNode(key);
	        return node == null ? null : node.value;
	    }
	
	    private Node<V> getNode(String key) {
	        Node<V> node = root;
	        for (int i = 0; i < key.length(); i++) {
	            Character c = key.charAt(i);
	            if (node.children == null)
	                return null;
	            node = node.children.get(c);
	            if (node == null)
	                return null;
	        }
	        if (node.isWord)
	            return node;
	        else
	            return null;
	    }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章