算法导论第十一(11)章散列(Hash)表知识点梳理及详细课后答案

11.1直接寻址表

关键字集合U = { 0, 1, ..., m - 1 },实际的关键字集合K。
用一个数组T[0..m - 1],其中每个位置对应U中的一个关键字k。
把关键字k映射到槽T(k)上的过程称为散列。
散表表仅支持INSERT、SEARCH、DELETE操作。
11.1-1假设一动态集合S用一个长度为m的直接寻址表T表示。请给出一个查找S中最大元素的过程。你所给的过程在最坏情况下的运行时间是多少?
查找指定元素所用时间为O(1),如果找表中最大值,那么最坏情况下需要循环遍历散列表中所有有效值以确定最大值。运行时间为O(m).
[cpp]  view plain  copy
  1. struct Hash* direct_address_search(struct Hash *T[], int key)  
  2. {  
  3.     int Max=0;  
  4.     while (key!=MAX)//MAX表示散列表容量最大值  
  5.     {  
  6.         if (T[key]!=NULL&&Max<T[key]->key)  
  7.         {  
  8.              Max=T[key]->key;          
  9.         }  
  10.         key++;//key既可以表示直接寻址表T的下标,也可以表示关键字  
  11.     }  
  12. }  
11.1-2位向量是一个仅包含0和1的数组。长度为m的位向量所占空间要比包含m个指针的数组少得多,请说明如何用一个位向量表示一个包含不同元素(无卫星数据)的动态集合。字典操作的运行时间应为O(1).
核心思想:如果插入元素x,那么把位向量的第x位设置为1,否则为0.  如果删除元素x,那么把位向量的第x位设置为0。
[cpp]  view plain  copy
  1. #include <stdio.h>    
  2. #include <stdlib.h>   
  3. #define INT_BIT 32    
  4. typedef struct {    
  5.     unsigned int *table;    
  6.     int size;    
  7. } BitMap;    
  8.   
  9. BitMap * bitmap_create(int max)    
  10. {    
  11.     BitMap *bitmap = (BitMap *)malloc(sizeof(BitMap));    
  12.     bitmap->size = max / INT_BIT + 1;   
  13.     bitmap->table =(unsigned int *) calloc(sizeof(int), bitmap->size);    
  14.     return bitmap;    
  15. }    
  16.   
  17. void bitmap_insert(BitMap *bitmap, int key)    
  18. {    
  19.     bitmap->table[key / INT_BIT] = bitmap->table[key / INT_BIT] |(1 << (key % INT_BIT));    
  20. }    
  21.   
  22. void bitmap_delete(BitMap *bitmap, int key)    
  23. {    
  24.     bitmap->table[key / INT_BIT] &= ~(1 << (key % INT_BIT));    
  25. }    
  26.   
  27. int bitmap_search(BitMap *bitmap, int key)    
  28. {    
  29.     return bitmap->table[key / INT_BIT] & (1 << (key % INT_BIT));    
  30. }    
  31.   
  32. void bitmap_print(BitMap *bitmap)    
  33. {    
  34.     printf("-----\n");    
  35.     int i;    
  36.     for (i = 0; i < bitmap->size; i++)    
  37.         if (bitmap->table[i] != 0)    
  38.             printf("%d: %d\n ", i, bitmap->table[i]);    
  39.         printf("-----\n");    
  40. }    
  41.   
  42. int main(void)    
  43. {    
  44.     BitMap *bitmap = bitmap_create(1024);    
  45.     bitmap_insert(bitmap, 15);    
  46.     bitmap_insert(bitmap, 10);  
  47.     bitmap_insert(bitmap, 520);    
  48.     bitmap_insert(bitmap, 900);  
  49.     bitmap_delete(bitmap, 10);  
  50.     bitmap_print(bitmap);     
  51.     printf("%d\n", bitmap_search(bitmap, 68));    
  52.     printf("%d\n", bitmap_search(bitmap, 520));    
  53.     return 1;    
  54. }  
