数据结构

2.4并查集
[ 编辑]1. 知识讲解
1.无论怎么样还是在合并的时候先判断是否属于同一集合,有些题合并两次会错。  
2.一个节点的深度代表了该节点被移动合并的次数。  
3.Rank代表的是子树可能的最大高度,一般可以不写。  
4.带路径压缩的并查集合并和查询的时间复杂度略大于O(1)。  
5.带权值的多用位运算,循环多用取模,s[]是偏移量。  
6.表达式1,已知i和原root的关系及原root与现root的关系,求i与现root的关系  
7.表达式2,已知i和原rooti的关系及j和原rootj的关系,求原rooti与原rootj的关系,其中一个已并到另外一个上  
const int maxn=100;  
int n;  
int fa[maxn],s[maxn];  
void init()//初始化,每个元素形成一个单独的集合  
{   
         for (i=1;i<=n;i++)     
              fa[i]=i;  
}  
int find(int i)//查询i所在的集合  
{    
      if (fa[i]!=i)
         {    
            int tmp=fa[i];     
                fa[i]=find(fa[i]);     
                 s[i]=//表达式 1    
                  return fa[i];   
          }   
        return i;  
}  
bool Union(int i,int j)//将i所在的集合和j所在的集合合并  
{   
     int fi=find(i),fj=find(j);   
     if (fi==fj)    
         return 0;   
     fa[fi]=fj;   
     s[fi]=//表达式 2   
     return 1;  
}  

[ 编辑]2. 例题分析

Poj 2492 A Bug’s Life

? 题目大意

虫分为雌性雄性,不同性别的虫才会有接触。给出一些接触虫子的编号,问是否存在同性恋。

? 题目分析

Define s[i]=0代表i与父节点同性别。

表达式1:tmp为保存下的i原来的父节点。表达式2

S[i]=0 S[i]=s[tmp] H S[i]=0/1 s[j]=0/1 S[fi]=s[fj]^1  S[i]=1 S[i]=s[tmp]^1 LL S[i]=0/1 S[j]=1/0 S[fi]=s[fj]  

? 题目源码

#include<cstdio>  
#include<cstring>  
using namespace std;  
int fa[2005];  
bool s[2005];  
int Find(int pos)  
{   
    int tmpfa=fa[pos];//记录之前的父节点    
     if (fa[pos]==pos)    
         return pos;   
     fa[pos]=Find(fa[pos]);   
        if (!s[pos])//修改权值,第一个表达式     
           s[pos]=s[tmpfa];  
        else     
           s[pos]=!s[tmpfa];   
     return fa[pos];  
}   
bool Union(int i,int j)  
{   
      int fi=Find(i),fj=Find(j);   
       if (fi==fj&&s[i]==s[j])//父节点相同,且与父节点的关系相同,同性恋     
          return false;   
      if (fi!=fj){    
           fa[fi]=fj;    
           s[fi]=!(s[i]^s[j]);//修改权值,第2个表达式     
              return true;   
           }  
     }  
int main()  
{   
       int a,b,t,n,m,i,ca=1;   
       bool f;   
       scanf("%d",&t);   
       while (t--){    
             f=true;    
             scanf("%d%d",&n,&m);    
             for (i=1;i<=n;i++)     
             fa[i]=i;    
             memset(s,false,sizeof(s));    
             for (i=0;i<m;i++){     
                scanf("%d%d",&a,&b);     
                if (f)      
                   f=Union(a,b);    
             }    
         printf("Scenario #%d:\n%s bugs found!\n",ca++,f?"No suspicious":"Suspicious");    
         if (t!=0)     
             printf("\n");   
         }   
     return 0;  
}
2.5归并树
[ 编辑]●知识讲解

? 归并树利用类似线段树的树型结构记录合并排序的过程。 ? 以1 5 2 6 3 7为例: 把归并排序递归过程记录下来即是一棵归并树:

       [1 2 3 5 6 7]     [1 2 5]      [3 6 7]    [1 5] [2]    [6 3] [7]    [1][5]        [6][3]  

用对应的下标区间建线段树:(这里下标区间对应的是原数列)

           [1 6]      [1 3]      [4 6]   [1 2] [3]   [4 5][6]   [1][2]      [4][5]  

每次查找[l r]区间的第k大数时,在[1 2 3 4 5 6 7]这个有序的序列中二分所要找的数x,然后对应到线段树中去找[l r]中比x小的数有几个,即x的rank。由于线段树中任意区间对应到归并树中是有序的,所以在线段树中的某个区间查找比x小的数的个数也可以用二分在对应的归并树中找。这样一次查询的时间复杂度是log(n)^2。 要注意的是,多个x有相同的rank时,应该取最大的一个

1.建立归并树后我们得到了序列key[]的非降序排列,由于此时key[]内元素的rank是非递减的,因此key[]中属于指定区间[s,t]内的元素的rank也是非递减的,所以我们可以用二分法枚举key[]中的元素并求得它在[s,t]中的 rank值,直到该rank值和询问中的rank值相等;  2.那对于key[]中的某个元素val,如何求得它在指定区间[s,t]中的rank?这就要利用到刚建好的归并树:我们可以利用类似线段树的 query[s,t]操作找到所有属于[s,t]的子区间,然后累加val分别在这些子区间内的rank,得到的就是val在区间[s,t]中的 rank,注意到这和合并排序的合并过程一致;  3.由于属于子区间的元素的排序结果已经记录下来,所以val在子区间内的rank可以通过二分法得到。  

