1. 程式人生 > >我有500w個單詞,你幫忙設計一個數據結構來進行儲存,存好之後,我有兩個需求。(程式人生程式碼copy)

我有500w個單詞,你幫忙設計一個數據結構來進行儲存,存好之後,我有兩個需求。(程式人生程式碼copy)

 1、來了一個新的單詞,需要判斷是否在這500w個單詞中

2、來了一個單詞字首,給出500w個單詞中有多少個單詞是該字首

package cango.scf.common.util;

import java.util.HashMap;
import java.util.Map;

public class DictionaryTree {
    // 字典樹的節點
    private class Node {
        // 是否是單詞
        private boolean isWord;
        // 單詞計數
        private int count;
        // 字串
        private String str;
        // 子節點
        private Map<String, Node> childs;
        // 父節點
        private Node parent;

        public Node() {
            childs = new HashMap<String, Node>();
        }

        public Node(boolean isWord, int count, String str) {
            this();
            this.isWord = isWord;
            this.count = count;
            this.str = str;
        }

        public void addChild(String key, Node node) {
            childs.put(key, node);
            node.parent = this;
        }

        public void removeChild(String key) {
            childs.remove(key);
        }

        public String toString() {
            return "str : " + str + ", isWord : " + isWord + ", count : " + count;
        }
    }

    // 字典樹根節點
    private Node root;

    DictionaryTree() {
        // 初始化root
        root = new Node();
    }

    // 新增字串
    private void addStr(String word, Node node) {

        // 計數
        node.count++;

        String str = node.str;
        if (null != str) {

            // 尋找公共字首
            String commonPrefix = "";
            for (int i = 0; i < word.length(); i++) {
                if (str.length() > i && word.charAt(i) == str.charAt(i)) {
                    commonPrefix += word.charAt(i);
                } else {
                    break;
                }
            }

            // 找到公共字首
            if (commonPrefix.length() > 0) {
                if (commonPrefix.length() == str.length() && commonPrefix.length() == word.length()) {
                    // 與之前的詞重複
                } else if (commonPrefix.length() == str.length() && commonPrefix.length() < word.length()) {
                    // 剩餘的串
                    String wordLeft = word.substring(commonPrefix.length());
                    // 剩餘的串去子節點中繼續找
                    searchChild(wordLeft, node);
                } else if (commonPrefix.length() < str.length()) {
                    // 節點裂變
                    Node splitNode = new Node(true, node.count, commonPrefix);
                    // 處理裂變節點的父關係
                    splitNode.parent = node.parent;
                    splitNode.parent.addChild(commonPrefix, splitNode);
                    node.parent.removeChild(node.str);
                    node.count--;
                    // 節點裂變後的剩餘字串
                    String strLeft = str.substring(commonPrefix.length());
                    node.str = strLeft;
                    splitNode.addChild(strLeft, node);
                    // 單詞裂變後的剩餘字串
                    if (commonPrefix.length() < word.length()) {
                        splitNode.isWord = false;
                        String wordLeft = word.substring(commonPrefix.length());
                        Node leftNode = new Node(true, 1, wordLeft);
                        splitNode.addChild(wordLeft, leftNode);
                    }
                }
            } else {
                // 沒有共同字首,直接新增節點
                Node newNode = new Node(true, 1, word);
                node.addChild(word, newNode);
            }
        } else {
            // 根結點
            if (node.childs.size() > 0) {
                searchChild(word, node);
            } else {
                Node newNode = new Node(true, 1, word);
                node.addChild(word, newNode);
            }
        }
    }

    // 在子節點中新增字串
    public void searchChild(String wordLeft, Node node) {
        boolean isFind = false;
        if (node.childs.size() > 0) {
            // 遍歷孩子
            for (String childKey : node.childs.keySet()) {
                Node childNode = node.childs.get(childKey);
                // 首字母相同,則在該子節點繼續新增字串
                if (wordLeft.charAt(0) == childNode.str.charAt(0)) {
                    isFind = true;
                    addStr(wordLeft, childNode);
                    break;
                }
            }
        }
        // 沒有首字母相同的孩子,則將其變為子節點
        if (!isFind) {
            Node newNode = new Node(true, 1, wordLeft);
            node.addChild(wordLeft, newNode);
        }
    }