参考资料来自:
直接寻址表的位向量表示
11.1-3试说明如何实现一个直接寻址表,表中各元素的关键字不必都不相同,且各元素可以有卫星数据。所有三种字典操作(INSERT,DELETE和SEARCH)的运行时间应为O(1)
(不要忘记DELETE要处理的是被删除对象的指针变量,而不是关键字)。
[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #define LEN sizeof(struct Hash)  
  4. #define MAX 6  
  5.    
  6.   struct Hash{  
  7.      int key;  
  8.      int satellite;  
  9.  }Data;                               //元素结构体,有关键字和卫星数据  
  10.    
  11.  struct Hash* direct_address_search(struct Hash *T[], int key);        //散列表查找操作  
  12.    
  13.  void direct_address_insert(struct Hash *T[], struct Hash *x);         //散列表插入操作  
  14.    
  15.  void direct_address_delete(struct Hash *T[], struct Hash *x);         //散列表删除操作  
  16.    
  17.  void print_table(struct Hash *T[]);                            //打印散列表(为了方便查看删除操作后,散列表的变化)  
  18.    
  19.  int main(){  
  20.      int i, key, satellite;  
  21.      struct Hash *data[MAX];  
  22.      struct Hash *d;  
  23.      for(i = 0; i < MAX; i++){  
  24.          data[i] =new struct Hash [LEN];  
  25.          data[i] = NULL;  
  26.      }  
  27.        
  28.      for(i = 0; i <= 3; i++){  
  29.          d = new struct Hash[LEN];  
  30.         printf("Input the key_value:\n");  
  31.          scanf("%d", &key);  
  32.          printf("Input the satellite:\n");  
  33.          scanf("%d", &satellite);  
  34.          d->key = key;  
  35.          d->satellite = satellite;  
  36.          direct_address_insert(data, d);  
  37.      }  
  38.      print_table(data);  
  39.      printf("请输入待查找的元素\n");  
  40.      scanf("%d", &key);  
  41.      d = direct_address_search(data, key);  
  42.      if (d)  
  43.      {  
  44.          printf("the key is %d, and its satellite is %d\n", d->key, d->satellite);  
  45.      }   
  46.      else  
  47.      {  
  48.          printf("没有找到\n");  
  49.      }  
  50.      if (d)  
  51.      {  
  52.          direct_address_delete(data, d);  
  53.      }  
  54.       print_table(data);  
  55.      delete d;  
  56.     for(i = 0; i < MAX; i++)  
  57.          delete data[i];  
  58.     print_table(data);  
  59.      return 0;  
  60.  }  
  61.    
  62.  //直接返回下标为key的元素  
  63.  struct Hash* direct_address_search(struct Hash *T[], int key){  
  64.      return T[key];  
  65.  }  
  66.    
  67.   
  68.  //直接将元素插入下标key的位置里  
  69.    
  70.  void direct_address_insert(struct Hash *T[], struct Hash *x){  
  71.      T[x->key] = x;  
  72.  }  
  73.    
  74.   
  75.   // 将要删除的元素所在的位置指向空指针  
  76.    
  77.  void direct_address_delete(struct Hash *T[], struct Hash *x){  
  78.      T[x->key] = NULL;  
  79.  }  
  80.    
  81.   
  82.  //打印直接寻址表  
  83.   
  84.  void print_table(struct Hash *T[]){  
  85.      int i;   
  86.      for(i = 0; i < MAX; i++){  
  87.         if(T[i] != NULL){  
  88.              printf("key is %d, and its satellite is %d\n", T[i]->key, T[i]->satellite);  
  89.          }  
  90.      }  
  91.  }  
参考资料来自: 直接寻址表的字典操作
11.1-4我们希望在一个非常大的数组上,通过利用直接寻址的方式来实现一个字典。开始时,该数组中可能包含一些无用信息,但要对整个数组进行初始化是不太实际的,因为该数组的规模太大。请给出在大数组上实现直接寻址字典的方案。每个存储对象占用O(1)空间;SEARCH,INSERT和DELETE操作的时间均为O(1);并且对数据结构初始化的时间为O(1).(提示:可以利用一个附加数组,处理方式类似于栈,其大小等于实际存储在字典中的关键字数目,以帮助确定大数组中某个给定的项是否有效)
思路:我们用T表示大数组,用S表示含有所有有效数据的数组作为辅助栈。T的关键字作为下标,而数组T的值为数组S的下标,相反地,数组S的值为关键字,同时也是数组T的下标。这是一种循环形式 。设有关键字x,如果T[x]=y,那么S[y]=x.
[cpp]  view plain  copy
  1. #include <iostream>  
  2. using namespace std;  
  3. #define MAX 100  
  4. #define LEN sizeof(struct hash)  
  5. struct hash{  
  6.     int key;  
  7. };  
  8. struct Stack  
  9. {  
  10.     int STack[MAX];  
  11.     int top;  
  12. };   
  13. void Init(struct Stack&s)  
  14. {  
  15.     s.top=-1;  
  16. }  
  17. struct hash * search(struct hash Hash[],struct Stack&s,int k)  
  18. {  
  19.     if (Hash[k].key>=0&&Hash[k].key<=s.top&&s.STack[Hash[k].key]==k)  
  20.     {  
  21.         cout<<"已经找到"<<endl;  
  22.         return Hash+k;  
  23.     }  
  24.     else  
  25.     {  
  26.         cout<<"没有找到"<<endl;  
  27.         return NULL;  
  28.     }  
  29. }  
  30. void insert(struct hash Hash[],struct Stack&s,struct hash *x)  
  31. {  
  32.    int k=x->key;  
  33.    if (search(Hash,s,k))  
  34.    {  
  35.        cout<<"待插入的数据已经在散列表中!"<<endl;  
  36.        return ;  
  37.    }  
  38.    else  
  39.    {  
  40.        s.top++;  
  41.        s.STack[s.top]=k;  
  42.        Hash[k].key=s.top;  
  43.    }  
  44. }  
  45. void Delete(struct hash Hash[],struct Stack&s,int k)  
  46. {  
  47.     struct hash *p=search(Hash,s,k);  
  48.     if (!p)  
  49.     {  
  50.         cout<<"待删除的数据不在散列表中,无法删除"<<endl;  
  51.         return;  
  52.     }  
  53.      s.STack[p->key]=s.STack[s.top];  
  54.      Hash[s.STack[p->key]].key=p->key;  
  55.      p->key=0;  
  56.      s.top--;  
  57. }  
  58. void Print(struct Stack&s)    
  59. {    
  60.     int i;    
  61.     if (s.top==-1)  
  62.     {  
  63.         cout<<"无数据可打印!"<<endl;  
  64.         return ;  
  65.     }  
  66.     for(i = 0; i <= s.top; i++)    
  67.         cout<<s.STack[i]<<' ';    
  68.     cout<<endl;    
  69. }   
  70. void main()  
  71. {  
  72.     struct Stack s;  
  73.     struct hash Hash[10000],*y;  
  74.     Init(s);  
  75.     char str;  
  76.     int k=0;  
  77.     while (1)  
  78.     {  
  79.         cout<<"A是插入B是删除C是查找D是打印E是退出"<<endl;  
  80.         cin>>str;  
  81.         if (str=='A')  
  82.         {  
  83.             y=new struct hash[LEN];  
  84.             cin>>y->key;  
  85.             insert(Hash,s,y);  
  86.         }  
  87.         if (str=='B')  
  88.         {  
  89.             cin>>k;  
  90.             Delete(Hash,s,k);  
  91.         }  
  92.         if (str=='C')  
  93.         {  
  94.             cin>>k;  
  95.             search(Hash,s,k);  
  96.         }  
  97.         if (str=='D')  
  98.         {  
  99.             Print(s);  
  100.         }  
  101.         if (str=='E')  
  102.         {  
  103.             break;  
  104.         }  
  105.     }  
  106. }  
11.2散列表
多个关键字映射到同一个数组下标位置称为碰撞。
好的散列函数应使每个关键字都等可能地散列到m个槽位中。
链接法散列一次查找时间为O(1+a)  a=n/m.删除和插入的时间在下面练习中。

11.2-1假设用一个散列函数h将n个不同的关键字散列到一个长度为m的数组T中。假设采用的是简单均匀散列,那么期望的冲突数是多少?更准确地,集合{{k,l}:k≠l,且h(k)=h(l)}基的期望值是多少?
书中已经给出分析:E[X(kl)]=1/m
那么总的期望应该是E[∑∑E[X(kl)]]=∑∑1/m=n(n-1)/2m

11.2-2 对于一个用链接法解决冲突的散列表,说明将关键字5,28,19,15,20,33,12,17,10,插入到该表中的过程。设该表中有9个槽位,并设其散列函数为h(k)=k mod 9.

[cpp]  view plain  copy
  1. //11.2-2  
  2. #include <iostream>  
  3. using namespace std;  
  4. #define MAX 10  //槽的个数  
  5. #define LEN sizeof(struct list)  
  6. struct list*head=NULL;  
  7. struct list  
  8. {  
  9.     int key;  
  10.     struct list*next;  
  11.     struct list*prev;  
  12. }slot[MAX];  
  13. int hash(int y)  
  14. {  
  15.     int i=y%(MAX-1);  
  16.     return i;  
  17. }  
  18. void Init(struct list *slot[])//初始化结构体数组  
  19. {  
  20.     for (int i=0;i<MAX;i++)  
  21.     {  
  22.         slot[i]=NULL;  
  23.     }  
  24. }  
  25. //插入数据  
  26. struct list*insert(struct list *slot[],struct list*x,int a[],int j)  
  27. {  
  28.     x=new struct list[LEN];  
  29.     x->key=a[j];  
  30.     int i=hash(x->key);  
  31.     x->next=slot[i];  
  32.     if (slot[i]!=NULL)  
  33.     {  
  34.         slot[i]->prev=x;  
  35.     }  
  36.     slot[i]=x;  
  37.     x->prev=NULL;  
  38.     return slot[i];  
  39. }  
  40. void Print(struct list *slot[])  
  41. {  
  42.     int i=0;  
  43.     while (i!=MAX)  
  44.     {  
  45.         cout<<"第"<<i<<"个槽:"<<" ";  
  46.         if (!slot[i])  
  47.         {  
  48.             cout<<"NULL";  
  49.         }  
  50.         while (slot[i])  
  51.         {  
  52.             cout<<slot[i]->key;  
  53.             slot[i]=slot[i]->next;  
  54.             if (slot[i])  
  55.             {  
  56.                 cout<<"->";  
  57.             }  
  58.         }  
  59.         cout<<endl;  
  60.         i++;  
  61.     }  
  62. }  
  63. void main()  
  64. {  
  65.     struct list *slot[10*MAX],*x;  
  66.     int a[9]={5,28,19,15,20,33,12,17,10};  
  67.     Init(slot);  
  68.     int i=0;  
  69.     while (i!=9)  
  70.     {  
  71.         insert(slot,x,a,i);  
  72.         i++;  
  73.     }  
  74.     Print(slot);  
  75. }  

11.2-3 Marley教授做了这样一个假设,即如果将链模式改动一下,使得每个链表都能保持已排好序的顺序,散列的性能就可以有较大的提高。 Marley教授的改动对成功查找,不成功查找,插入和删除操作的运行时间有何影响?

对于一个总共有n个元素,m个槽的散列表来说,平均存储元素数为a=n/m。设Marley教授方法的所有排列都是非降序的。
查找操作:普通链接模式:由于插入时是无序的,所以查找时,成功的查找需要O(1+a1)时间(a1<a)。不成功查找也需要O(1+a)时间,因为遍历链表的时间是不稳定的。
                  有序链接模式:由于插入时是有序的,所以一次成功的查找时,一次成功查找平均需要O(1+a/2)时间。一次不成功的查找,平均时间也是O(1+a/2)。因为在查找过程中,都仅仅需要找到第一个大于等于k的元素为止,不需要遍历整个链表。假设链表中有序元素从1,2,....a依次排列,那么查找链表k元素平均时间就是O((1+a)/2)=O(a/2).所以总的时间为O(1+a/2)。所以查找时间减半了。
插入操作:普通链接模式:由于插入是不按照顺序的。所以只需要在表头插入即可。需要时间为O(1).
                  有序链接模式:由于插入是需要按照顺序的。所以要先找到插入位置。那么需要时间为O(1+a/2)。因为首先找到小于k的结点A,并且A的下一个结点大于k,然后插入到A的后面。所以插入操作的时间增加了。
删除操作:普通链接模式:利用双向链表仅用O(1)时间删除。但是删除前做一个元素是否存在的判断,那么就需要调用查找函数了。这样就需要O(1+a)时间了。
                 有序链接模式:利用双向链表仅用O(1)时间删除。如果同上一样要做判断,那么总时间就是O(1+a/2).所以删除操作时间减半了。
总结:查找与删除操作时间减半,插入操作时间增加了。

11.2-4 说明在散列表内部,如何通过将所有未占用的槽位链接成一个自由链表,来分配和释放元素所占的存储空间。假定一个槽位可以存储一个标志,一个元素加上一个或两个指针。所有的字典和自由链表操作均应具有O(1)的期望运行时间。该自由链表需要是双向链表吗?或者,是不是单链表就足够了呢?
        首先初始化一个自由表,该表能容纳散列表所有元素。然后和第十章链表的释放和分配内存类似,只不过由于要解决碰撞问题,散列需要一个指针数组来表示。对于这个自由链表需要用双向链表来提高插入,删除操作速度。
代码如下:
[cpp]  view plain  copy
  1. //11.2-4 有自由表的散列表,自由表用来分配和释放内存的。  
  2. #include <iostream>  
  3. using namespace std;  
  4. #define MAX 13  //槽的个数m  
  5. #define max 100 //整个散列表的元素个数n  
  6. #define LEN sizeof(struct list)  
  7. struct list*Free=NULL;  
  8. struct list * Allocate_object(struct list *x);//数组next为自由数组表  
  9. void free_object(struct list *x);  
  10. struct list  
  11. {  
  12.     int key;  
  13.     int flag;  
  14.     struct list*next;  
  15.     struct list*prev;  
  16. };  
  17. int hash(int y)  
  18. {  
  19.     return y%MAX;  
  20. }  
  21. void Init(struct list *slot[])  
  22. {  
  23.     struct list *p,*p1;  
  24.     for (int i=0;i<MAX;i++)//指针数组初始化  
  25.     {  
  26.         slot[i]=NULL;  
  27.     }  
  28.     for ( i=0;i<max;i++)//初始化自由表,该自由表也是一个双向链表。  
  29.     {//自由表每个结点都是一个槽位,一个槽位包含1个key,1个flag和2个指针  
  30.         p=new struct list[LEN];  
  31.         p->flag=0;//0表示该结点无数据  
  32.         if (Free==NULL)  
  33.         {  
  34.             p1=Free=p;  
  35.         }  
  36.         else  
  37.         {  
  38.             p1->next=p;  
  39.             p->prev=p1;  
  40.             p1=p;  
  41.         }  
  42.     }  
  43.     p->next=NULL;  
  44. }  
  45. //查找数据  
  46. struct list*search(struct list *slot[],int k)  
  47. {//O(1+n/m)  
  48.     int i=hash(k);//O(1)  
  49.     struct list*x;  
  50.     x=slot[i];  
  51.     while (x!=NULL&&x->key!=k)//O(n/m) 如果散列表中槽和表中元素数成正比,有n=O(m)。那么运行时间就为O(1)。  
  52.     {  
  53.         x=x->next;  
  54.     }  
  55.     return x;  
  56. }  
  57. //插入数据  
  58. struct list*insert(struct list *slot[],struct list*x)  
  59. {  
  60.     x=Allocate_object(x);  
  61.     if (!x)  
  62.     {  
  63.         return NULL;  
  64.     }  
  65.     x->flag=1;//1表示有数据  
  66.     x->key=rand()%max;  
  67.     int i=hash(x->key);  
  68.     x->next=slot[i];  
  69.     if (slot[i]!=NULL)  
  70.     {  
  71.         slot[i]->prev=x;  
  72.     }  
  73.     slot[i]=x;  
  74.     x->prev=NULL;  
  75.     return slot[i];  
  76. }  
  77. //删除数据  
  78. struct list*Delete(struct list *slot[],int k)  
  79. {  
  80.     int i=hash(k);  
  81.     struct list *x=search(slot, k);  
  82.     if (!x)  
  83.     {  
  84.         cout<<"待删除数据不存在!"<<endl;  
  85.         return NULL;  
  86.     }  
  87.     if (x->prev!=NULL)  
  88.     {  
  89.         x->prev->next=x->next;  
  90.     }   
  91.     else  
  92.     {  
  93.         slot[i]=x->next;  
  94.     }  
  95.     if (x->next!=NULL)  
  96.     {  
  97.         x->next->prev=x->prev;  
  98.     }  
  99.     free_object(x);  
  100.     cout<<"删除成功!"<<endl;  
  101.     return slot[i];  
  102. }  
  103. //分配空间  
  104. struct list * Allocate_object(struct list *x)//数组next为自由数组表  
  105. {  
  106.     if (Free==NULL)//表示未占用的槽  
  107.     {  
  108.         cerr<<"out of space"<<endl;  
  109.         return NULL;  
  110.     }  
  111.     else  
  112.     {  
  113.         x=Free;  
  114.         Free=Free->next;  
  115.         return x;  
  116.     }  
  117. }  
  118. //释放空间    
  119. void free_object(struct list *x)  
  120. {  
  121.     x->flag=0;  
  122.     x->next=Free;  
  123.     Free=x;  
  124. }  
  125. void Print(struct list *slot[])  
  126. {  
  127.     int i=0;  
  128.     while (i!=MAX)  
  129.     {  
  130.         cout<<"第"<<i<<"个槽:"<<" ";  
  131.         if (!slot[i])  
  132.         {  
  133.             cout<<"NULL";  
  134.         }  
  135.         struct list *p=slot[i];  
  136.         while (p)  
  137.         {  
  138.             cout<<p->key;  
  139.             p=p->next;  
  140.             if (p)  
  141.             {  
  142.                 cout<<"->";  
  143.             }  
  144.         }  
  145.         cout<<endl;  
  146.         i++;  
  147.     }  
  148. }  
  149. void main()  
  150. {  
  151.     struct list *slot[10*MAX],*x;  
  152.     Init(slot);  
  153.     int i=0;  
  154.     while (i!=max)  
  155.     {  
  156.         insert(slot,x);  
  157.         i++;  
  158.     }  
  159.     Print(slot);  
  160.     if(search(slot,0))  
  161.     {  
  162.         cout<<"已经找到"<<endl;  
  163.     }  
  164.     else  
  165.     {  
  166.         cout<<"没有找到"<<endl;  
  167.     }  
  168.     Delete(slot,89);  
  169.     Delete(slot,26);  
  170.     insert(slot,x);  
  171.     insert(slot,x);  
  172.     Print(slot);  
  173. }  
11.2-5假设将一个具有n个关键字的集合存储到一个大小为m的散列表中。试说明如果这些关键字均源于全域U,且|U|>mn,则U中还有一个大小为n的子集,其由散列到同一槽位中的所有关键字构成,使得链接法散列的查找时间最坏情况下位θ(n).
       对于全域|U|>mn映射到m个槽中,若想U>mn,那么至少有一个槽位中的关键字大于n,其查找最坏时间为O(n)
11.2-6假设将n个关键字存储到一个大小为m且通过链接法解决冲突的散列表中,同时已知每条链的长度,包括其中最长链的长度L,请描述从散列表的所有关键字中均匀随机地选择某一元素并在O(L(1+1/a))的期望时间内返回该关键字的过程。
        如果整个哈希表有m行L列,每一行存储一条链。该数组有mL个位置存储这n个元素,并且有mL-n个空值。该程序随机选择数组中任意关键字,直到找到一个关键字并返回。成功的选择一个关键字的概率为n/mL,所以根据伯努利的几何分布知,需要经过mL/n=L/a次选择才能成功找到一个关键字。每次选择需要的时间是O(1),所以成功选择一个关键字需要O(L/a),由于各链表的长度是已知的。这个最长链表L找到期望元素需要O(L)时间,所以从选择元素到找到元素需要O(L(1+1/a)).
11.3散列函数
(1)除法散列法:h(k) = k mod m

(2)乘法散列法:h(k) = m * (k * A mod 1) (0<A<1)

(3)全域散列:从一组散列函数中随机地选择一个,这样平均情况运行时间比较好。

11.3-1 假设我们希望查找一个长度为n的链表,其中每一个元素都包含一个关键字k并具有散列值h(k).每一个关键字都是长字符串。那么在表中查找具有给定关键字的元素时,如何利用各元素的散列值呢?

通过k的散列值h(k)对应的链表找到k.

11.3-2假设将一个长度为r的字符串散列到m个槽中,并将其视为一个以128为基数的数,要求应用除法散列法。我们可以很容易地把数m表示为一个32位的机器字,但对长度为r的字符串,由于它被当做以128为基数的数来处理,就要占用若干个机器字。假设应用散列法计算一个字符串的散列值,那么如何才能在除了该串本身占用的空间外,只利用常数个机器字。

对于每个字母分别取模然后求∑在取模 这一过程利用了模运算的性质,(a+b)%m=(a%m+b%m)%m

11.3-3考虑除法散列法的另一种,其中h(k)=k mod m,m=2^p-1,k为按基数2^p表示字符串。试证明,如果串x可由串y通过其自身的字符置换排列导出,则x和y具有相同的散列值。给出一个应用的例子,其中这一特性在散列函数中是不希望出现的。

比如录入英语单词时,mary是人名,而army是军队,两个单词如果都被用这种方式散列到同一个槽中,那么就会出现歧义。
11.3-4 考虑一个大小为m=1000的散列表和一个对应的散列函数h(k)=m(kAmod1),其中A=(√5-1)/2,试计算关键字61,62,63,64和65被映射到位置。
[cpp]  view plain  copy
  1. #include <iostream>  
  2. #include <math.h>  
  3. using namespace std;  
  4. const double A=(sqrt(5)-1)/2.0;  
  5. int hash(int k,int m)  
  6. {  
  7.     double x=(A*k-(int)(A*k))*m;  
  8.     return x;  
  9. }  
  10. void main()  
  11. {  
  12.    int k;  
  13.    while (cin>>k)  
  14.    {  
  15.        cout<<"Hash["<<k<<"]="<<hash(k,1000)<<endl;  
  16.    }  
  17. }  

11.3-5定义一个从有限集合U到有限集合B上的散列函数簇H为ε全域的。如果对U中所有的不同元素对k和l,都有 Pr{h(k)=h(l)}≤ε,其中概率是相对从函数簇H中随机抽取的散列函数h而言的。试证明:一个ε全域的散列函数簇必定满足:ε≥1/|B|-1/|U|
设b为|B|的槽个数,u为全域|U|的元素个数。
第j个槽中的元素数uj,其中发生冲突的元素对数为C(uj,2)=uj(uj-1)/2
第j个槽中最少有uj=u/b个元素在同一槽中。(《教师手册》里面有证明,这里略过)
所以总的冲突对数至少为∑uj(uj-1)/2=b(u/b)(u/b-1)/2。。。。(1)
而集合U中的元素对数总数是u(u-1)/2.....(2)
所以冲突对数概率ε≥(1)/(2)=(u/b-1)/(u-1)>(u/b-1)/u=1/b-1/u
所以一个ε全域的散列函数簇必定满足:ε≥1/b-1/u=1/|B|-1/|U|
11.3-6 设U为由取自Zp中的值构成的n元组集合,并设B=Zp,其中p为质数。当输入为一个取自U的输入n元组<a0, a1, …, an-1>时,定义其上的散列函数hb:U→B(b∈Zp)为:hb(<a0, a1, …, an-1>) = ∑(j=0~n-1, aj*b^j) 并且,设ℋ={hb:b∈Zp}.根据练习11.3-5中 ε全域的定义,证明 ℋ是((n-1)/p)全域的。(提示:见练习31.4-4)
      对于集合U上的两个互异的n元组 k=<k0, k1, …, kn-1> ϵ U, l=<l0, l1, …, ln-1> ϵ U, 其中ki != li, b ϵ Zp.U散列到B上发生碰撞的概率: Pr{hb(k) = hb(l)}= Pr{hb(k) – hb(l) = 0}= Pr{∑(j=0~n-1, (kj-lj)*b^j) = 0}.其中 ∑(j=0~n-1, (kj-lj)*b^j) = 0)是一个 n-1次的多项式方程,这个方程在Zp上的解b有至多n-1个根。所以Pr{∑(j=0~n-1, (kj-lj)*b^j) = 0} <= (n-1)/p, b ϵ Zp.Pr{hb(k) = hb(l)} <= (n-1)/p, b ϵ Zp.  ℋ 是((n – 1)/p)全域的。得证!参考资料: Rip's Infernal Majesty的博客
