检索----二分,二叉查找,hash查找

来源:互联网 发布:ppt修改图表数据 编辑:程序博客网 时间:2024/05/10 09:32

查找的方法有很多,首先最简单的就是顺序查找,对于那种数据量不大的就可以使用这种方法查找~~~

二分检索:首先必须得有序才行,每次把查找的范围缩小一半,采用迭代的方法,设置一个上限hign和下限low,中间值为mid=(low+high)/2,然后用需要查找的值和mid对应的值相比较,比a[mid]大,则low=mid+1,否则:high=mid-1;直到找到或结束为止。模板:

low=0,high=n-1;while(low<=high){   mid=(low+high)/2;     if(a[mid]==value) {flag=1;pos=mid;}      if(a[mid]>value) high=mid-1;     else low=mid+1;} 

动态查找:二叉排序树(二叉查找树--Binary Sort Tree)

特点为:(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值; (2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值; (3)左、右子树也分别为二叉排序树;更有如果我们遍历二叉排序树的话,会得到一个有序的序列,所以称二叉排序树.

二叉排序的树的查找过程:若根结点的关键字值等于查找的关键字,成功;否则,若小于根结点的关键字值,递归查左子树.若大于根结点的关键字值,递归查右子树.若子树为空,查找不成功.

二叉排序树模板,来源于数据结构书:

class BSTNode{public:    void CLR(BSTNode *T) {T->lchild=T->rchild=NULL;}      int SearchBST(BSTNode *T,int value,BSTNode *father,BSTNode *&p)//查找操作     {   //其中指针p指向该数据元素的结点,并返回1,否则指针p指向查找路径上访问的最后一个结点并返回0,指针f指向T的双亲,初始调用值为NULL;         if(!T) {p=father;return 0;}         else if(T->data==value) {p=T;return 1;}//查找成功         else if(T->data>value) return SearchBST(T->lchild,value,T,p);//继续查找左子树         else return SearchBST(T->rchild,value,T,p);//继续查找右子树     }     void InsertBST(BSTNode *&T,int value)    {   BSTNode *p=NULL;         if(!SearchBST(T,value,NULL,p))//只有当树中不存在关键字的时候才进行插入操作         {   BSTNode *s=new BSTNode;            s->data=value;            CLR(s);            if(!p) T=s; //被插入结点作为*s的新的根结点             else if(p->data>value) p->lchild=s;//被插结点作为*s的左孩子             else p->rchild=s; //被插结点作为*s的右孩子         }    }     void Order(BSTNode *T)//中序遍历二叉排序树为一个有序序列     {   if(T)         {   Order(T->lchild);            printf("%d ",T->data);            Order(T->rchild);        }    }private:    int data;    struct BSTNode *lchild,*rchild; }BST;

例题:POJ 2418题目链接:http://poj.org/problem?id=2418

题目大意是:给定的一些树名对这些树名进行字典排序并输出所占的比例。分析:按照二叉排序树的规律,以字典序来排列这些字符串,由于要计算概率我们可以在类中添加一个计算每一个字符串出现的次数,然后在查找的过程中找到有与前面字符串相同时这个字符串出现的次数+1,并不进行插入操作,输出时只需按照中序遍历输出字符串和概率即可。

#include<iostream>#include<cstring>#include<cstdio>using namespace std;int num;class BSTNode{public:    void CLR(BSTNode *T)     {   T->lchild=T->rchild=NULL;        T->total=1;//不要赋值为0了     }      int SearchBST(BSTNode *T,char *value,BSTNode *father,BSTNode *&p)    {   if(!T) {p=father;return 0;}         else if(!strcmp(T->data,value)) {p=T;return 1;}        else if(strcmp(T->data,value)==1) return SearchBST(T->lchild,value,T,p);        else return SearchBST(T->rchild,value,T,p);    }     void InsertBST(BSTNode *&T,char *value)    {   BSTNode *p=NULL;         if(SearchBST(T,value,NULL,p)) p->total++;//注意了~~~~         else        {   BSTNode *s=new BSTNode;            strcpy(s->data,value);            CLR(s);            if(!p) T=s;             else if(strcmp(p->data,value)==1) p->lchild=s;            else p->rchild=s;         }    }     void Order(BSTNode *T)    {   if(T)         {   Order(T->lchild);            printf("%s %.4lf\n",T->data,100.0*(T->total)/num);            Order(T->rchild);        }    }private:    char data[40];    int total;    struct BSTNode *lchild,*rchild; }BST;int main(){   char value[50];    BSTNode *T=NULL;     num=0;    while(gets(value))    {   num++;        BST.InsertBST(T,value);        }    BST.Order(T);    return 0;} 

顺便提一下,这种没有结束标志的题目测试起来有点困难,那么你可以自己定义一下这个测试数据的组数,比方这个题目你就可以假设这个字符串的组数为29,在输入就可以看到结果了~~~
哈希查找:查找的时候我们可能希望不经过任何比较,一次存取就能得到所查记录,那么我们就必须保存记录的存储位置和他的关键字之间建立一个确定的关系f,使每个关键字和结构中一个唯一个存储位置相对应,所以在查找中我们只需根据这个对应关系f找到对应的K的像f(K),若结构中存在关键字和K的相等记录,则必定在f(K)的存储位置上,所以不需进行任何比较得到我们想要的结果,这个对应关系f为哈希函数。.

哈希函数的构造方法:(1)直接定址法;(2)数字分析;(3)平均取中法;(4)折叠法;(5)随机数法

(6)保留余数法:取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址:即:H(key)=key mod p(p<=m)(p可以取质数或不包含小于20的质因数的合数)

处理冲突的方法:(1)开放定址法,Hi=(H(key)+di)mod m (i=1,2,3..........k)(k<=m-1)其中H(key)为哈希函数,m为哈希表长,d为增量序列。(2)再哈希法;(3)链地址法等.....

哈希查找步骤:(1)用给定的哈希函数构造哈希表;(2)根据选择的冲突处理方法解决地址冲突;(3)在哈希表的基础上执行查找----给定K值,根据造表时设定的哈希函数求得哈希地址,若是表中此位置上没有记录,则说明查找不成功,否则比较关键字是否给定值相等,则说明查找成功,否则根据造表时设定的处理冲突的方法查找"下一个地址",直到哈希表中的某个位置为空或表中所填记录的关键字等于给定值时为止。

一个简单的利用hash思想来做的题目:HDU 1425(sort)题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1425,

#include<iostream>#include<cstring>#include<cstdio>using namespace std;const int MAX=1000010; #define CLR(arr,val) memset(arr,val,sizeof(arr))int n,m,value,Key[MAX],flag;int main(){   CLR(Key,0);    while(scanf("%d%d",&n,&m)!=EOF)    {   for(int i=0;i<n;i++)        {   scanf("%d",&value);            Key[value+500000]++;//保证存储的为正数          }         flag=1;        for(int i=MAX-1;i>=0&&m>=1;i--)            if(Key[i])             {   if(!flag) printf(" %d",i-500000);                if(flag) {printf("%d",i-500000);flag=0;}                m--;            }        printf("\n");    }     return 0;} 

hash处理冲突的方法:线性探测再散列技术。

即当H(k)位置已经有存储有元素时,依次探测H(k)=(H(k)+i) mod len,i=1,2,3......直到找到空的存储位置为止,其中len为数组的长度。

例题2:hdu 1496题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1496

题目大意:给你a,b,c,d四个正数,问你满足a*x1^2+b*x2^2+c*x3^2+d*x4^2=0等式成立的组数。类似百鸡百钱,可以移一下项变成:a*x1^2+b*x2^2=-c*x3^2-d*x4^2,这样就可以变成双重循环了。

#include<iostream>#include<cstring>#include<cstdio>using namespace std;const int MAX=2000010;#define CLR(arr,val) memset(arr,val,sizeof(arr))int a,b,c,d,x1,x2,x3,x4,hash[MAX];int main(){   while(scanf("%d%d%d%d",&a,&b,&c,&d)!=EOF)    {   int total=0;        if((a>0&&b>0&&c>0&&d>0)||(a<0&&b<0&&c<0&&d<0)) {printf("0\n");continue;}        CLR(hash,0);//开始写在上面的if语句前面TLE,改到这里就有只有213ms了~~~         for(x1=1;x1<=100;x1++)            for(x2=1;x2<=100;x2++)                hash[a*x1*x1+b*x2*x2+1000000]++;//和上题一样数组下标不能为负值,要把它变为正值,a,b最小为-50,x1*x1最大为10000,所以其最小值为-1000000         for(x3=1;x3<=100;x3++)            for(x4=1;x4<=100;x4++)                total+=hash[-c*x3*x3-d*x4*x4+1000000];          printf("%d\n",16*total);//x1,x2,x3,x4可以为负数,组合有2*2*2*2=16种;     }    return 0;} 

数组一开大,初始化时间就会过长,现在我们得考虑怎样使用较小的数组来处理,以减少初始化时间。那么得用hash函数处理冲突的方法了,思考中~~~

#include<iostream>#include<cstring>#include<cstdio>using namespace std;const int MAX=100001;//一个合适的质数 #define CLR(arr,val) memset(arr,val,sizeof(arr))#define For(i,start,end) for(int i=start;i<=end;i++)int a,b,c,d,total;class HASH{public:    int Hash(int value)//求哈希地址    {   int pos=value%MAX;        if(pos<0) pos+=MAX;         while(Key[pos]!=value&&visit[pos])//该位置没有存储元素且存储的元素与value值不同,产生冲突,可做模板             pos=(pos+1)%MAX;         return pos;    }    void Create()    {   For(x1,1,100)            For(x2,1,100)            {   int value=a*x1*x1+b*x2*x2;                int pos=Hash(value);//求得哈希地址                 Key[pos]=value;//记录pos位存储的元素为value                 visit[pos]++;//记录是否已经存储有元素及次数             }    }       void Search()    {   For(x3,1,100)            For(x4,1,100)            {   int pos=Hash(-c*x3*x3-d*x4*x4);                total+=visit[pos];             }    }    void Init(){CLR(visit,0);total=0;}private:    int visit[MAX],Key[MAX];//用Key[i]来保存i位置的关键码,H[i]记录i是否存储有元素   };int main(){   HASH H;    while(scanf("%d%d%d%d",&a,&b,&c,&d)!=EOF)    {   if((a>0&&b>0&&c>0&&d>0)||(a<0&&b<0&&c<0&&d<0)) {printf("0\n");continue;}        H.Init();        H.Create();        H.Search();        printf("%d\n",16*total);    }    return 0;} 

现在又有一个问题,哈希表的长度如何取?因为要是取的太大的话,会浪费内存空间,但是取得太小的话又会增加冲突的概率,体现不了效率。(其实开大开小影响的是运行的时间,上例中开100001的时候,运行的时间为39ms,消耗的内存为1000k,当开10001时,运行的时间就变为了390ms,但是内存为300k),即MAX的值,我取100001可以过,取别的值呢,当然只要合理也可以,问题什么样的长度才为合理呢~~~(http://blog.csdn.net/qll125596718/article/details/6997850),哈希表长度的大小可能取值为:

17,27,79,763,331,673,1361,2729,5471,10949,21911,43853,87719,175447,350899,701819,1403641,2807303,5614657等等~~~~~
例题3:poj 3349(相同的雪花)题目链接:http://poj.org/problem?id=3349
WA,WA~~~~老是WA,都不知道错在哪~先贴下,最迟明天,一定要A了!!!

#include<iostream>#include<cstring>#include<cstdio>using namespace std;const int MAX=100010;const int MAX2=100001;#define CLR(arr,val) memset(arr,val,sizeof(arr))int n,m;struct node{    int len[6],sum;  }snow[MAX];bool Same(node &a,node &b) {   for(int j,i=0;i<6;i++)        if(a.len[0]==b.len[i])//找到开始位置        {   for(j=1;j<6;j++) //顺序                if(a.len[j]!=b.len[(i+j)%6]) break;           if(j==6) return true;           for(j=1;j<6;j++) //逆序                if(a.len[6-j]!=b.len[(i+j)%6]) break;           if(j==6) return true;       }    return false;}class HASH{public:       int Hash(int value)    {   int pos=value%MAX2;         while(Key[pos]!=value&&visit[pos])            pos=(pos+1)%MAX2;         return pos;    }    void Create()    {   for(int i=0;i<n;i++)        {   int pos=Hash(snow[i].sum);            visit[pos]++;            Key[pos]=snow[i].sum;           }    }    void Search()    {   int flag=0;        for(int i=0;i<MAX2;i++)            for(int j=0;j<visit[i];j++)                for(int k=j+1;k<visit[i];k++)                    if(Same(snow[j],snow[k])) {flag=1;break;}        if(flag) printf("Twin snowflakes found.\n");        else printf("No two snowflakes are alike.\n");     }    void Init(){CLR(visit,0);}      private:    int visit[MAX2],Key[MAX2];    }H;int main(){   scanf("%d",&n);     for(int i=0;i<n;i++)    {   snow[i].sum=0;        for(int j=0;j<6;j++)        {   scanf("%d",&snow[i].len[j]);            snow[i].sum+=snow[i].len[j];         }    }    H.Init();    H.Create();    H.Search();    return 0;} 

哈哈~~~想出来了,早上睡在床上一想,才知道是Search语句中的if语句出错了,Same(snow[j],snow[k])我没有保存在i位置的j,k位置雪花,所以我一直比较的就是snow[0],snow[1]......,肯定有错误啦,所以一大早就起来保存了相对于i位置的j和k所指的雪花,果然A了,我是定义的二维数组。

#include<iostream>#include<cstring>#include<cstdio>using namespace std;const int MAX=100010;const int MAX2=100001;#define CLR(arr,val) memset(arr,val,sizeof(arr))int n,m;struct node{    int len[6],sum;  }snow[MAX];bool Same(node &a,node &b) {   for(int j,i=0;i<6;i++)        if(a.len[0]==b.len[i])//找到开始位置        {   for(j=1;j<6;j++) //顺序                if(a.len[j]!=b.len[(i+j)%6]) break;           if(j==6) return true;           for(j=1;j<6;j++) //逆序                if(a.len[6-j]!=b.len[(i+j)%6]) break;           if(j==6) return true;       }    return false;}class HASH{public:       int Hash(int value)    {   int pos=value%MAX2;         while(Key[pos]!=value&&visit[pos])            pos=(pos+1)%MAX2;         return pos;    }    void Create()    {   for(int i=0;i<n;i++)        {   int pos=Hash(snow[i].sum);            hash[pos][visit[pos]]=snow[i];            Key[pos]=snow[i].sum;            visit[pos]++;           }    }    int Search()    {   int flag=0;        for(int i=0;i<MAX2;i++)            for(int j=0;j<visit[i];j++)                for(int k=j+1;k<visit[i];k++)                    if(Same(hash[i][j],hash[i][k])) {flag=1;break;}        if(flag) return 1;        return 0;    }    void Init(){CLR(visit,0);}      private:    int visit[MAX2],Key[MAX2];    node hash[MAX2][10];    }H;int main(){   scanf("%d",&n);     for(int i=0;i<n;i++)    {   snow[i].sum=0;        for(int j=0;j<6;j++)        {   scanf("%d",&snow[i].len[j]);            snow[i].sum+=snow[i].len[j];         }    }    H.Init();    H.Create();    if(H.Search()) printf("Twin snowflakes found.\n");    else printf("No two snowflakes are alike.\n");    return 0;} 

定义了二维数组的话,就像邻接表一样,可以利用向量保存,拿nyist 138(找球号来说):http://acm.nyist.net/JudgeOnline/problem.php?pid=138,题目较简单,讨论中说可以用位运算过,没试过,那个不懂,下面是向量代码,用结构体的TLE了n次~~~~~~~~~结构体应该可以的抓狂,用结构体写出来了再贴下。

#include<iostream>#include<cstring>#include<cstdio>#include<vector>using namespace std;const int MAX=10001;#define CLR(arr,val) memset(arr,val,sizeof(arr))vector<int> v[MAX];int num,n,m;char s[8];int main(){   scanf("%d",&n);    getchar();    while(n--)    {   scanf("%s %d",s,&m);        if(s[0]=='A')        {   for(int i=0;i<m;i++)            {   scanf("%d",&num);                v[num%MAX].push_back(num);            }        }         else if(s[0]=='Q')        {   for(int i=0;i<m;i++)            {   scanf("%d",&num);                int flag=0;                for(int j=0;j<v[num%MAX].size();j++)                    if(v[num%MAX][j]==num) {flag=1;break;}                if(flag) puts("YES");                else puts("NO");                 }           }    }    return 0;}

可以用向量就可以用邻接矩阵,RE了n次,数组开小了~~~~~代码:

#include<iostream>#include<cstring>#include<cstdio>using namespace std;const int MAX=1000001; #define CLR(arr,val) memset(arr,val,sizeof(arr))int num,n,m;char s[8];struct ArcNode{      void Link(int value)           {   int pos=value%MAX;        next[sum]=prior[pos];                   data[sum]=value;                   prior[pos]=sum++;           }         void Init(){CLR(prior,-1);sum=0;}       int prior[MAX],next[MAX],data[MAX],sum;}Arc;int Search(int value){   int pos=value%MAX;    for(int j=Arc.prior[pos];j!=-1;j=Arc.next[j])        if(Arc.data[j]==value) {return 1;break;}    return 0; }int main(){   scanf("%d",&n);    getchar();    Arc.Init();    while(n--)    {   scanf("%s %d",s,&m);        if(s[0]=='A')        {   for(int i=0;i<m;i++)            {   scanf("%d",&num);                Arc.Link(num);            }        }         else if(s[0]=='Q')        {   for(int i=0;i<m;i++)            {   scanf("%d",&num);                if(Search(num)) puts("YES");                else puts("NO");            }           }    }      return 0;}

贴一下nyist 130题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=130的标准程序

#include<cstring> #include<string> #include<cstdio> #include<vector> #include<algorithm> #include<numeric> using namespace std; template<class KeyType,int MaxHash> struct HashFunction {     int operator()(const KeyType& key)     {   int h=key%MaxHash;         if(h<0) h+=MaxHash;         return h;     } }; template<int MaxHash> struct HashFunction<std::string,MaxHash>{     int operator()(const std::string& key)     {   unsigned long h=0,g;         std::string::const_iterator it=key.begin();         while(it!=key.end())         {   h=(h<<4)+*it++;             g=h&0xf0000000L;             if(g) h^=g>>24;             h&=~g;         }         return h%MaxHash;     } }; template<class KeyType,class ValueType,int KeyContain,int MaxHash,class Hash=HashFunction<KeyType,MaxHash> >  class HashMap {   public:        HashMap():hash(),top(0){}       ValueType& operator[](const KeyType& key)        {   int h=hash(key);               int pos=head[h];               for(;pos!=0;pos=next[pos])               {   if(keys[pos]==key)                           return values[pos];          }         next[++top]=head[h];         head[h]=top;         keys[top]=key;                 values[top]=ValueType();               return values[top];      }     bool Find(const KeyType& key)        {   int h=hash(key);              int pos=head[h];                for(;pos!=0;pos=next[pos])               {   if(keys[pos]==key)                           return true;               }            return false;        }       void Clear()       {   memset(head,0,sizeof(head));               memset(next,0,(top+1)*sizeof(int));                top=0;       }    private:        Hash hash;        int head[MaxHash];         int next[KeyContain];        KeyType keys[KeyContain];        ValueType values[KeyContain];         int top; }; struct Snow {         Snow(){}       Snow(int dd[])       {   copy(dd,dd+6,data);                copy(dd,dd+6,sorted);                 sort(sorted,sorted+6);        }         int data[6];        int sorted[6]; }; bool eq1(const int data1[],int s1,const int data2[],int s2) {   for(int i=0;i!=6;i++)                if(data1[(s1+i)%6]!=data2[(s2+i)%6]) return false;        return true; } bool eq2(const int data1[],int s1,const int data2[],int s2) {   for(int i=0;i!=6;i++)                if(data1[(s1+i)%6]!=data2[(s2-i+6)%6]) return false;     return true; } bool operator==(const Snow& s1,const Snow& s2) {   for(int i=0;i!=6;i++)                 if(eq1(s1.data,i,s2.data,0)||eq2(s1.data,i,s2.data,0)) return true;        return false;    } template<int MaxHash> struct HashFunction<Snow,MaxHash> {   int operator()(const Snow& snow)     {   unsigned int h=0;          for(int i=0;i!=6;i++)                    h^=(snow.sorted[i]<<i);                     h%=MaxHash;               return h;         } }; HashMap<Snow,bool,100010,7345123> h; int main() {   int T;       scanf("%d",&T);       while(T--)        {   bool found=false;                int n;                int snow[6];                 scanf("%d",&n);               while(n--)               {   for(int i=0;i!=6;i++)                                          scanf("%d",&snow[i]);                               if(found) continue;                         Snow s=Snow(snow);                        bool& hs=h[s];                         if(hs==true)                        {   puts("Twin snowflakes found.");                                found=true;                         }                       else hs=true;                }               if(!found) puts("No two snowflakes are alike.");                h.Clear();        }    return 0; }

上面几个题目都只是涉及数组的,但对于字符串能不能同样使用hash算法来查找呢?肯定的,查找的方法有:Rabin-Karp算法和ELFHash算法,

对于ELFHash算法,它是对字符串的散列。对于节省时间上没有什么优势,但是能节省不少空间,说实话我也没有看太懂,只是这个模板基本固定,只用来套用即可,具体功能为:将一个字符串的数组中的每个元素依次按前四位与上一个元素的低四位相与,组成一个长整形,如果长整的高四位大于零,那么就将它折回再与长整的低四位相异或,这样最后得到的长整对HASH表长取余,得到在HASH中的位置。 模板如下:(转:http://blog.chinaunix.net/uid-24683784-id-3061386.html)

例题1:hdu 1800题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1800,我反正是看不懂题目,蒙~~~数据:提示求出输入的多组数据中最大值出现的次数,map可过,只是记得用scanf输入即可,现在主要是利用ELFHash算法求解,同数组查找一样,需要找到字符串的哈希地址,那么得利用ELFHash算法来求了,用ELFHash算法求出地址后就好解决了,然后只需记录出现的每个地址出现的次数,找出其中最大值出现的次数输出即可,具体见代码~~~~哦,还有一个需要注意的地方就是记得要去掉前导0。

#include<iostream>#include<cstring>#include<cstdio>using namespace std;const int MAX=10001;#define CLR(arr,val) memset(arr,val,sizeof(arr))char s[50]; int n,maxt; class HASH{public:          int ELFhash(char *str)//求字符串的哈希地址     {   unsigned long hash=0,g;        while(*str)        {   hash=(hash<<4)+*str++;            g=hash&0xf0000000L;            if(g) hash^=g>>24;            hash&=~g;        }        return hash;    }    int Hash(int add)    {   int pos=add%MAX;        while(Key[pos]!=add&&Key[pos]!=-1)            pos=(pos+1)%MAX;            return pos;    }    void Maximum(char *str)    {   while(*str=='0') str++; //删除前导0         int add=ELFhash(str); //求出字符串的哈希地址        int pos=Hash(add);        visit[pos]++;        if(Key[pos]==-1) Key[pos]=add;        else if(visit[pos]>maxt) maxt=visit[pos];      }    void Init(){CLR(Key,-1);CLR(visit,0);}private:    int visit[MAX],Key[MAX];    }H;      int main(){   while(scanf("%d",&n)!=EOF)    {   H.Init();        maxt=1;        for(int i=0;i<n;i++)        {   scanf("%s",s);            H.Maximum(s);        }        printf("%d\n",maxt);    }    return 0;}

看不懂ELFhash中的内容没关系,只需要知道这个函数的作用是什么作用即可,它的作用就是求取字符串的哈希地址。

Rabin-Karp算法:直接转到一个链接上吧~~~~http://archive.cnblogs.com/a/1212755/,这个上面写的比较的详细,我是看到这个头都大了,直接用模板算了,不管他怎么来的~~~
百练的有个题目直接说了用这个算法,可以看下:http://poj.grids.cn/practice/4042/

涉及到hash算法的题目有:poj 1200(Rabin-Karp算法),1635,1690,1733,1840,1971,2002,2503,2549,3320,3349,3504等,HDU 1043,1228,1425,1496,1800,2523,2648,3833等等


 


原创粉丝点击