双数组字典树

基本概念

  1. 双数组字典树是字典树的一种特殊形式,思维逻辑和字典树完全相同,只是表现形式上用两个数组来表示。 类似完全二叉树可以用一个数组来表示。
  2. 表示规范:
    用base和check两个数组来表示一个字典树。
    base数组确定父节点到子节点的映射关系;
    check数组确定子节点到父节点的映射关系;
    映射关系:
root_index = 1
base[1] = 1

child_i = base[father] + i

check[child_i] = father
check[child_i] = -father // 负数表示该节点独立成词

变量解释:

check: 当前节点父节点的编号
child_i: 子节点的编号
base数组:特殊确定的一组值
i值: x - 'a'

base值如何确定:不同的学术论文有不同的确定优化方案。

代码演示:

#include <iostream>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
using namespace std;
#define BASE 26
#define MAX_CNT 10000

class node {
	public:
		int flag;
		int next[BASE]; //每一个节点在数组中的下标
		void clear() {
			flag = 0;
			for (int i = 0; i < BASE; i++) {
				next[i] = 0;
			}
		}
} trie[MAX_CNT];

int cnt = 2, root = 1;
void clearTie() {
	int cnt = 2; //能够使用的节点的第一个编号
	int root = 1; //根节点的编号
	trie[root].clear();
	return;
}

int getNewNode() {
	trie[cnt].clear();
	return cnt++;
}

void insert(string s) {
	int p = root;
	for (auto x : s) {
		int ind = x - 'a';
		if (trie[p].next[ind] == 0) {
			trie[p].next[ind] = getNewNode();
		}
		p = trie[p].next[ind];
		//printf("insert ind %d, p %d\n", ind, p);
	}
	trie[p].flag = 1;
	return;
}

bool search (string s) {
	int p = root;
	for (auto x : s){
		int ind = x - 'a';
		p = trie[p].next[ind];
		//printf("search ind %d, p %d\n", ind, p);
		if (p == 0) {
			return false;
		}

	}
	return trie[p].flag;
}

int getBaseValue(int root, int *base, int *check) {
	int b = 1, flag = 0; //flag表示是否找到了一个合适的base值
	while (flag == 0) {
		flag = 1;
		b += 1;
		for (int i = 0; i < BASE; i++) {
			if (trie[root].next[i] == 0) continue; //当前节点的第i条边为空
			if (check[b + i] == 0) continue; //check数组b+i的位置为空
			//check[b+i]值不是空的,b值出现冲突了
			flag = 0;
			break;
		}

	}
	return b;
}

int *base, *check, da_root; //da_root表示双数组字典树根节点的编号
int convertToDoubleArrayTrie(int root, int da_root, int *base, int *check) {
	if (root == 0) return 0; //空节点
	int max_ind = da_root;

	//determin base 确定当前节点的base值
	base[da_root] = getBaseValue(root, base, check);
	for (int i = 0; i < BASE; i++) {
		if (trie[root].next[i] == 0) continue;  //当前节点的第i个子节点没有值
		check[base[da_root] + i] = da_root;
		if (trie[trie[root].next[i]].flag) {
			//当前节点的第i个子节点独立成词
			check[base[da_root] + i] = -da_root;
		}
 	}
	//确定每一个节点的base值
	for(int i = 0; i < BASE; i++) {
		if (trie[root].next[i] == 0) continue; //root没有第i棵子树
		//有第i棵子树,把第i棵子树转换成双数组的形式
		max_ind = max(
				max_ind,
				convertToDoubleArrayTrie(
				    trie[root].next[i],
				    base[da_root] + i,
				    base, check
				)
		);
		//trie[root].next[i]表示第i个节点的原节点值,base[da_root] + i表示这个节点在双数组字典中的值

	}
	return max_ind;
}

bool searchDATrie(string s) {
	int p = da_root;

	//让p节点开始往下跳
	for(auto x: s) {
		int ind = x - 'a';  //字母转换成相应的节点编号
		if (abs(check[base[p] + ind]) != p) { //判断ind位置节点的父节点是否是p
			return false;
		}

		//ind位置的父节点是p
		p = base[p] + ind;  //p跳到下一个节点

	}
		return check[p] < 0; //当前节点独立成词
}


int main() {
	//  /***   字典树插入和查找代码
	cout << "trie version 4 : " << endl;
	clearTie();
	int op;
	string s;
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> s;
		insert(s);
	}
	base = new int[MAX_CNT];
	check = new int[MAX_CNT];
	memset(base, 0, sizeof(int) * MAX_CNT);
	memset(check, 0, sizeof(int) * MAX_CNT);
	int max_ind = convertToDoubleArrayTrie(root, da_root, base, check);
	//max_ind表示所使用的数组的最大下标, 原数组的最大下标存储在cnt值中
	printf("trie usage : %lu byte\n", cnt * sizeof(node));
	printf("double array trie usage : %lu byte\n", (max_ind + 1) * sizeof(int) * 2);
  	while (cin >> s) {
		cout << "find " << s << " from trie : " << search(s) << endl;
		cout << "find " << s << " from da trie : " << searchDATrie(s) << endl;

	}

	return 0;
}

代码输出:

trie version 4 :
cnt : 2, root : 1
>> 3
>> beijing
>> hello
>> world
trie usage : 2052 byte
double array trie usage : 200 byte
>> hell
find hell from trie : 0
find hell from da trie : 0
>> hello
find hello from trie : 1
find hello from da trie : 1
>> worl
find worl from trie : 0
find worl from da trie : 0
>> world
find world from trie : 1
find world from da trie : 1
>> beijing
find beijing from trie : 1
find beijing from da trie : 1
>> beijing1
find beijing1 from trie : 0
find beijing1 from da trie : 0

总结:
双数组字典树的使用场景一:节省内存。和普通字典树相比节省了10倍左右的空间。
使用场景二:双数组字典树可以存到文件中(序列化,方便传输)。从而可以在一台电脑上建立,然后在另外一台电脑上使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值