zoukankan      html  css  js  c++  java
  • Trie树算法

    算法介绍

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

    算法原理

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

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

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

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

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

    算法实现

    输入的字符数据Input.txt:

    abc
    bcd
    bca
    bcc
    bbd
    abca
    
    树节点类TreeNode.java:

    package Trie;
    
    import java.util.ArrayList;
    
    /**
     * 
     * 
     * 
     * @author lyq
     * 
     * 
     */
    public class TreeNode {
    	//节点的值
    	String value;
    	//节点孩子节点
    	ArrayList<TreeNode> childNodes;
    
    	public TreeNode(String value) {
    		this.value = value;
    		this.childNodes = new ArrayList<TreeNode>();
    	}
    
    	public ArrayList<TreeNode> getChildNodes() {
    		return childNodes;
    	}
    
    	public void setChildNodes(ArrayList<TreeNode> childNodes) {
    		this.childNodes = childNodes;
    	}
    }
    
    算法工具类TrieTool.java:

    package Trie;
    
    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.ArrayList;
    
    /**
     * 
     * 
     * 
     * @author lyq
     * 
     * 
     */
    public class TrieTool {
    	// 测试数据文件地址
    	private String filePath;
    	// 原始数据
    	private ArrayList<String[]> datas;
    
    	public TrieTool(String filePath) {
    		this.filePath = filePath;
    		readDataFile();
    	}
    
    	/**
    	 * 
    	 * 从文件中读取数据
    	 */
    	private void readDataFile() {
    		File file = new File(filePath);
    		ArrayList<String[]> dataArray = new ArrayList<String[]>();
    		try {
    			BufferedReader in = new BufferedReader(new FileReader(file));
    			String str;
    			String[] tempArray;
    			while ((str = in.readLine()) != null) {
    				tempArray = new String[str.length()];
    				for (int i = 0; i < str.length(); i++) {
    					tempArray[i] = str.charAt(i) + "";
    				}
    				dataArray.add(tempArray);
    			}
    
    			in.close();
    		} catch (IOException e) {
    			e.getStackTrace();
    		}
    
    		datas = dataArray;
    	}
    
    	/**
    	 * 
    	 * 构造Trie树
    	 * 
    	 * 
    	 * 
    	 * @return
    	 */
    	public TreeNode constructTrieTree() {
    		TreeNode rootNode = new TreeNode(null);
    		ArrayList<String> tempStr;
    
    		for (String[] array : datas) {
    			tempStr = new ArrayList<String>();
    
    			for (String s : array) {
    				tempStr.add(s);
    			}
    
    			// 逐个字符串的添加
    			addStrToTree(rootNode, tempStr);
    		}
    
    		return rootNode;
    	}
    
    	/**
    	 * 
    	 * 添加字符串的内容到Trie树中
    	 * 
    	 * 
    	 * 
    	 * @param node
    	 * 
    	 * @param strArray
    	 */
    	private void addStrToTree(TreeNode node, ArrayList<String> strArray) {
    		boolean hasValue = false;
    		TreeNode tempNode;
    		TreeNode currentNode = null;
    
    		// 子节点中遍历寻找与当前第一个字符对应的节点
    		for (TreeNode childNode : node.childNodes) {
    			if (childNode.value.equals(strArray.get(0))) {
    				hasValue = true;
    				currentNode = childNode;
    				break;
    			}
    
    		}
    
    		// 如果没有找到对应节点,则将此节点作为新的节点
    		if (!hasValue) {
    			// 遍历到了未曾存在的字符值的,则新键节点作为当前节点的子节点
    			tempNode = new TreeNode(strArray.get(0));
    			// node.childNodes.add(tempNode);
    			insertNode(node.childNodes, tempNode);
    			currentNode = tempNode;
    		}
    		strArray.remove(0);
    
    		// 如果字符已经全部查找完毕,则跳出循环
    		if (strArray.size() == 0) {
    			return;
    		} else {
    			addStrToTree(currentNode, strArray);
    		}
    	}
    
    	/**
    	 * 
    	 * 将新建的节点按照字母排序的顺序插入到孩子节点中
    	 * 
    	 * 
    	 * 
    	 * @param childNodes
    	 * 
    	 *            孩子节点
    	 * 
    	 * @param node
    	 * 
    	 *            新键的待插入的节点
    	 */
    	private void insertNode(ArrayList<TreeNode> childNodes, TreeNode node) {
    		String value = node.value;
    		int insertIndex = 0;
    
    		for (int i = 0; i < childNodes.size() - 1; i++) {
    			if (childNodes.get(i).value.compareTo(value) <= 0
    					&& childNodes.get(i + 1).value.compareTo(value) > 0) {
    				insertIndex = i + 1;
    				break;
    			}
    		}
    
    		if (childNodes.size() == 0) {
    			childNodes.add(node);
    		} else if (childNodes.size() == 1) {
    			// 只有1个的情况额外判断
    			if (childNodes.get(0).value.compareTo(value) > 0) {
    				childNodes.add(0, node);
    			} else {
    				childNodes.add(node);
    			}
    		} else {
    			childNodes.add(insertIndex, node);
    		}
    
    	}
    
    }
    
    测试类Client.java:

    package Trie;
    
    /**
     * 
     * Trie树算法
     * 
     * @author lyq
     * 
     * 
     */
    public class Client {
    	public static void main(String[] args) {
    		String filePath = "C:\Users\lyq\Desktop\icon\input.txt";
    		
    		TrieTool tool = new TrieTool(filePath);
    		tool.constructTrieTree();
    	}
    }
    
    算法的最终构造的树的形状大致如下(由于时间关系,我就没有写在控制台输出的程序了):

    root

    |

    a      b

    |        |---|

    b       b   c   

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

    c        d    a    c      d

    |

    a

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

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

    算法的特点

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

  • 相关阅读:
    Keil 4 与 J-Link 8 连接配置
    嵌入式之认识内存
    Windows10光驱位硬盘不识别
    无法定位程序输入点ucrtbase.terminate于动态链接库api-ms-win-crt-runtime-l1-1-0.dll上
    博客一夜回到解放前
    电动汽车-电池
    STM32书集选择
    嵌入式—学习嵌入式系统需具备的条件、方法及步骤
    Modbus抄表中应用到CRC8+CRC16+CRC32源码
    USART输出乱码3F,RS485抄表
  • 原文地址:https://www.cnblogs.com/bianqi/p/12183980.html
Copyright © 2011-2022 走看看