    // 新增單詞
    public void add(String word) {
        addStr(word, root);
    }

    // 在節點中查詢字串
    private boolean findStr(String word, Node node) {
        boolean isMatch = true;
        String wordLeft = word;
        String str = node.str;
        if (null != str) {
            // 字串與單詞不匹配
            if (word.indexOf(str) != 0) {
                isMatch = false;
            } else {
                // 匹配,則計算剩餘單詞
                wordLeft = word.substring(str.length());
            }
        }
        // 如果匹配
        if (isMatch) {
            // 如果還有剩餘單詞長度
            if (wordLeft.length() > 0) {
                // 遍歷孩子繼續找
                for (String key : node.childs.keySet()) {
                    Node childNode = node.childs.get(key);
                    boolean isChildFind = findStr(wordLeft, childNode);
                    if (isChildFind) {
                        return true;
                    }
                }
                return false;
            } else {
                // 沒有剩餘單詞長度,說明已經匹配完畢,直接返回節點是否為單詞
                return node.isWord;
            }
        }
        return false;
    }

    // 查詢單詞
    public boolean find(String word) {
        return findStr(word, root);
    }

    // 統計子節點字串單詞數
    private int countChildStr(String prefix, Node node) {
        // 遍歷孩子
        for (String key : node.childs.keySet()) {
            Node childNode = node.childs.get(key);
            // 匹配子節點
            int childCount = countStr(prefix, childNode);
            if (childCount != 0) {
                return childCount;
            }
        }
        return 0;
    }

    // 統計字串單詞數
    private int countStr(String prefix, Node node) {
        String str = node.str;
        // 非根結點
        if (null != str) {
            // 字首與字串不匹配
            if (prefix.indexOf(str) != 0 && str.indexOf(prefix) != 0) {
                return 0;
                // 字首匹配字串,且字首較短
            } else if (str.indexOf(prefix) == 0) {
                // 找到目標節點,返回單詞數
                return node.count;
                // 字首匹配字串,且字串較短
            } else if (prefix.indexOf(str) == 0) {
                // 剩餘字串繼續匹配子節點
                String prefixLeft = prefix.substring(str.length());
                if (prefixLeft.length() > 0) {
                    return countChildStr(prefixLeft, node);
                }
            }
        } else {
            // 根結點,直接找其子孫
            return countChildStr(prefix, node);
        }
        return 0;
    }

    // 統計字首單詞數
    public int count(String prefix) {
        // 處理特殊情況
        if (null == prefix || prefix.trim().isEmpty()) {
            return root.count;
        }
        // 從根結點往下匹配
        return countStr(prefix, root);
    }

    // 列印節點
    private void printNode(Node node, int layer) {
        // 層級遞進
        for (int i = 0; i < layer; i++) {
            System.out.print("\t");
        }
        // 列印
        System.out.println(node);
        // 遞迴列印子節點
        for (String str : node.childs.keySet()) {
            Node child = node.childs.get(str);
            printNode(child, layer + 1);
        }
    }

    // 列印字典樹
    public void print() {
        printNode(root, 0);
    }

}
package cango.scf.common.util;

public class DictionaryTreeMain {

    public static void main(String[] args) {

        DictionaryTree dt = new DictionaryTree();

        dt.add("interest");
        dt.add("interesting");
        dt.add("interested");
        dt.add("inside");
        dt.add("insert");
        dt.add("apple");
        dt.add("inter");
        dt.add("interesting");
        dt.print();

        boolean isFind = dt.find("inside");
        System.out.println("find inside : " + isFind);

        int count = dt.count("inter");
        System.out.println("count prefix inter : " + count);

    }
}