11.4开放寻址法
三种探查技术来计算探查序列:1)线性探查:h(k,i)=(h'(k)+i)mod m                i=0,1,1...,m-1
                                                   2)二次探查:h(k,i)=(h'(k)+c1i+c2i²) mod m   i同上
                                                   3)双重探查:h(k,i)=(h1(k)+ih2(k))mod m      i同上
开放寻址散列的分析:定理11.6 给定一个装载因子a=n/m<1的开放寻址散列表,在一次不成功的查找中,期望的探查数之多为1/(1-a).假设散列是均匀的。
推论11.7 平均情况,项一个装载因子为a的开放寻址散列表中插入一个元素时,至多需要做1/(1-a)次探查。假设采用的是均匀散列。
定理11.8 给定一个装载因子为a<1的开放寻址散列表,一次成功查找中的期望探查数之多为(1/a)ln(1/(1-a)).
在开放寻址法中,删除操作比较困难,但也有解决办法,不过在必须删除关键字的应用中,往往采用链接法解决碰撞。
以下是11.3-11.4节代码:
[cpp]  view plain  copy
  1. //除法,乘法,全域散列。与 线性,二次,双重探查。  
  2. #include <iostream>  
  3. #include <math.h>  
  4. #include <time.h>  
  5. using namespace std;  
  6. #define Null -1  
  7. #define DELETED -2  
  8. //除法散列  
  9. int hash_div1(int k,int m)//选m的值常常是不接近2的幂的质数。  
  10. {  
  11.     return k%m;  
  12. }  
  13. //除法散列  
  14. int hash_div2(int k,int m)//选m的值常常是不接近2的幂的质数。  
  15. {  
  16.     return k%m;  
  17. }  
  18. //乘法散列  
  19. int hash_mul(int k)  
  20. {  
  21.    double A=(sqrt(5)-1)/2.0;  
  22.    int m=100;//对于m没有特别要求。m一般选择为2的某个幂。  
  23.    return (A*k-(int)(A*k))*m;  
  24. }  
  25. //全域散列 P为质数  
  26. int hash_Universal(int k,int p)  
  27. {  
  28.     static int a=0,b=0,flag=0;  
  29.     int m=6;//m不一定非是质数。  
  30.     if (flag==0)  
  31.     {  
  32.         a=rand()%(p-1)+1;  
  33.         b=rand()%p;  
  34.         flag=1;  
  35.     }  
  36.     return ((a*k+b)%p)%m;  
  37. }  
  38. //线性探查  
  39. int LineProbe(int k,int m,int i)  
  40. {  
  41.     return (hash_div1(k,m)+i)%m; //选m的值常常是不接近2的幂的质数。  
  42. }  
  43. //二次探查    
  44. int QuadraticProbing(int k,int m,int i)    
  45. {    
  46.     int c1=1,c2=3;      
  47.     return (hash_div1(k,m)+c1*i+c2*i*i)%m; //其中常数c1,c2,m的值的选择是受限制的   
  48. }    
  49. //双重探查    
  50. int DoublefunProbing(int k,int m,int i)    
  51. {    
  52.     return (hash_div1(k,m)+i*(1+hash_div2(k,m-1)))%m; //其中m为质数,还可以选择m为2的幂,并设置一个总产生奇数的hash_div2  
  53. }    
  54. int hash_insert1(int T[],int m,int k)  
  55. {  
  56.     int i=0;  
  57.     do   
  58.     {  
  59.         int j=LineProbe(k,m,i);//这里可以替换成二次,双重探查。插入,查找,删除函数同时被替换  
  60.         if (T[j]==Null||T[j]==DELETED)  
  61.         {  
  62.             T[j]=k;  
  63.             return j;  
  64.         }  
  65.         else i++;  
  66.     } while (i!=m);  
  67.     cerr<<"hash table overflow"<<endl;  
  68. }  
  69. int hash_insert2(int T[],int m,int k)  
  70. {  
  71.     int i=0;  
  72.     do   
  73.     {  
  74.         int j=QuadraticProbing(k,m,i);//这里可以替换成二次,双重探查。插入,查找,删除函数同时被替换  
  75.         if (T[j]==Null||T[j]==DELETED)  
  76.         {  
  77.             T[j]=k;  
  78.             return j;  
  79.         }  
  80.         else i++;  
  81.     } while (i!=m);  
  82.     cerr<<"hash table overflow"<<endl;  
  83. }  
  84. int hash_insert3(int T[],int m,int k)  
  85. {  
  86.     int i=0;  
  87.     do   
  88.     {  
  89.         int j=DoublefunProbing(k,m,i);//这里可以替换成二次,双重探查。插入,查找,删除函数同时被替换  
  90.         if (T[j]==Null||T[j]==DELETED)  
  91.         {  
  92.             T[j]=k;  
  93.             return j;  
  94.         }  
  95.         else i++;  
  96.     } while (i!=m);  
  97.     cerr<<"hash table overflow"<<endl;  
  98. }  
  99. int hash_search(int T[],int m,int k)  
  100. {  
  101.     int i=0,j=0;  
  102.     do   
  103.     {  
  104.         j=LineProbe(k,m,i);//这里可以替换成二次,双重探查。插入,查找,删除函数同时被替换  
  105.         if (T[j]==k)  
  106.         {  
  107.             return j;  
  108.         }  
  109.         else i++;  
  110.     } while (T[j]!=Null||i!=m);  
  111.     return Null;  
  112. }  
  113. void hash_delete(int T[],int m,int k)  
  114. {  
  115.     int j=hash_search(T,m,k);//首先先找到该关键字k  
  116.     if (j!=Null)  
  117.     {  
  118.        T[j]=DELETED;//如果找到了,那么设置其为空。  
  119.        cout<<"删除成功!"<<endl;  
  120.     }  
  121.     else cout<<"待删除的数据不在表中!"<<endl;  
  122. }  
  123. int main()    
  124. {    
  125.     srand( (unsigned)time( NULL ) );  
  126.     int a[9]={10,22,31,4,15,28,17,88,59};    
  127.     int T[11]={0};   
  128.     cout<<"线性散列="<<endl;  
  129.     for (int i=0;i<11;i++)  
  130.     {  
  131.         T[i]=Null;  
  132.     }  
  133.     for( i=0;i<9;i++)    
  134.     {    
  135.         hash_insert1(T,11,a[i]);    
  136.     }    
  137.     for(i=0;i<11;i++)    
  138.     {     
  139.         cout<<T[i]<<" ";     
  140.     }   
  141.     cout<<endl;  
  142.     cout<<"二次散列="<<endl;  
  143.     for (i=0;i<11;i++)  
  144.     {  
  145.         T[i]=Null;  
  146.     }  
  147.     for( i=0;i<9;i++)    
  148.     {    
  149.         hash_insert2(T,11,a[i]);    
  150.     }    
  151.     for(i=0;i<11;i++)    
  152.     {     
  153.         cout<<T[i]<<" ";     
  154.     }   
  155.     cout<<endl;  
  156.     cout<<"双重散列="<<endl;  
  157.     for (i=0;i<11;i++)  
  158.     {  
  159.         T[i]=Null;  
  160.     }  
  161.     for( i=0;i<9;i++)    
  162.     {    
  163.         hash_insert3(T,11,a[i]);    
  164.     }    
  165.     for(i=0;i<11;i++)    
  166.     {    
  167.          cout<<T[i]<<" ";     
  168.     }    
  169.     cout<<endl;    
  170.     cout<<hash_search(T,11,31)<<endl;    
  171.     hash_delete(T,11,31);    
  172.     cout<<hash_search(T,11,31)<<endl;    
  173.     return 0;  
  174. }    
