散列表的实现常常叫做散列(hashing),它是一种以常数平均插入,删除,查找的技术。但是这些元素间的任何排序信息的操作将不会得到有效的支持。就是每个元素都有一个KEY值,通过这个KEY值找到该元素,由于这个key值有可能是有冲突的,所以是散列最主要的是如何解决冲突问题
1、分离链接法:
如图:
咋们可以对数据取然后将其存储到对应的表中即可。Hash(X) = X mod 10;这种方法的思路和了链表差不多。
2、开放定址法
分离链接散列算法的缺点是需要指针,由于给新的单元分配地址需要时间,导致算法速度多少有些减慢。开放定址法就是刚开始定义存储空间,如果有冲突的话,那就通过某个规则去查找下一个存储空间。
1)线性空间探测法。
转换规则就是hi(X)=(Hash(X)+F(i)) mod TableSize 且 F(0)=0,就是相当于逐个探测每个单元(必要时可以绕回)查找出一个空单元。比如插入89 18 49 58 69
第一
假设现在之后10个数据空间,第一个数据89取余之后,存在9哪里,第二个数据18取余存在8哪里,第三个数据49,本来应该也是存在9哪里,但是与89冲突了,于是位置+1回到了0位置**(注:由于这里假设空间大小只有10,9位置已经是极限了,在+1就只能是回头寻找了。)**存在0位置,第四个数据58这个数据和18,89,49冲突了,就只能是存在1哪里,第五个数据69同理只能是存在2哪里。
这种方法比较容易使存储的数据聚集在一块位置,这就导致需要查找多次才能找到新单元。效果不是很好。
2)平方探测法。
平方探测是为了消除线性探测的一次聚集问题,公式是i(X)=(Hash(X)+i*i) mod TableSize
定理:表的大小是素数的时候,当表至少有一半是空的时候,总能找到一个新的元素。
疑问:为啥要指定表的大小为素数呢?网上找到一个回答
H( c ) = c % N;
当N取一个合数8的时候。
H( 11100(二进制) ) = H( 28 ) = 4
H( 10100(二进制) ) = H( 20 )= 4
这时候c的二进制第4位(从右向左数)就”失效”了,也就是说,无论第c的4位取什么值,都会导致H( c )的值一样.这时候c的第四位就根本不参与H( c )的运算,这样H( c )就无法完整地反映c的特性,增大了导致冲突的几率.
取其他合数时,都会不同程度的导致c的某些位”失效”,从而在一些常见应用中导致冲突.
但是取质数,基本可以保证c的每一位都参与H( c )的运算,从而在常见应用中减小冲突几率..
第一数据89,插入了9的位置,第二个数据18插入了8的位置,第三个数据49与89冲突,根据((9+11)%10=0),插入在0的位置,第4个数据58和18和19冲突,那就是((8+22)%10=2),在第2个位置哪里插入,第5个数据69在和89和49冲突了,那就是
((9+2*2)%10=3),在第3个位置哪里。
下来实现一下平方探测法
#include <stdio.h>
#include <malloc.h>
#include <string.h>
struct sl{
int key; //散列键值
char name[10]; //信息
};
typedef struct sl SL;
#define SLSIZE 10
SL *Initsl(int slsize); //建立表
SL *findsl(int key,SL *head);//查找表
void Insertsl(int key,char *name,SL *head);//插入表
void deletesl(int key,SL *head);//删除表
SL *Initsl(int slsize)
{
SL *A=0;
if(slsize<0) return NULL;
A=malloc(slsize*sizeof(SL)); //申请slsize个内存空间
memset((char *)A,0,slsize*sizeof(SL));//初始化空间,直接填充0
return A;
}
SL *findsl(int key,SL *head)
{
int count=0,i=0,cur;
if(head==0) return 0;
cur =key%SLSIZE;//求余数,找出初始位置
i=cur;
while(1){
if(head[i].key==key){
printf("my key=%d name=%s\n",head[i].key,head[i].name);
return (head+i);//返回找到的地址
}
count++;
i=(cur+count*count)%SLSIZE; //根据查找公式(Hash(X)+i*i) 求出位置
if(count>SLSIZE) {
printf("no find\n");
return 0; //查找不到
}
}
}
void Insertsl(int key,char *name,SL *head)
{
int count=0,i=0,cur;
if(head==0) return;
cur =key%SLSIZE;//求余数,找出初始位置
i=cur;
while(1){
if(head[i].key==0){
break;//找到插入的位置了
}
count++;
i=(cur+count*count)%SLSIZE; //根据查找公式(Hash(X)+i*i) 求出位置
if(count>SLSIZE) {
printf("no memory\n");
return ; //没有足够空间
}
}
head[i].key=key;
strcpy(head[i].name,name);
}
void deletesl(int key,SL *head)
{
int count=0,i=0,cur;
if(head==0) return ;
cur =key%SLSIZE;//求余数,找出初始位置
i=cur;
while(1){
if(head[i].key==key){
head[i].key=0;
memset(head[i].name,0,10);
printf("dele %d ok\n",key);
return ;
}
count++;
i=(cur+count*count)%SLSIZE; //根据查找公式(Hash(X)+i*i) 求出位置
if(count>SLSIZE) { //找不到
printf("no find\n");
return ;
}
}
}
void main(void)
{
int i=0;
SL *mainhead=0;
mainhead=Initsl(SLSIZE);//初始化表
Insertsl(89,"I AM 89",mainhead);
Insertsl(18,"I AM 18",mainhead);
Insertsl(49,"I AM 49",mainhead);
Insertsl(58,"I AM 58",mainhead);
Insertsl(69,"I AM 69",mainhead);
for(i=0;i<SLSIZE;i++){ //打印出插入的效果
if(mainhead[i].key!=0){
printf(" %2d ",mainhead[i].key);
}
}
printf("\n");
findsl(49,mainhead);
deletesl(49,mainhead);
findsl(49,mainhead);
findsl(89,mainhead);
free(mainhead);
}