字典树基础

目录

简介

树的建立

常见代码

思考

拓展-堆的应用

字典树

例题

代码

简介

字典树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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值