11.4-1 考虑将关键字10,22,31,4,15,28,17,88,59用开放寻址法插入到一个长度为m=11的散列表中,主散列函数为h'(k)=k mod m.说明用线性探查,二次探查(c1=1,c2=3)以及双重散列h2(k)=1+(k mod (m-1))将这些关键字插入散列表的结果。
用11.3-11.4节代码可得:
11.4-2请写出HASH-DELETE的伪代码;修改HASH-INSERT,使之能处理特殊值DELETED。

见11.3-11.4节代码。
11.4-3 假设采用双重散列来解决碰撞;亦即,所用的散列函数为h(k,i)=(h1(k)+h2(k))mod m.证明:如果对某个关键字k,m和h2(k)有最大公约数d≥1,则在对关键字k的一次不成功的查找中,在回到槽h1(k)之前,要检查散列表的1/d.于是,当d=1时,m与h2(k)互质,查找操作可能要检查整个散列表。(提示:见第31章)
      设经过s次查找后,回到槽h1(k)。那么就有(h1(k)+sh2(k))mod m=h1(k)mod m 。此时sh2(k)是m的倍数=> m|sh2(k) 由于gcd(m,h2(k))=d => (m/d) | s(h2(k)/d) 由于gcd(m/d,h2(k)/d)=1 => m/d | s =>s≥m/d=>所以在回到槽h1(k)之前,必检查散列表总元素数的1/d.(更具体地讲应该是至少1/d个) 。当d=1时,1/d=1,所以可能要检查整个散列表。
