哈希相关及C语言实现

散列(哈希)

  1.     是在记录的存储位置和它的关键字之间建立一个确定的映射关系f,使得每个关键字key对应一个存储位置(参考大话数据结构8.9-8.12)。
  2.     存储位置=f(key)f称为散列函数,又称为哈希函数。采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表
  3.     哈希是用来解决查找与给定值相等的位置。
  4.     设计一个简单、均匀、存储利用率高的散列函数是散列技术中最关键的问题。
  5.     散列冲突:两个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;
}



输出结果如图:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值