Trie树算法

算法介绍

第一眼看到Trie树算法,首先明白的就是他一定是用树形结构实现的算法。后来实现完整个算法才知道其实他也是压缩树,类似于哈弗曼编码和CF-Tree,因为树中保留了公共的前缀,减少了不必要的重复存储空间。所以查询效率会高很多,如果你明白哈弗曼编码的实现过程,这个自然也是一样的道理。那Trie树与Huffman编码树有什么区别呢,Huffman是0或1的编码,而Trie则是文本查找树,节点上可以是一个字母字符,也可以是汉字等等,大体就是这个意思。好,下面说说算法的原理。

算法原理

1、首先获取所有的文本数据,划分成逐条逐条的形式。

2、读入每行数据,对照当前比较字符值与当前节点的子节点比较,寻找到与之匹配的节点

3、如果找到对应的子节点,将子节点作为当前节点,并移除数据的此字符,继续步骤2。

4、如果未找到对应子节点,新建节点插入当前的节点中,并将新节点作为当前节点,继续步骤2。

5、操作的终止条件为数据中的字符已经全部移除比较完毕。

算法实现

输入的字符数据Input.txt:

[java]  view plain copy print ?
  1. abc  
  2. bcd  
  3. bca  
  4. bcc  
  5. bbd  
  6. abca  
树节点类TreeNode.java:

[java]  view plain copy print ?
  1. package Trie;  
  2.   
  3. import java.util.ArrayList;  
  4.   
  5. /** 
  6.  *  
  7.  *  
  8.  *  
  9.  * @author lyq 
  10.  *  
  11.  *  
  12.  */  
  13. public class TreeNode {  
  14.     //节点的值  
  15.     String value;  
  16.     //节点孩子节点  
  17.     ArrayList<TreeNode> childNodes;  
  18.   
  19.     public TreeNode(String value) {  
  20.         this.value = value;  
  21.         this.childNodes = new ArrayList<TreeNode>();  
  22.     }  
  23.   
  24.     public ArrayList<TreeNode> getChildNodes() {  
  25.         return childNodes;  
  26.     }  
  27.   
  28.     public void setChildNodes(ArrayList<TreeNode> childNodes) {  
  29.         this.childNodes = childNodes;  
  30.     }  
  31. }  
算法工具类TrieTool.java:

[java]  view plain copy print ?
  1. package Trie;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileReader;  
  6. import java.io.IOException;  
  7. import java.util.ArrayList;  
  8.   
  9. /** 
  10.  *  
  11.  *  
  12.  *  
  13.  * @author lyq 
  14.  *  
  15.  *  
  16.  */  
  17. public class TrieTool {  
  18.     // 测试数据文件地址  
  19.     private String filePath;  
  20.     // 原始数据  
  21.     private ArrayList<String[]> datas;  
  22.   
  23.     public TrieTool(String filePath) {  
  24.         this.filePath = filePath;  
  25.         readDataFile();  
  26.     }  
  27.   
  28.     /** 
  29.      *  
  30.      * 从文件中读取数据 
  31.      */  
  32.     private void readDataFile() {  
  33.         File file = new File(filePath);  
  34.         ArrayList<String[]> dataArray = new ArrayList<String[]>();  
  35.         try {  
  36.             BufferedReader in = new BufferedReader(new FileReader(file));  
  37.             String str;  
  38.             String[] tempArray;  
  39.             while ((str = in.readLine()) != null) {  
  40.                 tempArray = new String[str.length()];  
  41.                 for (int i = 0; i < str.length(); i++) {  
  42.                     tempArray[i] = str.charAt(i) + "";  
  43.                 }  
  44.                 dataArray.add(tempArray);  
  45.             }  
  46.   
  47.             in.close();  
  48.         } catch (IOException e) {  
  49.             e.getStackTrace();  
  50.         }  
  51.   
  52.         datas = dataArray;  
  53.     }  
  54.   
  55.     /** 
  56.      *  
  57.      * 构造Trie树 
  58.      *  
  59.      *  
  60.      *  
  61.      * @return 
  62.      */  
  63.     public TreeNode constructTrieTree() {  
  64.         TreeNode rootNode = new TreeNode(null);  
  65.         ArrayList<String> tempStr;  
  66.   
  67.         for (String[] array : datas) {  
  68.             tempStr = new ArrayList<String>();  
  69.   
  70.             for (String s : array) {  
  71.                 tempStr.add(s);  
  72.             }  
  73.   
  74.             // 逐个字符串的添加  
  75.             addStrToTree(rootNode, tempStr);  
  76.         }  
  77.   
  78.         return rootNode;  
  79.     }  
  80.   
  81.     /** 
  82.      *  
  83.      * 添加字符串的内容到Trie树中 
  84.      *  
  85.      *  
  86.      *  
  87.      * @param node 
  88.      *  
  89.      * @param strArray 
  90.      */  
  91.     private void addStrToTree(TreeNode node, ArrayList<String> strArray) {  
  92.         boolean hasValue = false;  
  93.         TreeNode tempNode;  
  94.         TreeNode currentNode = null;  
  95.   
  96.         // 子节点中遍历寻找与当前第一个字符对应的节点  
  97.         for (TreeNode childNode : node.childNodes) {  
  98.             if (childNode.value.equals(strArray.get(0))) {  
  99.                 hasValue = true;  
  100.                 currentNode = childNode;  
  101.                 break;  
  102.             }  
  103.   
  104.         }  
  105.   
  106.         // 如果没有找到对应节点,则将此节点作为新的节点  
  107.         if (!hasValue) {  
  108.             // 遍历到了未曾存在的字符值的,则新键节点作为当前节点的子节点  
  109.             tempNode = new TreeNode(strArray.get(0));  
  110.             // node.childNodes.add(tempNode);  
  111.             insertNode(node.childNodes, tempNode);  
  112.             currentNode = tempNode;  
  113.         }  
  114.         strArray.remove(0);  
  115.   
  116.         // 如果字符已经全部查找完毕,则跳出循环  
  117.         if (strArray.size() == 0) {  
  118.             return;  
  119.         } else {  
  120.             addStrToTree(currentNode, strArray);  
  121.         }  
  122.     }  
  123.   
  124.     /** 
  125.      *  
  126.      * 将新建的节点按照字母排序的顺序插入到孩子节点中 
  127.      *  
  128.      *  
  129.      *  
  130.      * @param childNodes 
  131.      *  
  132.      *            孩子节点 
  133.      *  
  134.      * @param node 
  135.      *  
  136.      *            新键的待插入的节点 
  137.      */  
  138.     private void insertNode(ArrayList<TreeNode> childNodes, TreeNode node) {  
  139.         String value = node.value;  
  140.         int insertIndex = 0;  
  141.   
  142.         for (int i = 0; i < childNodes.size() - 1; i++) {  
  143.             if (childNodes.get(i).value.compareTo(value) <= 0  
  144.                     && childNodes.get(i + 1).value.compareTo(value) > 0) {  
  145.                 insertIndex = i + 1;  
  146.                 break;  
  147.             }  
  148.         }  
  149.   
  150.         if (childNodes.size() == 0) {  
  151.             childNodes.add(node);  
  152.         } else if (childNodes.size() == 1) {  
  153.             // 只有1个的情况额外判断  
  154.             if (childNodes.get(0).value.compareTo(value) > 0) {  
  155.                 childNodes.add(0, node);  
  156.             } else {  
  157.                 childNodes.add(node);  
  158.             }  
  159.         } else {  
  160.             childNodes.add(insertIndex, node);  
  161.         }  
  162.   
  163.     }  
  164.   
  165. }  
测试类Client.java:

[java]  view plain copy print ?
  1. package Trie;  
  2.   
  3. /** 
  4.  *  
  5.  * Trie树算法 
  6.  *  
  7.  * @author lyq 
  8.  *  
  9.  *  
  10.  */  
  11. public class Client {  
  12.     public static void main(String[] args) {  
  13.         String filePath = "C:\\Users\\lyq\\Desktop\\icon\\input.txt";  
  14.           
  15.         TrieTool tool = new TrieTool(filePath);  
  16.         tool.constructTrieTree();  
  17.     }  
  18. }  
算法的最终构造的树的形状大致如下(由于时间关系,我就没有写在控制台输出的程序了):

root

|

a      b

|        |---|

b       b   c   

|         |     |----|-----|

c        d    a    c      d

|

a

算法的遗漏点和可以改进的地方

这里所说的遗漏点就是在插入节点的时候,需要按照字母的排序插入,这是为了使得查找更加的高效。算法在构建树的时候每次都从根节点开始往下找,效率不够高,其实更好的办法是把输入数据进行字典序的排序,然后再当前节点做处理,要么继续往下添加,要么回溯到上一个节点。

算法的特点

算法的特点在最开始介绍的时候也已经提到过,利用了字符串的公共前缀减少了查询时间,最大限度的减少无谓的字符串比较,常用于做文本的词频统计。

你可能感兴趣的:(Trie树算法)