11.4-4考虑一个采用了均匀散列的开放寻址散列表。给出当装载因子为3/4和7/8时,在一次不成功查找中期望探查数的上界,以及一次成功查找中期望探查数的上界
 不成功的探查上界为1/(1-a):  1/(1-3/4)=4次      1/(1-7/8)=8次                              成功的探查上界为(1/a)ln(1/(1-a)):  1/(3/4)ln(1/(1-3/4))=1.848次   1/(7/8)ln(1/(1-7/8))=2.377次
11.4-5 考虑一个装载因子为a的开放寻址散列表。找出一个非0的值a,使得在一次不成功的查找中,期望的探查数等于成功查找中期望探查数的两倍。此处的两个期望探查数上界可以根据定理11.6和定理11.8得到。
        不成功探查:1/(1-a)   成功探查:(1/a)ln(1/(1-a))   1/(1-a)=2(1/a)ln(1/(1-a)) 关于a的解为:a≈0.715
11.5完全散列
      定义:在使用链接法的1级散列产生冲突后,在每个冲突的槽再次用较小的2级散列,经过随机多次的2级散列筛选后,从而达到2级散列表不发生冲突的目的。
11.5-1 假设要将n个关键字插入到一个大小为m,采用了开放寻址法和均匀散列基数的散列表中。设p(n,m)为没有碰撞发生的概率。证明:p(n,m)≤e^[-n(n-1)/2m].(提示:见公式3.11)论证当n超过√m时,不发生碰撞的概率迅速趋于0.
     对于这n个关键字,有C(n,2)对关键字可能发生碰撞,而每一对关键字对于m个槽发生碰撞的概率是1/m。所以对于n个关键字总共有C(n,2)/m概率发生碰撞,那么p(n,m)=1-C(n,2)/m=1-n(n-1)/2m≤e^[-n(n-1)/2m].(根据1+x≤e^x).后面那个问题不清楚,网上也没有相关答案。
11-1最长探查的界
      用一个大小为m的散列表来存储n个数据项目,并且有n≤m/2。采用开放寻址法来解决碰撞问题。
      a)假设采用了均匀散列,证明对于i=1,2,....n,第i次插入需要严格多于k次探查的概率至多为2^(-k).
        p(x>k)=p(x≥k+1)=(n/m)((n-1)/(m-1)).....(n-k+1)/(m-k+1)≤(n/m)^k≤2^(-k)  利用的是定理11.5分析方法。
          b) 证明:对于i=1,2...n,第i次插入需要多于2lgn次探查的概率至多是1/n^2.
        当k=2lgn时,带入a式便得到p(x>2lgn)≤1/n^2
设随机变量Xi表示第i次插入所需的探查数。在上面b中已经证明Pr{Xi>2lgn}≤1/n^2.设随机变量X=maxXi表示n次插入中所需探查数的最大值。
      c)证明:Pr{X>2lgn}≤1/n
       设事件Xi发生的概率为P{Ai},那么事件X1或事件X2或。。。。时间Xn发生的概率为P{X1∪X2...∪Xn}≤P{X1}+P{X2}+......P{Xn}
由于事件X为探查数最大值,所以事件Xi中成为事件X=X1∪X2...∪Xn,根据b式有P{Xi}≤1/n^2,所以P{X1∪X2...∪Xn}≤n*(1/n^2)=1/n
         d)证明:最长探查序列的期望长度为E[x]=O(lgn)
        根据期望的定义:E[x]=∑(k=1~n)kPr{X=k}
                                           =∑(k=1~2lgn)kPr{X=k}+∑(k=2lgn+1,n)kPr{X=k}
                                           =∑(k≤2lgn)kPr{X=k}+∑(k≥2lgn+1)kPr{X=k}
                                           ≤∑2lgnPr{X=k}+∑nPr{X=k}
                                           =2lgnPr{X≤2lgn}+nPr{X>2lgn} 其中Pr{X≤2lgn}≤1,根据b式Pr{X>2lgn}≤1/n^2 E[x]≤2lgn*1+n*(1/n^2)≤2lgn*1+n*(1/n)=O(lgn) d式参照《教师手册》
