目录
简介
字典树Trie是一种通过建树达到快速查找字符串的效果
快速查找的代价是空间换时间
树的建立
建树有两种方法
1.通过指针指向子节点,实现从root开始的访问
2.静态数组建立
二者互有优劣
指针指向可以有效节省空间,但实现复杂
数组实现简单,但耗费空间资源较多
常见代码
一般还是选择采用数组建立,建树常用代码如下
以建立二叉查找树为例,代码示例如下
#include<bits/stdc++.h>
#define int long long
#define md(arr) memset(arr,0,sizeof(arr))
using namespace std;
#define N (int)1e5
struct node{
int val;
int left,right;
}t[N];
int cnt=1;//代表节点数量
void insert(int val,int root){
if(t[root].val>=val){
if(t[root].left==0){
t[cnt].val=val;
t[root].left=cnt++;
}else{
insert(val,t[root].left);
}
}else{
if(t[root].right==0){
t[cnt].val=val;
t[root].right=cnt++;
}else{
insert(val,t[root].right);
}
}
}
void inOrder(int cur){
if(cur==0)return;
inOrder(t[cur].left);
cout<<t[cur].val<<" ";
inOrder(t[cur].right);
}
void work(){
md(t);
int arr[10]={3,4,1,2,5,7,9,6,8,0};
//先建根
t[1].val=arr[0];cnt++;
for(int i=1;i<10;i++)insert(arr[i],1);
inOrder(1);
return ;
}
signed main (){
ios::sync_with_stdio(false);cin.tie(NULL);
int Case=1;
//cin>>Case;
while(Case--){
work();
}
return 0;
}
思考
cnt的作用是什么?
left,right存在的必要性是什么?
一.
cnt代表了一棵树上一共的节点数量,所以它本质上是struct node 里的一个static变量,t[N]数组每一个元素代表的就是一个节点,增加时也依靠cnt变量 找到 数组保存节点的末尾
二.
既然t[N]数组的元素只是一个节点,那么我们建树必然要有边的概念,什么串联起了节点之间的联系?就是left,right,他们的数值指向的是该节点的子节点在t[N]中的下标,从而可以实现遍历
同理,我们还可以node中添加变量p记录节点的父节点下标
拓展-堆的应用
既然我们使用left,right记录节点的子节点,那有什么办法可以默认指代节点呢,即如建立堆时的思路,节点i的左右节点分别是2*i 以及2*i+1
这种方法建树会更加浪费空间,需要谨慎使用,如n层树的空间复杂度达到了O(2^n)
遍历还要警惕数组越界问题
修改上文代码主要函数后如下
#include<bits/stdc++.h>
#define int long long
#define md(arr) memset(arr,0,sizeof(arr))
using namespace std;
#define N (int)1e5
int t[N];
void insert(int val,int root){
int l=2*root;
int r=2*root+1;
if(t[root]>=val){
if(t[l]==-1){
t[l]=val;
}else{
insert(val,l);
}
}else{
if(t[r]==-1){
t[r]=val;
}else{
insert(val,r);
}
}
}
void inOrder(int cur){
if(t[cur]==-1)return;
inOrder(2*cur);
cout<<t[cur]<<" ";
inOrder(2*cur+1);
}
void work(){
memset(t,-1,sizeof(t));//原始值要暗含空节点信息,此处位修改为-1,t[i]=-1来标记
int arr[10]={3,4,1,2,5,7,9,6,8,0};
//先建根
t[1]=arr[0];
for(int i=1;i<10;i++)insert(arr[i],1);
inOrder(1);
return ;
}
signed main (){
ios::sync_with_stdio(false);cin.tie(NULL);
int Case=1;
//cin>>Case;
while(Case--){
work();
}
return 0;
}
运行结果如下
字典树
例题
字典树的建立不过是二叉树建立的变形,考察的是我们对树建立的熟练程度,这也是为什么前文对二叉树建立详细介绍,现在从二叉树转为了多叉树
代码
上述链接模板题代码如下
#include<bits/stdc++.h>
//#define int long long
#define md(arr) memset(arr,0,sizeof(arr));
using namespace std;
const int N=8*1e5;
int cnt=1;
struct node{
//等效静态数组建立树
//不同的是一个数节点有val left right
//tire不同的是子节点下标有暗含了val
int son[26];
bool vis;
bool end;
}trie[N];
void insert(string& s){
int now=0;
for(int i=0;i<s.size();i++){
int ch=s[i]-'a';
if(trie[now].son[ch]==0)trie[now].son[ch]=cnt++;
now=trie[now].son[ch];
}
trie[now].end=1;
return;
}
int find(string &s){
int now=0;
for(int i=0;i<s.size();i++){
int ch=s[i]-'a';
if(trie[now].son[ch]==0){
return 0;
}
now=trie[now].son[ch];
}
if(trie[now].end==false)return 0;
if(trie[now].end==true and trie[now].vis==0){
trie[now].vis=1;
return 1;//第一次点到该有效名字
}else{
return 2;//重复点名
}
}
void work(){
md(trie);
int n;cin>>n;
for(int i=0;i<n;i++){
string s;cin>>s;
insert(s);
}
int m;cin>>m;
for(int i=0;i<m;i++){
string s;cin>>s;
int k=find(s);
if(k==0)cout<<"WRONG"<<endl;
else if(k==1)cout<<"OK"<<endl;
else cout<<"REPEAT"<<endl;
}
return ;
}
signed main (){
//freopen("C:\\Users\\Lenovo\\Desktop\\in.txt","r",stdin);
//freopen("C:\\Users\\Lenovo\\Desktop\\out.txt","w",stdout);
std::cin.tie(0);
std::ios::sync_with_stdio(false);
int Case=1;
//cin>>Case;
while(Case--){
work();
}
return 0;
}