? 复杂度分析:三次二分操作

1.第一次二分:从已经排好序的n个数中做二分,选出一个数tmp,并用后面两次二分求出其在所求区间内的位序。  2.第二次二分:用类似线段树的方法,选出区间[x,y]中所包含的归并树中的有序片段。  3.第三次二分:求出第二次二分中得到的各个片断中tmp的位序,总位序就是各个位序之和。  于是每次询问的复杂度是O(log n * log n * log n)  

[ 编辑]●例题分析

poj2104

? 题目大意

给定一个序列a[1..n]和m个询问(s,t,rank)(1 <= n <= 100 000, 1 <= m <= 5 000),对于每个询问输出区间[s,t]中第rank小的值

? 题目分析

经典的k-th number问题,按以上的分析建立归并树解决问题。

? 题目源码

#include<cstdio>  #include<cstring>  #include<algorithm>  using namespace std;  const int maxn=100005;  struct node{   int l,r;  };//  node tree[maxn*5];  int n,m,num[maxn],merg[20][maxn];//merg的最大深度为线段树的最大深度  struct MergeTree{    int from,to,no;//查询from到to中第no小的值   void creat(int l,int r,int k,int deep)//创建归并树,deep为深度   {    tree[k].l=l,tree[k].r=r;    if (l==r){     merg[deep][l]=num[l];     return ;    }    int mid=(l+r)>>1;    creat(l,mid,2*k,deep+1);//递归创建    creat(mid+1,r,2*k+1,deep+1);    int i=l,j=mid+1,p=l;        while (i<=mid&&j<=r){//归并排序,记录下每个深度的排序序列     if (merg[deep+1][i]<merg[deep+1][j])      merg[deep][p++]=merg[deep+1][i++];     else      merg[deep][p++]=merg[deep+1][j++];    }    while (i<=mid)     merg[deep][p++]=merg[deep+1][i++];    while (j<=r)     merg[deep][p++]=merg[deep+1][j++];   }   int query(int k,int key,int deep)   {    if (tree[k].l>to||tree[k].r<from)     return 0;    if (tree[k].l>=from&&tree[k].r<=to)     return lower_bound(&merg[deep][tree[k].l],&merg[deep][tree[k].r]+1,key)-&merg[deep][tree[k].l];  // lower_bound返回不小于key的第一个位置,利用的二分查找.可自己写二分函数    return query(2*k,key,deep+1)+query(2*k+1,key,deep+1);//   }   int slove()//二分枚举所有的数,logn   {    no--;    int l=1,r=n,mid,ans;    while (l<r){     mid=(l+r+1)>>1;     ans=query(1,merg[1][mid],1);//     if (ans<=no)      l=mid;      else      r=mid-1;    }    return merg[1][l];   }  }MergeT;  int main()  {   while (scanf("%d%d",&n,&m)!=EOF){    for (int i=1;i<=n;i++)     scanf("%d",num+i);    MergeT.creat(1,n,1,1);//    while (m--){     scanf("%d%d%d",&MergeT.from,&MergeT.to,&MergeT.no);//     printf("%d\n",MergeT.slove());  //查询原序列中第MergeT.from个元素到MergeT.to个元素中第MergeT.no大的是多少    }   }   return 0;  }

霍夫曼树
[ 编辑]●知识精讲

霍夫曼树,也称最优树,是一种带权外路径长度最短的树。 相关概念为:

路径长度是指树中两个结点间路径上所含有的分支数目。   树的路径长度是指从树根到每一结点的路径长度之和。   带权路径长度是指结点的路径长度与该结点的权之积。  树的带权外路径长度是树中所有叶子结点的带权路径长度之和。  

设各叶子结点的路径长度为 lk,权为wk ,则该树的带权外路径长度为:

                 WPL = ∑ wk|lk 。   

则 WPL 最小的二叉树即为最优二叉树或霍夫曼树。

霍夫曼树中除叶子结点外,所有分支结点的度均为 2,叶子结点(外部结点)可看成是由分支结点(内部结点)组成的树扩充出来的,因此,霍夫曼树是一棵扩充二叉树。

霍夫曼树的构造:

1、把给定的 n 个权值集合的各结点均作为一棵树;   2、从这 n 棵树中选取两个权值最小的结点组成新树;  3、将这两个结点的权值之和作为新树树根的权值;  4、将这两个结点从 n 棵树中删去,把新树树根加入;  5、原来的 n 棵树减少为 n-1 棵树;  6、重复步骤 2 -- 5,直到 n=1 即为所求。  

例如:已知字符 A、B、C、D、E、F、G,使用频度分别为:0.25、0.1、0.15、0.06 、 0.3 、 0.05、0.09,试以此构造霍夫曼树。

                        

霍夫曼树的应用:

  1、最佳判定树;     2、霍夫曼编码。  

霍夫曼树,即最优二叉树,带权路径长度最小的二叉树,经常应用于数据压缩,霍夫曼编码就是基于霍夫曼树的一种压缩编码。霍夫曼编码是可变字长编码(VLC)的一种,完全依据字符出现概率来构造平均长度最短的码字。

霍夫曼编码:

                             

WPL霍夫曼编码 = 2.56

等长编码:

                 


WPL等长编码 = 3 利用霍夫曼编码可提高效率:( 3 - 2.56 ) / 3 ≈ 15%



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值