散列(哈希):
- 是在记录的存储位置和它的关键字之间建立一个确定的映射关系f,使得每个关键字key对应一个存储位置(参考大话数据结构8.9-8.12)。
- 存储位置=f(key)f称为散列函数,又称为哈希函数。采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表。
- 哈希是用来解决查找与给定值相等的位置。
- 设计一个简单、均匀、存储利用率高的散列函数是散列技术中最关键的问题。
- 散列冲突:两个key1 != key2,但是f(key1) == f(key2),不同的key值对应相同的位置,称为散列冲突。
散列函数的构造方法:
1.直接定址法:关键字的某个线性函数值为散列地址,即f(key) = a*key+b。
优点是简单,均匀,也不会产生散列冲突(f(key)与key从二维坐标来看是线性关系,自然不会冲突)。
但是需要预先知道key值的分布情况(比如key_1=1000,key_2=2000,如果不根据分布情况来设计散列函数,会造成存储空间的极大浪费)。
2.数字分析法、平方取中法、除留取余法、随机数法。
处理散列冲突的方法:
1.开放定址法:一旦发生冲突,就去寻找下一个空的散列地址,只要散列表够大,就不会出现散列冲突。也称为线性探测法。在此基础上增加平方运算称为二次探测法。
2.再散列函数法:准备多个散列函数,当发生冲突时,就换一个散列函数计算(增加计算成本,有概率还是会出现散列冲突)。
3.链地址法:将f(key)相等的key放在一个链表里,冲突一次就增加一个节点。
4.公共溢出区法
散列表查找性能分析:
如果没有冲突,时间复杂度O(1)。
平均查找查找长度取决于:
1.散列函数是否均匀。
2.处理冲突的方式
3.散列表的装填因子=表中的记录个数/散列表的长度。表示着散列表的装满程度。这个值越大,再次填入时产生冲突的可能性越高。
通常会将散列表的空间设置的比查找集合大一些,通过空间换时间的方式降低平均查找长度。
C语言代码实现如下:
将人的id作为key值,体重作为待查找数据,实现哈希。注意,这块代码大话数据结构里面写的不好,没有插入实际数据,自己基于理解进行了代码实现。
哈希函数使用除留取余法。注意在hash create时和search时对冲突的处理。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<string.h>
#include<sys/stat.h>
#include<time.h>
#include<stdarg.h>
#if 1
#define INFO_DEBUG "%d lines in "__FILE__", complie time is "__TIME__" "__DATE__" \n",__LINE__
#define ADI_RUN_ERROR 1
#define ADI_NULL_POINTER -1
#define ADI_OK 0
#define ADI_PRINT printf("[%s][%d]:",__FUNCTION__,__LINE__);printf
#define ADI_ASSERT(c,prt,ret) if(c) {printf(prt);return ret;}
#endif
#define PARAM_FAIL -1
#define P_NULL -2
#define SUCCESS 0
#define NUllKEY -32768
/*将id作为hash的key值,weight作为每个id的体重,实现根据ID查找对应的体重*/
typedef struct{
int id; //每个人的id
float weight; //每个人的体重
}ELE;
typedef struct{
ELE *elem; //数据元素存储基地址,
int size; //哈希表大小
}HashTable;
int hashSize = 12;
/*初始化hash表,num表示hash表的表长*/
int InitHashTable(HashTable *H,int num)
{
H->size = num;
H->elem = (ELE*)malloc(num*sizeof(ELE));
if(NULL == H->elem)
{
ADI_PRINT("malloc return NULL !\n");
return P_NULL;
}
for(int i=0;i<num;i++)
{
H->elem[i].id = NUllKEY;
}
return SUCCESS;
}
/*散列函数*/
int Hash(int key)
{
return key%hashSize;//除留取余法,除数为HASH_SIZE
}
/*创建哈希表*/
int CreateHash(HashTable *H,int key,float data)
{
int addr = Hash(key);
while(NUllKEY != H->elem[addr].id)
{
ADI_PRINT("hash hit,key = %d\n",key);//发生哈希冲突
addr = (addr+1)%H->size;//发生哈希冲突时,使用开放定址法解决冲突
/*这段代码是一个动态扩容的实现,但是如果在create hash时有动态扩容的操作,在查找时并不能知道当时动态扩容的hashsize,导致找不到对应的位置
比如原来hash表的size是12,因为冲突扩容到13,再14,当查找如果发生冲突并不知道扩容时的hashsize,会比较麻烦,所以通常通过申请较大的哈希表(相比于要存储的数据)来避免这个问题
当然我觉得在ELE中增加一个变量存储扩容时的hashsize也是一个解决方案*/
// if(addr>(H->size-1))//addr是从0开始
// {
// /*超过原有hash表内存,需要动态扩容*/
// ADI_PRINT("beyond hashSize, need reinit\n");
// int *temp = realloc((addr+1)*sizeof(ELE));//realloc 会将原内存块里的内容复制到新的内存指针中
// if(NULL == temp)
// {
// ADI_PRINT("malloc return NULL !\n");
// return P_NULL;
// }
// H->elem = temp;
// for(int i=H->size-1;i<addr;i++)
// {H->elem[i].id = NUllKEY;}
// H->size = addr+1;
// }
}
H->elem[addr].id = key;
H->elem[addr].weight = data;
return SUCCESS;
}
/*
基于hash的查找
在Hash表中,基于给定的key值,返回对应的data
*/
int searchHash(HashTable *H,int key, float *data)
{
int pos = Hash(key);
while(key != H->elem[pos].id)//!=说明发生过散列冲突或者key值不存在
{
pos = (pos+1)%H->size;
if(NUllKEY == H->elem[pos].id || pos == Hash(key))//key值不存在,pos == Hash(key)说明已经轮询哈希表一遍都没有
{
ADI_PRINT("hash no key = %d\n",key);
return PARAM_FAIL;
}
}
*data = H->elem[pos].weight;
return SUCCESS;
}
void printHash(HashTable *H)
{
for(int i=0;i<H->size;i++)
{
if(NUllKEY != H->elem[i].id)
{
ADI_PRINT("i = %d,id = %d,weight = %f\n",i,H->elem[i].id,H->elem[i].weight);
}
}
}
int main()
{
ELE test[12] = {12,70.2, 67,71.5, 56,72.4, 16,81.5, 25,96.5, 37,98.5, 22,88.5, 29,69.5, 15,98.5, 47,77.6, 48,98.5, 34,77.6};
HashTable hash;
int ret = 0;
ret = InitHashTable(&hash,12);
ADI_ASSERT(ret!=SUCCESS, "init fail!\n", PARAM_FAIL);
for(int i=0;i<12;i++)
{
ADI_PRINT("i=%d,test[i].id = %d, test[i].weight = %f\n",i,test[i].id,test[i].weight);
ret = CreateHash(&hash, test[i].id, test[i].weight);
ADI_ASSERT(ret!=SUCCESS, "create fail!\n", PARAM_FAIL);
}
printHash(&hash);
int test_id = 34;
float test_float = 0;
ret = searchHash(&hash,test_id,&test_float);
ADI_ASSERT(ret!=SUCCESS, "search fail!\n", PARAM_FAIL);
ADI_PRINT("test_id = %d 's weight = %f\n",test_id,test_float);
test_id = 100;
ret = searchHash(&hash,test_id,&test_float);
ADI_ASSERT(ret!=SUCCESS, "search fail!\n", PARAM_FAIL);
return SUCCESS;
}
输出结果如图: