Trie树,并查集,堆与哈希表

Trie字典树

Trie又称字典树用来高效的存储与查找字符串。例题:字符串统计
模板如下:

int son[maxn][N],cnt[maxn],idx; //其中N的取值和题目有关,例如字符串全是小写字母则N = 26
//son[][]数组用来存储字符串,本题中它存储的是字符串中每个字符的下标,cnt[i]表示以i结尾的字符串总共出现的次数。
void insert(char str[]){
    int p = 0;
    for(int i = 0; str[i] ;i++){ //因为字符数组最后一个元素是'\0'被视为空
       int u = str[i] - 'a';
       if(!son[p][u]) son[p][u] = ++idx;
       p = son[p][u];
    }
    cnt[p]++;
    return ;
}
int query(str []){
    int p = 0;
    for(int i = 0;str[i];i++){
        int u = str[i] - 'a';
        if(!son[p][u]) return 0;
        p = son[p][u];
}
    return cnt[p];
}

并查集

  并查集很常用,因为代码较短,但是需要精巧的思路。它的作用是:
1.将两个集合合并。 2.询问两个集合是否在一个集合中。
将以上两个操作在近似O(1)的时间内完成。
  并查集将每个集合用树表示,树根的编号是集合的编号。利用p[x]表示x父节点这样一来,
判断树根:if(p[x] == x)
求出集合x编号:while(p[x] != x) x = p[x];其中,这一步的时间复杂度与树的高度有关,为了优化引入:路径压缩。
合并集合:x的根节点指向y的根节点p[x] =y。或者,p[y] = x;
例题:并查集
模板如下:

#include<iostream>
using namespace std;
const int maxn = 1e5 + 10;
int n,m,p[maxn];
int find(int x){ //核心,已经包含了路径压缩
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}
int main(){
    cin>>n>>m;
    for(int i = 1;i<=n;i++) p[i] = i;
    while(m--){
        string opt;
        cin>>opt;
        int a,b;
        scanf("%d%d",&a,&b);
        if(opt == "M"){
            p[find(a)] = find(b);
        }else{
            if(find(a) == find(b)) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

有时并查集还需要维护其他信息,比如一个集合中元素的个数。代码如下:

#include<iostream>
using namespace std;
const int maxn = 1e5 + 10;
int p[maxn],num[maxn],n,m; //num数组动态维护集合中元素数量,只维护根节点
int find(int x){
    if(p[x] != x) p[x] =find(p[x]);
    return p[x]; 
}
int main(){
    cin>>n>>m;
    for(int i = 1; i<=n ;i++){
       p[i] = i;
       num[i] = 1;//初始化为1
    } 
    while(m--){
      string opt;
      cin>>opt;
      int a,b;
      if(opt == "C"){
          scanf("%d%d",&a,&b);
          if (find(a) != find(b)){
             num[find(b)] += num[find(a)]; //只修改根节点的num值
             p[find(a)] = find(b); 
          }
      }else if(opt == "Q1"){
          scanf("%d%d",&a,&b);
          if(p[find(a)]==p[find(b)]) puts("Yes");
          else puts("No");
      }else{
          scanf("%d",&a);
          cout << num[find(a)]<< endl; //输出根节点
      }
    }
    return  0;
}

  先介绍下完全二叉树,除了最后一层外,每层节点都有两个子节点。且最后一层节点只能从左到右依次排布。完全二叉树可以用一维数组存储,下标从1开始。
  堆是特殊的完全二叉树,它可以被分为小根堆和大根堆。小根堆满足:每个节点的若有子节点,根节点的值小于子节点的值。
堆的基本操作,堆构建,调整堆(向上调整up,向下调整down)
模板如下:例题堆排序

#include<iostream>
#include<algorithm>
int len,heap[maxn]; //len表示堆的长度,heap表示堆,以小根堆为例。
void down(int u){
    int t = u;
    if(2 * u <= len && heap[t]<=heap[2*u]) t = 2*u;
    if(2*u + 1 <= len && heap[t]<=heap[2*u+1]) t = 2*u + 1;
    if(u!=t){
        swap(heap[t],heap[u]);
        down(t);
} 
}
void up(int u){
    while(u/2 && heap[u]<heap[u/2]){
         swap(heap[u],heap[u/2]);
         up(u/2);
    }
}
int main(){
   for(int i = 1; i<=n ;i++) cin>>heap[i]; //下标从1开始!
   len = n;
   //构建堆,复杂度是O(n),可证明。
   for(int i = n/2; i>=1 ;i--) down(i);
   while(m--){ //堆排序过程,用最后一个元素覆盖第一个元素,然后down一遍
   cout<<heap[1]<<" ";
   heap[1] = heap[len--];
   down(1); 
}
}

哈希表

  哈希表主要用来将较大范围的数,映射到较小范围。这里的范围值得是数量。举个例子,将0-1000的数映射到0-50。哈希表主要操作:1.哈希函数,它可以被定义为直接取模 eg. x mod p。其中对于p而言,要选择质数,可以证明这样选择冲突概率最小。2.冲突处理,可以分为拉链法和开放寻址法。
拉链法
拉链法
  当发生冲突的时候,将冲突元素用链表存储。链表要存储原始值。一般而言,链表的长度不会太长,因此它可以看做O(1)的。如果需要删除元素的话,一般而言设置标志位,而不直接在链表中删除。
模板,例题:模拟散列

const int maxn = 1e5 + 3; //需要根据题目找p
int h[maxn],val[maxn],ne[maxn],idx;
void insert(int x){
    int k = (x%maxn + maxn) % maxn; //将负数和正数取模的结果,统一转换为正数
    val[idx] = x; 
    ne[idx] = h[k],h[k] = idx++; //h[k]作为头指针,相当于head
}
bool find(int x){
    int k = (x%maxn + maxn)%maxn;
    for(int i  = h[k]; i!=-1 ;i = ne[i])
        if(val[i] == x) return true;
    return false;
}

开放寻址法
在这里插入图片描述
  开放寻址法,当发生冲突时自动寻找到下一位,直到找到空为止。因此,通常需要较大的空间来避免冲突,一般而言要高于题目数据量的2-3倍。对于删除而言,同样是加标记。
模板:

const int maxn = 3e5 + 3,null = 0x3f3f3f3f; //null满足在题目中,数据元素外即可
int h[maxn];
int find(int x){
    int k = (x % maxn + maxn)% maxn;
    while(h[k]!=null && h[k] != x){ //当前位置为空且不是x,继续寻找
        k++; 
        if(k == maxn) k = 0; //找到边界,从头寻找。
    }
    return k;
}

字符串哈希
  字符串哈希首先将字符串视为一个P进制的数,接着将将其转换为10进制数,然后对较小的数字Q取模,最终映射到0-Q-1之间。
需要注意:
1.一般情况不要将,某一个字母映射为0.
2.一般,p取133或13331,Q取2^64不会发生冲突。但是不是100%。
用法,快速判断某两串字符串是否相同。例题字符串哈希

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 1e5 + 10,P = 131; //取P = 131
int n,m;
char str[maxn];
unsigned long long h[maxn],p[maxn]; //利用unsigned long long存储字符串的哈希值,当哈希值可以被自动取模,而不需要手动取模
unsigned long long Hash(int l ,int r){ //返回处于区间[l,r]的字符串的哈希值
    return h[r] - h[l - 1] *p[r - l + 1];
}
int main(){
    cin>>n>>m;
    scanf("%s",str + 1);
    p[0] = 1;
    for(int i = 1;i<=n;i++){
        p[i] = p[i-1] * P; //构造P的n次幂,加速计算
        h[i] = h[i-1] * P + str[i]; //构造前缀哈希值
     }
     while(m--){
         int l1,r1,l2,r2;
         scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
         if(Hash(l1,r1) == Hash(l2,r2)) puts("Yes"); //假定不会冲突。
         else puts("No");
     }
     return 0;
}

  本章,涵盖了《数据结构》的很多考点。作为计算机专业的学生必须熟悉。至于图论将在下一章讲解。本篇全部使用手撕的方式实现这些数据结构,它们的一个好处是速度快。下节我们使用STL实现数据结构。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值