第11章 散列表
11.2 散列表
- 直接寻址表的缺点:全域U很大时,存储U的表占用空间太大,计算机内存可能存储不了这个,并且实际的关键字集合很小,可能用不了这么多的空间,造成浪费。
- 如果用散列表来存储,虽然查找一个元素的时间是O(1),但是这个界的前提是在简单均匀假设下成立的平均性能界,对于直接寻址表来说,O(1)是最差寻址时间。
- 运用散列表存储可能产生一个问题:由于关键字全集大,而存储集合小,可能出现两个关键字散列到一个槽中,出现冲突。
- 为了解决这个问题:为了尽可能的减少冲突,可以选择一个合适的散列函数。一种可行的方法是散列函数尽可能的随机,使冲突最小化或者次数最小化,也可以使用链接法来解决冲突,或者开放寻址法解决此冲突。
通过链接法解决冲突
在链接法中,将散列到同一个槽中的所有元素都存放在一个链表中。
链接法代码如下:#include<stdio.h> #include<stdlib.h> //链接法解决hash冲突 #define max 9 typedef struct list { int key; struct list* prev; struct list* next; } Slot; Slot a[max]; int hash(int key) { return key%9; } //初始化 void Init(Slot a[]) { int i; for( i=0;i<max;i++) { a[i].key=i; a[i].prev=NULL; a[i].next=NULL; } } //插入数据 Chained-Hash-Insert void Insert(Slot a[],int x) { int i; Slot* c=(Slot*)malloc(sizeof(Slot)); c->key=x; i=hash(x); if(a[i].next!=NULL) { c->next=a[i].next; a[i].next=c; c->prev=&a[i]; } else { a[i].next=c; c->prev=&a[i]; c->next=NULL; } } //查找元素 Chainde-Hash-Search 返回具有关键字key的链表的头指针 Slot* Search(Slot a[],int key) { return a[key].next; } //删除元素 Chained-Hash-Delete void Delete(Slot a[],int x) { int key,i,flag=0; key=hash(x); Slot* p=a[key].next; while(p!=NULL) { if(p->key==x) { flag=1; break; } p=p->next; } if(flag) { p->next->prev=p->prev; p->prev->next=p->next; free(p); printf("delete successfully\n"); } else { printf("delete failed\n"); } } void Destroy(Slot a[]) { int i; for(i=0;i<max;i++) { Slot* x=a[i].next; while(x!=NULL) { Slot* p=x; x=x->next; Delete(p,i); } } } void Print(Slot a[]) { int i; for(int i=0;i<max;i++) { Slot* p=a[i].next; while(p!=NULL) { printf("槽为%d的元素数值为%d\n",i,p->key); p=p->next; } } } int main() { int b[9]={5,28,19,15,20,33,12,17,10}; int i; for(i=0;i<max;i++) { Insert(a,b[i]); } Print(a); return 0; }
习题解答
11.2-1
- 书上已经给出了结果
11.2-2
- 见上述代码
11.2-3
- 有序说明查找元素的时间减半,可以二分查找。相对应的,插入元素的过程也需要有序,所以插入元素时间要增加。删除元素的过程也要先进行查找给定元素。
11.2-4
思路:对于该散列表,可以存储一个标志flag,来指示是否是空闲链表,加上一个元素和一个指针。结构体代码如下:
struct list { int flag; //标志位,空闲为0,非空闲为1 int key; //关键字 struct list* prev; //前驱指针 struct list* next; //后驱指针 } ;
由于这节内容还没涉及到散列函数,所以关键字key集合并不是确定的集合,可能是随机的,散列表只是一个存储容器,散列表结构体中的指针类型,可以换成其他结构体类型,不需要在用该自身类型指针,因为对于flag这个属性,链表中是不需要的,是多余的。所以该结构体这种定义并不是很好,会浪费存储空间。并且,对于空闲链表,需要额外定义一个全局指针变量,将所有空的key槽链接成一个表。
11.2-5
- nm个关键字散列到m个槽中,至少会有一个槽的个数大于等于n,而其查找时间肯定是O(n)。
并没有想好如何解答,贴出别人的答案供大家参考
如果整个哈希表有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)). 参考来源