字典树/并查集/大小根堆

  • 字典树(Tire树)

    • 用来高效存储和查找字符串集合的数据结构
    • 基本性质:

      • 根节点不包含字符
      • 除根节点外每一个节点都只包含一个字符:
      • 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串,每个节点的所有子节点包含的字符都不相同
      • 有的节点有标记,表示以改节点为尾存在一个单词
    • 代码:

       int son[N][26], cnt[N], idx;//son[p][u] = x p表示父节点的编号,u表示p节点的儿子的名字,x表示儿子的编号
       //cnt[p]表示以p节点为结尾的单词的个数
       //编号0既代表根节点又代表空节点,所有没有儿子的节点都指向空节点
       void insert(char str[]){
      	 int p = 0;//编号指针
      	 for(int i = 0; str[i]; ++ i){ //遍历字符串的每一个字符
      		 int u = str[i] - 'a'; //求出每个字符的名字(a~z名字对应0~25)
      		 if(!son[p][u]) son[p][u] = ++ idx; //p节点没有u儿子,就添加u儿子并且给儿子分配编号为++idx
      		 p = son[p][u]; //指向儿子节点
      	 }
      	 cnt[p] ++ ; //循环完毕以p节点为结尾的单词个数加一
       }
       
       //查询某个字符串的次数
       int query(char str[]){
      	 int p = 0;
      	 for(int i = 1; str[i]; ++ i){
      		 int u = str[i] - 'a';
      		 if(!son[p][u]) return 0;
      		 p = son[p][u];
      	 }
      	 return cnt[p]; //虽然有些节点的名字一样但是编号各不相同,所以可以保证cnt[p]就是所要查询的字符串的个数
       }
      
  • 并查集:

    • 作用:

      • 将两个集合合并O(1)
      • 询问两个元素是否在一个集合当中O(1)
      • 求集合元素的数量
    • 原理:

      • 每个集合用树来存储,树根的编号代表整个集合的编号,每个节点存储他的父节点
      • 如何判断树根:树根的父节点就是自己即fa[x] = x,x就是树根
      • 如何求x在哪个集合:当x的父节点不为根节点时,令x = fa[x],直到x为根节点时,x就是所在集合的编号
      • 如何合并两个集合:直接让根节点的父节点指向另一个集合的根节点
      • 如何统计一个集合的元素个数:添加一个size数组,初始时每个元素自成一个集合,size初始化为1,根节点存储所在集合的元素个数,每合并两个集合a,b,让size(find(x)) += size(find(b))
    • 代码:

      int size[N], f[N]; //size统计根节点所在集合的元素个数,f存储每个节点的祖先节点
      //初始化,每个节点的父节点为自己
      void init(){
      	for(int i = 1; i <= n; ++ i) f[i] = i;
      }
      
      //查找祖宗节点 + 路径压缩
      //路径压缩:在找祖宗节点的过程中将路径上节点的父节点都修改为祖宗节点
      int find(int x){
      	return x == f[x] ? x : (f[x] = find(f[x]));
      }
      
      //合并x,y所在的集合
      void merge(int x, int y){
      	f[find(x)] = f[find(y)];
      }
      
      //判断x,y是否在同一个集合中
      if(f[find(x)] == f[find(y)]) 在
      else not 在
      
      //需要统计集合元素个数时
      //初始化
      void init(){
      	for(int i = 1; i <= n; ++ i){
      		f[i] = i;
      		size[i] = 1;
      }
      //合并集合
      void merge(int x, int y){
          size[find(x)] += size[find(y)];
      	f[find(y)] = f[find(x)];
      	
      }
      
  • 堆:

    • 堆是一棵完全二叉树
    • 上层节点全满,最后一层节点从左到右排
    • 用一维数组模拟堆 x的左儿子下标为2x,右儿子下标为2x+1
    • 主要操作:

      • 插入一个数
      • 求集合中的最小值
      • 删除最小值
      • 删除任意一个元素
      • 修改任意一个元素
    • 小根堆:

      • 根节点的值最小,子树的根节点的值是子树中最小值

      • 基本操作:
        • 插入一个数\(heap[ ++ size] = x; up(size)\)
        • 求最小值\(heap[1]\)
        • 删除最小值\(heap[1] = heap[size]; size -- ; down(1)\)
        • 删除下标为k的节点\(heap[k]=heap[size];size--;up(k);down(k)\)
        • 修改下标为的k节点的值\(heap[k] = x;up(k);down(k)\)
        • up函数将大数往上移动,down函数将小数往下移动,size表示最后一个节点的下标,heap数组存储每个节点的值,节点下标从1开始
        • down函数实现:每次比较根节点与左右儿子节点的大小,更新根节点的值为这三个节点中的最小值
      • 进阶操作:
        • 删除第k个插入的节点:根据ph数组求出节点在堆中的下标,根据下标删除节点
        • 修改第k次插入的节点的值:根据下标修改值
        • 第k个插入的节点不一定是下标为k的节点,因为每次进行修改或者删除操作只交换了值,而没有改变下标,而第k个插入的节点的值是确定的,而在堆中下标为k的节点的值已经被改变了,所以要另开hp数组专门存储第k个插入的节点在堆中的下标,hp数组在修改删除元素时动态维护。由于每次删除或修改是根据节点在堆中的下标进行的,为了动态维护hp数组,此时需要知晓节点是第几次插入的,因此还要另开ph数组来根据节点在堆中的下标求出其第几次插入
        • 主要体现在交换节点值的操作上:每次交换节点的值相当于节点在堆中的下标进行了交换,首先通过hp数组求出节点的插入次序,同时根据插入次序交换节点的ph数组的值,以保证第k个插入的节点的值不变
      • 代码:
        //一般操作版
        int h[N], idx;//h数组存储每个节点的值,idx表示最后一个节点的下标,所有节点下标从1开始
        
        //down函数
        void down(int u){
        	int t = u, l = 2 * u, r = 2 * u + 1;//t用来指向三个节点中的最小值节点的下标,l,r表示u的左右儿子的下标
        	if(l <= idx && h[l] < h[u]) t = l;//左儿子存在且值小于u,t更新为左儿子
        	if(r <= idx && h[r] < h[u]) t = r;//右儿子存在且值小于u,t更新为右儿子
        	if(t != u) swap(h[u], h[t]), down(t);//发生了更新说明需要u下移,递归处理
        }
        
        //up函数
        void up(int u){
        //不论u是左右儿子,其父节点下标均为u/2
        	while(u / 2 && h[u / 2] > h[u]){//父节点存在且值比u小,就交换值
        		swap(h[u], h[u / 2])
        		u /= 2; //更新下标
        	}
        }
        
        //插入值x
        void add(int x){
        	h[ ++ idx] = x;//赋值
        	up(idx); //上升
        }
        
        //删除下标为k的节点
        void del(int u){
        	h[u] = h[idx];
        	idx -- ;
        	up(u);down(u);//要么往上要么往下
        }
        
        //将下标为u的节点的值改为x
        void change(int k, int x){
        	h[u] = x;
        	up(u);down(u);
        }
        
        //初始化
        void init(){
        	//输入
        	int x;
        	for(int i = 1; i <= n; ++ i){
        		cin >> x;
        		h[ ++ idx] = x;
        	}
        	//建堆
        	//建堆O(n),i不必从n开始因为有n/2的元素本来就在最底层
        	for(int i = n / 2; i; -- i) down(i);
        }
        
        
        //高级操作版
        //支持修改第k个插入的节点
        
        //h数组存储每个节点的值
        //hp[i] = k;表示下标为i的点是第k个插入的节点
        //ph[k] = i;表示第k个插入的节点的下标为i
        //idx表示最后一个节点的下标,所有节点下标从1开始
        //k记录第几次插入
        int h[N], hp[N], ph[N], idx, k;
        
        void swap_head(int u, int v){
        	swap(h[u], h[v]);//交换值
        	swap(ph[u], ph[v]);//交换下标
        	swap(hp[ph[u]], hp[ph[v]]);//交换次序
        }
        
        //上面代码中的swap函数均需要替换为swap_head()函数
        //down函数
        void down(int u){
        	int t = u, l = 2 * u, r = 2 * u + 1;//t用来指向三个节点中的最小值节点的下标,l,r表示u的左右儿子的下标
        	if(l <= idx && h[l] < h[u]) t = l;//左儿子存在且值小于u,t更新为左儿子
        	if(r <= idx && h[r] < h[u]) t = r;//右儿子存在且值小于u,t更新为右儿子
        	if(t != u) swap_head(u, t), down(t);//发生了更新说明需要u下移,递归处理
        }
        
        //up函数
        void up(int u){
        //不论u是左右儿子,其父节点下标均为u/2
        	while(u / 2 && h[u / 2] > h[u]){//父节点存在且值比u小,就交换值
        		swap_head(u, u / 2);
        		u /= 2; //更新下标
        	}
        }
        
        //插入x
        void add(int x){
        	++ idx; ++ k;
        	h[idx] = x;//赋值
        	ph[k] = idx;
        	hp[idx] = k;
        	up(idx);
        }
        
        //删除下标为k的节点
        void del(int u){
        	h[u] = h[idx];
        	idx -- ;
        	up(u);down(u);
        }
        
        //将下标为u的节点的值改为x
        void change(int k, int x){
        	h[u] = x;
        	up(u);down(u);
        }
        
        //初始化
        void init(){
        	//赋值
        	int x;
        	for(int i = 1; i <= n; ++ i){
        		cin >> x;
                ++ idx; ++ k;
        		h[idx] = x;
                hp[idx] = k;
                ph[k] = idx;
        	}
        	//建堆
        	//建堆O(n),i不必从n开始因为有n/2的元素本来就在最底层
        	for(int i = n / 2; i; -- i) down(i);
        }
        
        
        
    • 大根堆:

      • 与小根堆互逆不多赘述

posted on 2023-07-29 23:53  MoiLip  阅读(9)  评论(0编辑  收藏  举报

导航