【算法每日一练]-结构优化(保姆级教程 篇8 字典树,后缀数组,基数排序)
2023-12-13 10:59:03
目录
? ? ? ?
????????
? 字典树
又称Trie树、单词查找树、前缀树,是一种树形结构,用于保存关联数组,其中的键通常是字符串。适合统计、 排序和存储大量的字符串,经常被搜索引擎系统用于文本词频统计。
字典树利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
????????
字典树的3个性质:
1,根节点不包含字符,除了根节点外每一个节点都只包含一个字符。
2,从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
3,每个节点的所有子节点包含的字符都不相同(因为会共用前缀)
????????
例如,这里有单词bee,beer,bin,bike,yes。然后如图建树,然后在每个单词结束的位置都加一个end[]标记,表示从根到这里有一个单词就行了
创建过程:就是将字符串插入到字典树的过程,如果有这个分支就直接使用(公用前缀),如果没有那就创建这个分支点。
注意:每个分支点一定是26个大小,每个位置表示不同的字符。每个位置里面存的是下一个节点的下标,以便查找时候快速遍历。?
参考代码:?
#include <bits/stdc++.h>
using namespace std;
const int maxn=100005,maxz=26;//节点数
int trie[maxn][maxz];//里面存放下一个节点的下标
string word[maxn];//翻译的单词
int val[maxn];//值的下标
bool en[maxn];//单词结束标记
int n,tot;
void insert(string s,int k){//插入字符串s,下标为k
int len=s.size(),p=1;
for(int i=0;i<len;i++){
int ch=s[i]-'a';//转数字
if(!trie[p][ch]) trie[p][ch]=++tot;//不存在创建,同时记录下一个节点下标
p=trie[p][ch];//指向下一个
}
val[p]=k;//记录下标,到时候直接返回k再传给word[]即可
en[p]=1;//如果单词和翻译是一一对应的,那么就完全可以在end中直接存入下标(比如此题)
}
int search(string s){//在字典树中查该单词是否存在
int len=s.size(),p=1;
for(int i=0;i<len;i++){
p=trie[p][s[i]-'a'];
if(!p) return 0;
}
if(en[p])return val[p];//返回下标
return 0;
}
int main(){
string s,s1;
tot=1;
int i=1;
while(getline(cin,s)){
if(!s.size())break;
int j=s.find(' ');
word[i]=s.substr(0,j);
s1=s.substr(j+1);
insert(s1,i);
i++;
}
while(cin>>s){
int k=search(s);
if(k)cout<<word[k]<<'\n';
else cout<<"eh"<<'\n';
}
}
哈希树的map写法,慢?
#include <bits/stdc++.h>//map写法
using namespace std;
unordered_map<string,string> ma;
int main(){
string s,s1,s2,str;
while(getline(cin,s)){
if(s.empty()) break;
int pos=s.find(' ');
s1=s.substr(0,pos);
s2=s.substr(pos+1);
ma.insert(make_pair(s2,s1));
}
while(cin>>str){
auto it=ma.find(str);
if(it!=ma.end()) cout<<ma[str]<<'\n';
else cout<<"eh"<<'\n';
}
}
?参考样例:
dog ogday
cat atcay
pig igpay
froot ootfray
loops oppslay
????????
????????
基数排序
#include <bits/stdc++.h>//基数排序的桶排序是一种思想:桶是虚的,可以是每个数字的位,也可以是字符串的长度,你真正要的是每次排后的结果
using namespace std;//不能处理负数
const int maxn=10000;
int n,a[maxn],b[10][maxn];
int maxbit(){
int maxval=a[0],d=0;
for(int i=1;i<n;i++) if(a[i]>maxval) maxval=a[i];//找最大元素,然后求位数
while(maxval!=0){
d++;
maxval/=10;
}
return d;
}
int bitnum(int x,int bit){//求x在bit位上的数字
int tmp=1;
for(int i=1;i<bit;i++) tmp*=10;
return (x/tmp)%10;
}
void radixsort(){//基数排序
int i,j,k,bit,maxb;
maxb=maxbit();//找数组中最大位数
for(bit=1;bit<=maxb;bit++){
for(int j=0;j<n;j++){//分配
int num=bitnum(a[j],bit);//取第bit位上的数字为桶号
int index=++b[num][0];//更新读出计数器
b[num][index]=a[j];//放入元素
}
for(int i=0,j=0;i<10;i++){//收集,从0号桶开始
for(int k=1;k<=b[i][0];k++)a[j++]=b[i][k];//更新原数组
b[i][0]=0;
}
}
}
int main(){
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
radixsort();//排序
for(int i=0;i<n;i++)cout<<a[i]<<' ';
cout<<'\n';
}
#include <bits/stdc++.h>//memcpy(ar,br,sizeof(br))
using namespace std;
const int maxn=10000;
int a[maxn],n;
int maxbit(){
int d=1,p=10;
for(int i=0;i<n;i++){
while(a[i]>=p){
p*=10;++d;
}
}
return d;
}
void radixsort(){
int d=maxbit();
int tmp[maxn],cnt[10];//tmp数组存放每次的结果并给原数组,cnt存放桶中的数量
int i,j,pos,k=1;
for(i=1;i<=d;i++){//d次排序
memset(cnt,0,sizeof(cnt));
for(j=0;j<n;j++){
pos=(a[j]/k)%10;//从个位开始(往桶里扔)
cnt[pos]++;//统计每个桶中的记录数
}
for(j=1;j<10;j++) cnt[j]+=cnt[j-1];//对桶前缀和一下
for(j=n-1;j>=0;j--){//将桶中的记录倒着拿出来给tmp,即排序结果给tmp
pos=(a[j]/k)%10;//a数组必须是上次的结果
tmp[--cnt[pos]]=a[j];//前缀和下:计数减一就是下标值!!!
}
memcpy(a,tmp,sizeof(tmp));//排序结果传给原数组a
for(j=0;j<n;j++)cout<<a[j]<<"\t";cout<<'\n';
k=k*10;//用于获取下一位
}
}
/*
cnt数组就是桶计数器,用于将每次的排序结果传给排名数组a(原数组),只不过需要借助中间辅助数组tmp
*/
int main(){
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
radixsort();//排序
for(int i=0;i<n;i++)cout<<a[i]<<' ';
}
int cmp(int i,int j){
if(rk[i]!=rk[j])return rk[i]<rk[j];//比较前一半
int ri=(i+k<=n?rk[i+k]:-1);//后面没有的话补0嘛,可不就排名-1
int rj=(j+k<=n?rk[j+k]:-1);
return ri<rj;//比较后一半
}
void getsa(int n,char *str){
for(int i=1;i<=n;i++){
sa[i]=i;rk[i]=str[i];//比较码即可
}
for(int k=1;k<=n;k*=2){//倍增
sort(sa+1,sa+1+n,cmp);//先对sa排序
rk2[sa[1]]=1;//rk2是临时记录rk的数组
for(int i=2;i<=n;i++)
rk2[sa[i]]=rk2[sa[i-1]]+cmp(sa[i-1],sa[i]);//rk2可以从前一位推,如果cmp为0,那么rk相等,如果为1表示前一位小于当前位,要加1
for(int i=1;i<=n;i++)
rk[i]=rk2[i];//更新rk数组
}
}
?????????
后缀数组?
#include <bits/stdc++.h>
using namespace std;
const int maxn=100,maxm=10000;
int n;
int ss[maxn],sa[maxn];
int wa[maxn],wb[maxn],wv[maxn],c[maxm];
int cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void print(int *s,int t){
for(int i=0;i<t;i++)cout<<s[i]<<'\t';
cout<<'\n';
}
void print_suf(string s,int *sa){
for(int i=1;i<=n;i++)cout<<s.substr(sa[i])<<'\n';
}
void da(int *r,int *sa,int n,int m){
int i,k,p,*x=wa,*y=wb;
//先用c将基数排序结果给sa
for(i=0;i<m;i++)c[i]=0;//c数组是每个桶的计数器
for(i=0;i<n;i++)c[x[i]=r[i]]++;//r传递给x并统计数量
for(i=1;i<m;i++)c[i]+=c[i-1];//累加
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;//到此步:sa就是基数排序结果(2^0长度),x就是我们设置的对应rank数组
cout<<"rank: \t";
print(x,n);
cout<<"sa: \t";
print(sa,n);
//然后利用上次排序结果x进行第二第一关键字排序可得下一次的排序结果
//首先对x并移位可得第二关键字排序,然后将第二关键字转换成名次即第一关键字,然后第一关键字基数排序
for(k=1;k<=n;k<<=1){//倍增(两两结合其实就是第二第一关键字)求出每次的sa和x(rank)
p=0;//p是名次,用于求sa
for(i=n-k;i<n;i++) y[p++]=i;//先求第二关键字,补零位置的下标当然在最前面
for(i=0;i<n;i++)
if(sa[i]>=k) y[p++]=sa[i]-k;//第二关键字排序结果放入y中(移位,小于的不要动)
cout<<"y: \t";
print(y,n);
for(i=0;i<n;i++)wv[i]=x[y[i]];//将第二关键字排序结果转成名次即第一关键字wv
//在根据这次的排序结果基数排序求出x
for(i=0;i<m;i++)c[i]=0;//再次基数排序(对第一关键字)
for(i=0;i<n;i++)c[wv[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[wv[i]]]=y[i];//到此步:sa是第一关键字的,
cout<<"x[y[i]]: ";
print(wv,n);
cout<<"sa: ";
print(sa,n);
//求x数组
swap(x,y);//交换数组
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)x[sa[i]]=cmp(y,sa[i-1],sa[i],k)?p-1:p++;//sa再转x(也就是rank)
cout<<"rank: ";
print(x,n);
cout<<"sa: ";
print(sa,n);
cout<<'\n'<<'\n'<<'\n';
if(p>=n)break;//名次已经都不相同了,就可以提前结束了
m=p;//m是桶数(按名次来设置桶,专门处理同名得)
}
}
int ran[maxn],hei[maxn];
void calhei(int *r,int *sa,int n){
int i,j,k=0;
for(i=1;i<=n;i++)ran[sa[i]]=i;
print(ran,n);
for(i=0;i<n;i++){
if(k) k--;
j=sa[ran[i]-1];
while(r[i+k]==r[j+k]) k++;
hei[ran[i]]=k;
}
print(hei+1,n);
}
int main(){
string s;
cin>>s;
n=s.size();
for(int i=0;i<n;i++)ss[i]=s[i]-'a'+1;//转数字
ss[n]=0;
da(ss,sa,n+1,maxm);//构造后缀数组 maxm用于基数排序的最大值
print_suf(s,sa);
calhei(ss,sa,n);
}
文章来源:https://blog.csdn.net/m0_69478376/article/details/134878086
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!