字典树(c实现与Java实现)

本文详细介绍字典树(Trie)的概念、性质及其在不同编程语言中的实现方式,并通过实例展示了如何利用字典树进行单词的高效插入与检索。

概述

字典树(Trie),又称单词查找树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计、排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。

性质

字典树的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
字典树的三个性质:
  • 根节点不包含字符,根节点外每一个节点都只包含一个字符
  • 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
  • 每个节点的所有子节点包含的字符都不相同


数据结构实现

C语言实现

1. 定义数据结构
typedef struct TrieNode {
	struct TrieNode *children[26]; // 最多26个子节点,因为只有26个英文字母
	int cnt; // 记录该单词出现的次数,只有叶子节点时cnt才需要+1
} TrieNode;
2. 初始化根结点
/**
 * 初始化Trie树根结点
 */
void initTrie(TrieNode **root)
{
	int i;

	*root = (TrieNode *)malloc(sizeof(TrieNode));
	(*root)->cnt = 0;

	for (i = 0; i < 26; i++) {
		(*root)->children[i] = NULL;
	}
}
3. 插入单词到Trie树
/**
 * Trie树插入操作
 */
void insert(char *str, TrieNode *root)
{
	int i;

	TrieNode *p = root;

	while (*str != '\0') {
		if (p->children[*str - 'a'] == NULL) {
			TrieNode *tmp = (TrieNode *)malloc(sizeof(TrieNode));
			for (i = 0; i < 26; i ++) {
				tmp->children[i] = NULL;
			}
			tmp->cnt = 0;
			p->children[*str - 'a'] = tmp;
			p = p->children[*str - 'a'];
		} else {
			p = p->children[*str - 'a'];
		}

		str++;
	}
	p->cnt++;
}


4. 统计查找单词数量
/**
 * 统计前缀出现次数
 */
int count(char *search, TrieNode *root)
{
	TrieNode *p = root;
	while (*search != '\0') {
		if (p->children[*search - 'a'] == NULL) {
			return 0;
		} else {
			p = p->children[*search - 'a'];
			search ++;
		}
	}
	return p->cnt;
}
5. 清理Trie树
/**
 * 清理Trie树
 */
void delTrie(TrieNode *root)
{
	int i;
	for (i = 0; i < 26; i++) {
		if (root->children[i] != NULL) {
			delTrie(root->children[i]);
		}
	}
	free(root);
}

Java语言实现

同C语言实现相比,使用Java会让代码更具抽象性,代码会显得高内聚,低耦合。Java实现的Trie树代码如下:
public static class TrieNode {
    private TrieNode[] children;
    private int cnt;

    public TrieNode() {
        children = new TrieNode[26];
        cnt = 0;
    }

    /**
     * 插入单词
     */
    public void insert(String word) {
        if (word == null || word.length() == 0) {
            return;
        }

        TrieNode p = this;
        for (int i = 0, len = word.length(); i < len; i++) {
            int loc = word.charAt(i) - 'a';
            if (p.children[loc] == null) {
                p.children[loc] = new TrieNode();
            }
            p = p.children[loc];
        }
        p.cnt++;
    }

    /**
     * 搜索单词
     */
    public TrieNode search(String word) {
        if (word == null || word.length() == 0) {
            return null;
        }

        TrieNode p = this;
        for (int i = 0, len = word.length(); i < len; i++) {
            int loc = word.charAt(i) - 'a';
            if (p.children[loc] == null) {
                return null;
            }
            p = p.children[loc];
        }

        return p;
    }

    public int getCnt() {
        return cnt;
    }
}

时间复杂度

插入、查找的时间复杂度均为O(n),n为字符串的长度。空间复杂度较高,O(26^n),典型空间换时间。

参考题目

自从进入Android领域,我就被Java语言的抽象性吸引住了,目前做ACM基本都是用Java实现。这里举一个牛客网上的例子.
题目:
请设计一个高效的方法,找出任意指定单词在一篇文章中的出现频数。
给定一个string数组article和数组大小n及一个待统计单词word,请返回该单词在文章中的出现频数。保证文章的词数小于等于1000。

解答:
import java.util.*;

public class Frequency {
    public int getFrequency(String[] article, int n, String word) {
        TrieNode root = new TrieNode();
		for (int i = 0; i < n; i ++) {
            root.insert(article[i]);
        }
        
        TrieNode p = root.search(word);
        if (p == null) {
            return 0;
        } else {
            return p.cnt;
        }
    }
    
    public static class TrieNode {
        public TrieNode[] son;
        public int cnt;
        
        public TrieNode() {
            this.son = new TrieNode[26];
            this.cnt = 0;
        }
        
        public void insert(String word) {
            if (word == null || word.length() == 0) {
                return;
            }
            
            TrieNode p = this;
            for (int i = 0, len = word.length(); i < len; i++) {
                int loc = word.charAt(i) - 'a';
                if (p.son[loc] == null) {
                    p.son[loc] = new TrieNode();
                }
                p = p.son[loc];
            }
            p.cnt++;
        }
        
        public TrieNode search(String word) {
            if (word == null || word.length() == 0) {
                return null;
            }
            
            TrieNode p = this;
            for (int i = 0, len = word.length(); i < len; i++) {
                int loc = word.charAt(i) - 'a';
                if (p.son[loc] == null) {
                    return null;
                }
                p = p.son[loc];
            }
            return p;
        }
    }
}

后记

其实博主写这篇博客,也是因为电话面试涂鸦移动的时候被问到了智能提示,其实智能提示实现不难,trie树+top k,中文的话转成拼音存储即可,我当时虽然回答出来了,但是连连看的题目没回答上,蛋疼

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

低调小一

您的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值