11-2 链接法中槽大小的界
             假设有一个含n个槽的散列表,并用链接法来解决碰撞问题。另假设向表中插入n个关键字。每个关键字被等可能地散列到每个槽中。设在所有关键字被插入后,M是各槽中所含关键字数的最大值。读者的任务是证明M的期望值E[M]的一个上界为O(lgn/lglgn).
             a)论证k个关键字被散列到某一特定槽中的概率Qk为:Qk=(1/n)^k(1-1/n)^(n-k)C(n,k)
                这个概率可以抽象成:有C(n,k)种方法选出n次试验中哪k次成功。被散列到特定槽X0视为成功,其成功率是1/n,相反则视为失败概率为1-1/n. 那么根据伯努利的二项分布就是a的答案。
        b)设Pk为M=k的概率,也就是包含最多关键字的槽中k个关键字的概率。证明:Pk≤nQk.
               同思考题11-1c式被散列到槽中最多的关键字数视为一个事件X=X1∪X2∪....∪Xn,其中Xi为被散列到槽i的事件。所以根据布尔不等式Pr{X}≤Pr{X1}+Pr{X2}+....+Pr{Xn}=nQk
             c)应用斯特林近似公式,证明Qk<e^k/k^k 
                Qk=(1/n)^k(1-1/n)^(n-k)C(n,k)<n!/(n^k)k!(n-k)!   (根据(1-1/n)^(n-k)<1)
                                                               <1/k!                     (根据n!/(n-k)!<n^k)
                                                               <e^k/k^k               (根据斯特林近似式k!>(k/e)^k)
             d) 没看懂《教师手册》证明!
              e)论证:E[M]≤Pr{M>clgn/lglgn}*n+Pr{M≤clgn/lglgn}*clgn/lglgn并总结:E[M]=O(lgn/lglgn)
             这个和思考题11-1d证明完全一样,方法往上看11-1d。
11-3 二次探查
            假设要在一个散列表(表中的各个位置为0,1.。。m-1)中查找关键字k,并假设有一个散列函数h将关键字空间映射到集合{0,1,...,m-1}上,查找方法如下:
1) 计算值i←h(k),置j←0。
2)探查位置i,如果找到了所需的关键字k,或如果这个位置是空的,则结束查找。
3)置j←(j+1)mod m,i←(i+j) mod m,返回步2).
            设m是2的幂。
             a)通过给出等式(11.5)中c1和c2的适当值,来证明这个方案是一般的“二次探查”法的一个实例。
               由j=0~m-1得每次循环j就自加1,于是有j=1~m, 初始时,i=h(k),所以第j次循环i=h(k)+1+2..+j=h(k)+j²/2+j/2. 其中当c1=1/2, c2=1/2.
        b)证明:在最坏情况下,这个算法要检查表中的每一个位置。
                哎~ 这个比较坑爹!因为涉及了数论知识。不懂数论的可以略过了。
              在这个算法中,从j=0到m循环中,假设有第i次循环与第j次循环所得散列值h(k,i)=h(k,j)的情况( 且0<i<j<m.) h(k,i)=(h(k)+i²/2+i/2)mod m  h(k,j)=(h(k)+j²/2+j/2)mod m。所以h(k,i)与h(k,j)同余。同余式可表示为(h(k)+i²/2+i/2)≡(h(k)+j²/2+j/2)mod m  <=>(j²/2+j/2-i²/2-i/2)≡0(mod m)  <=>(j-i)(j+i+1)/2≡0(mod m) <=>(j-i)(j+i+1)/2=mk <=>由于m为2的幂。所以设m=2^t.   (j-i)(j+i+1)=k*2^(t+1).........①
              现在论证j-i与j+i+1若一个为奇数,则另一个必为偶数....②  若i=2k1,j=2k2,则j-i=2(k2-k1)为偶数,i+j+1=2(k1+k2)+1为奇数,类似地:i与j都为奇数或i与j一奇一偶都可以证明②式。
              现在假设j-i为奇数,那么j+i+1为偶数,gcd(2^(t+1),j-i)=1.由①式知:2^(t+1) | (j-i)(j+i+1)  所以根据推论31.5:2^(t+1) | (j+i+1).由于j+i+1≤m-1+m-2+1=2(m-1)<2m=2^(t+1) 所以2^(t+1) 不能整除(j+i+1),同理j-i为偶数与j+i+1为奇数时,2^(t+1) 不能整除(j-i).所以①式不成立。所以h(k,i)≠h(k,j),在循环0~m中,任意i与j的hash值都不相等,那么这m次循环得到的hash值各不相等,所以共有m个不同的hash值对应表中m个关键字,覆盖了整个散列表。
11-4 题目没读懂,不知从何下手!

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录(Table of Contents)   前言(Preface)   第一部分(Part I) 基础(Foundations)   第一 计算中算的角色(The Role of Algorithms in Computing)   第二 开始(Getting Started)   第三 函数的增长率(Growth of Functions)   第四 递归(Recurrences)   第五 概率分析与随机化算(Probabilistic Analysis and Randomized Algorithms)   第二部分(Part II) 排序与顺序统计(Sorting and Order Statistics)   第六 堆排序(Heapsort)   第七 快速排序(Quicksort)   第八 线性时间中的排序(Sorting in Linear Time)   第九 中值与顺序统计(Medians and Order Statistics)   第三部分(Part III) 数据结构(Data Structures)   第十 基本的数据结构(Elementary Data Structures)   第十一 散列表Hash Tables)   第十二 二叉查找树(Binary Search Trees)   第十三 红-黑树(Red-Black Trees)   第十四 扩充的数据结构(Augmenting Data Structures)   第四部分(Part IV) 高级的设计与分析技术(Advanced Design and Analysis Techniques)   第十五 动态规划(Dynamic Programming)   第十六 贪婪算(Greedy Algorithms)   第十七 分摊分析(Amortized Analysis)   第五部分(Part V) 高级的数据结构(Advanced Data Structures)   第十八 B-树(B-Trees)   第十九 二项式堆(Binomial Heaps)   第二十 斐波纳契堆(Fibonacci Heaps)   第二十一 不相交集的数据结构(Data Structures for Disjoint Sets)   第六部分(Part VI) 图算(Graph Algorithms)   第二十二 基本的图算(Elementary Graph Algorithms)   第二十三 最小生成树(Minimum Spanning Trees)   第二十四 单源最短路径(Single-Source Shortest Paths)   第二十五 全对的最短路径(All-Pairs Shortest Paths)   第二十六 最大流(Maximum Flow)   第七部分(Part VII) 精选的主题(Selected Topics)   第二十七 排序网络(Sorting Networks)   第二十八 矩阵运算(Matrix Operations)   第二十九 线性规划(Linear Programming)   第三十 多项式与快速傅里叶变换(Polynomials and the FFT)   第三十一 数论算(Number-Theoretic Algorithms)   第三十二 字符串匹配(String Matching) ......................................................
第一部分(Part I) 基础(Foundations) 第一 计算中算的角色(The Role of Algorithms in Computing) 第二 开始(Getting Started) 第三 函数的增长率(Growth of Functions) 第四 递归(Recurrences) 第五 概率分析与随机化算(Probabilistic Analysis and Randomized Algorithms) 第二部分(Part II) 排序与顺序统计(Sorting and Order Statistics) 第六 堆排序(Heapsort) 第七快速排序(Quicksort) 第八 线性时间中的排序(Sorting in Linear Time) 第九 中值与顺序统计(Medians and Order Statistics) 第三部分(Part III) 数据结构(Data Structures) 第十 基本的数据结构(Elementary Data Structures) 第十一 散列表Hash Tables) 第十二 二叉查找树(Binary Search Trees) 第十三 红-黑树(Red-Black Trees) 第十四 扩充的数据结构(Augmenting Data Structures) 第四部分(Part IV) 高级的设计与分析技术(Advanced Design and Analysis Techniques) 第十五 动态规划(Dynamic Programming) 第十六 贪婪算(Greedy Algorithms) 第十七 分摊分析(Amortized Analysis) 第五部分(Part V) 高级的数据结构(Advanced Data Structures) 第十八 B-树(B-Trees) 第十九 二项式堆(Binomial Heaps) 第二十 斐波纳契堆(Fibonacci Heaps) 第二十一 不相交集的数据结构(Data Structures for Disjoint Sets) 第六部分(Part VI) 图算(Graph Algorithms) 第二十二 基本的图算(Elementary Graph Algorithms) 第二十三 最小生成树(Minimum Spanning Trees) 第二十四单源最短路径(Single-Source Shortest Paths) 第二十五 全对的最短路径(All-Pairs Shortest Paths) 第二十六 最大流(Maximum Flow) 第七部分(Part VII) 精选的主题(Selected Topics) 第二十七 排序网络(Sorting Networks) 第二十八矩阵运算(Matrix Operations) 第二十九 线性规划(Linear Programming) 第三十 多项式与快速傅里叶变换(Polynomials and the FFT) 第三十一 数论算(Number-Theoretic Algorithms) 第三十二 字符串匹配(String Matching) 第三十三 计算几何学(Computational Geometry) 第三十四 NP-完备性(NP-Completeness) 第三十五 近似算(Approximation Algorithms) 第八部分(Part VIII) 附录:数学背景(Mathematical Background) 索引(Index)
目录(Table of Contents)   前言(Preface)   第一部分(Part I) 基础(Foundations)   第一 计算中算的角色(The Role of Algorithms in Computing)   第二 开始(Getting Started)   第三 函数的增长率(Growth of Functions)   第四 递归(Recurrences)   第五 概率分析与随机化算(Probabilistic Analysis and Randomized Algorithms)   第二部分(Part II) 排序与顺序统计(Sorting and Order Statistics)   第六 堆排序(Heapsort)   第七 快速排序(Quicksort)   第八 线性时间中的排序(Sorting in Linear Time)   第九 中值与顺序统计(Medians and Order Statistics)   第三部分(Part III) 数据结构(Data Structures)   第十 基本的数据结构(Elementary Data Structures)   第十一 散列表Hash Tables)   第十二 二叉查找树(Binary Search Trees)   第十三 红-黑树(Red-Black Trees)   第十四 扩充的数据结构(Augmenting Data Structures)   第四部分(Part IV) 高级的设计与分析技术(Advanced Design and Analysis Techniques)   第十五 动态规划(Dynamic Programming)   第十六 贪婪算(Greedy Algorithms)   第十七 分摊分析(Amortized Analysis)   第五部分(Part V) 高级的数据结构(Advanced Data Structures)   第十八 B-树(B-Trees)   第十九 二项式堆(Binomial Heaps)   第二十 斐波纳契堆(Fibonacci Heaps)   第二十一 不相交集的数据结构(Data Structures for Disjoint Sets)   第六部分(Part VI) 图算(Graph Algorithms)   第二十二 基本的图算(Elementary Graph Algorithms)   第二十三 最小生成树(Minimum Spanning Trees)   第二十四 单源最短路径(Single-Source Shortest Paths)   第二十五 全对的最短路径(All-Pairs Shortest Paths)
下面是使用双散列查找节点插入位置的示例代码,其中第一次散列函数为`Hash(x) = (3*x) % 11`,第二次散列函数为`Hash(x) = (7*x) % 10 + 1`: ```c++ #include <iostream> #include <vector> using namespace std; // 哈希中的节点结构体 struct Node { int key; int value; }; // 哈希结构体 struct HashTable { vector<Node*> buckets; int size; HashTable(int size): size(size) { buckets.resize(size); } }; // 第一次散列函数 int hash1(int x) { return (3*x) % 11; } // 第二次散列函数 int hash2(int x) { return (7*x) % 10 + 1; } // 双散列查找节点插入位置 int doubleHashInsert(HashTable *ht, int key) { int index = hash1(key); // 计算初始位置 int step = hash2(key); // 计算步长 int count = 0; // 记录插入次数 while (ht->buckets[index] != nullptr && ht->buckets[index]->key != key) { // 如果该位置已被占用且不是要插入的节点 index = (index + step) % ht->size; // 计算下一个位置 count++; if (count == ht->size) { // 如果已经插入了哈希大小次,说明哈希已满 return -1; } } return index; // 返回插入位置 } int main() { HashTable ht(11); Node n1 = {1, 10}; Node n2 = {2, 20}; Node n3 = {3, 30}; Node n4 = {4, 40}; Node n5 = {5, 50}; ht.buckets[doubleHashInsert(&ht, n1.key)] = &n1; ht.buckets[doubleHashInsert(&ht, n2.key)] = &n2; ht.buckets[doubleHashInsert(&ht, n3.key)] = &n3; ht.buckets[doubleHashInsert(&ht, n4.key)] = &n4; ht.buckets[doubleHashInsert(&ht, n5.key)] = &n5; for (int i = 0; i < ht.size; i++) { if (ht.buckets[i] != nullptr) { cout << "key: " << ht.buckets[i]->key << ", value: " << ht.buckets[i]->value << endl; } } return 0; } ``` 在上述代码中,我们首先定义了`Node`和`HashTable`结构体。`HashTable`结构体中使用了`vector`来存储哈希中的节点,同时定义了一个大小为`size`的哈希。接下来,我们定义了第一次散列函数`hash1(x) = (3*x) % 11`和第二次散列函数`hash2(x) = (7*x) % 10 + 1`。最后,我们使用`doubleHashInsert()`函数向哈希中插入了5个节点,并输出了哈希中的所有节点。 需要注意的是,在双散列中,第二次散列函数的返回值不能为0,因为如果步长为0,那么每次计算下一个位置时都会停留在同一个位置,无插入哈希中。因此,在上述代码中,我们将第二次散列函数的返回值加1,确保步长不为0。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值