您的当前位置:首页正文

哈希表算法通俗理解和实现

2024-12-01 来源:个人技术集锦

【坚决抵制某商业网站直接拷贝博客而不标明出处的粗暴做法,转载请标明出处,谢谢!】

顺序查表法

假设现在有1000个人的档案资料需要存放进档案柜子里。要求是能够快速查询到某人档案是否已经存档,如果已经存档则能快速调出档案。如果是你,你会怎么做?最普通的做法就是把每个人的档案依次放到柜子里,然后柜子外面贴上人名,需要查询某个人的档案的时候就根据这个人的姓名来确定是否已经存档。但是1000个人最坏的情况下我们查找一个人的姓名就要对比1000次!并且人越多,最大查询的次数也就越多,专业的说这种方法的时间复杂的就是O(n),意思就是人数增加n倍,那么查询的最大次数也就会增加n倍!这种方法,人数少的时候还好,人数越多查询起来就越费劲!那么有什么更好的解决方法吗?答案就是散列表算法,即哈希表算法。

哈希表算法

假设每个人的姓名笔划数都是不重复的,那么我们通过一个函数把要存档的人姓名笔划数转换到1000以内,然后把这个人的资料就放在转换后的数字指定的柜子里,这个函数就叫做哈希函数,按照这种方式存放的这1000个柜子就叫哈系表(散列表),人名笔画数就是哈系表的元素,转换后的数就是人名笔划数的哈希值(也就是柜子的序号)。当要查询某个人是否已经存档的时候,我们就通过哈希函数把他的姓名笔划数转化成哈希值,如果哈希值在1000以内,那么恭喜你这个人已经存档,可以到哈希值指定的柜子里去调出他的档案,否则这个人就是黑户,没有存档!这就是哈希表算法了,是不是很方便,只要通过一次计算得出哈希值就可以查询到结果了,专业的说法就是这种算法的时间复杂是O(1),即无论有多少人存档,都可以通过一次计算得出查询结果!

当然上面的只是很理想的情况,人名的笔划数是不可能不重复的,转换而来的哈希值也不会是唯一的。那么怎么办呢?如果两个人算出的哈希值是一样的,难道把他们都放到一个柜子里面?如果1000个人得出的哈希值都是一样的呢?下面有几种方法可以解决这种冲突。 


这种方法的做法是,如果计算得出的哈希值对应的柜子里面已经放了别人的档案,那么对不起,你得再次通过哈希算法把这个哈希值再次变换,直到找到一个空的柜子为止!查询的时候也一样,首先到第一次计算得出的哈希值对应的柜子里面看看是不是你要找的档案,如果不是继续把这个哈希值通过哈希函数变换,直到找到你要的档案,如果找了几次都没找到而且哈希值对应的柜子里面是空的,那么对不起,查无此人!

这种方法的做法是,如果计算得出的哈希值对应的柜子里面已经放了别人的档案,那也不管了,懒得再找其他柜子了,就跟他的档案放在一起!当然是按顺序来存放。这样下次来找的时候一个哈希值对应的柜子里面可能有很多人的档案,最差的情况可能1000个人的档案都在一个柜子里面!那么时间复杂度又是O(n)了,跟普通的做法也没啥区别了。在算法实现的时候,每个数组元素存放的不是内容而是链表头,如果哈希值唯一,那么链表大小为1,否则链表大小为重复的哈希值个数。

公共溢出区

这种方法跟拉链法也差不多,如果计算得出的哈希值对应的柜子里面已经放了别人的档案,那么就把这个人放到另外一个档案室里面哈希值对应的柜子里面,这样哈希值是一样的,但是档案室不同了。查找的时候根据得到哈希值去不同档案室分别查找,直到找到档案或者没有找到档案为止。这样其他的那些档案室就叫做公共溢出区。

 

通过上面的描述可以看出,所谓的哈希表算法复杂度也不一定就是理想的O(1),但即便如此,还是比普通的顺序查表法速度快多了,因为不可能所有的哈希值都是一样的,如果那样的话,只能说明你的哈希函数不够优秀,你要做的就是换一个哈希函数!一个好的哈希函数应该尽可能让要保存的内容平均的分布在哈希表上。常用的哈希函数有:直接定址法、求余法、数字分析法、平方取中法、折叠法、随机数法等。下面是最常用的求余法。

求余法哈希函数

这种方法就是用人名笔画数除以一个常数,最后的余数就是哈希值。这就是最简单也是最常用的哈希函数。当然这个常数的取法也是就讲究的,一句话,最好是一个跟2或者10的乘幂差值比较大的素数!这种方法的缺陷就是比较耗时,因为除法取余在CPU执行的时候比其他算数运算用的时钟周期更长!

 

C语言实现

下面是求余法哈希函数的C语言实现:

/*
 * hashTable.c
 *
 *  Created on: 2016-4-8
 *      Author: zhw123
 */
#include <stdio.h>
#include <stdlib.h>

#define HASHSIZE 25
#define NULLVALUE -32767

typedef struct{
	int size;
	int element[HASHSIZE];
}hashStruct;

/***********************************************************
 * 哈希表初始化
 * 给哈希表分配内存并初始化元素为空值
 ***********************************************************/
void hashTableInit(hashStruct **hashTable)
{
	*hashTable=(hashStruct *)malloc(sizeof(hashStruct));
	(*hashTable)->size=HASHSIZE;
	int i=0;
	for(i=0;i<HASHSIZE;i++){
		(*hashTable)->element[i]=NULLVALUE;
	}
}

/***********************************************************
 *求元素哈希表地址
 ***********************************************************/
int getHashAddress(int element)
{
	return element%HASHSIZE;
}

/***********************************************************
 *在哈希表中插入元素
 *成功返回0,返回-1表示哈系表已满
 ***********************************************************/
int insertElement(hashStruct *hashTable,int element)
{
	int address=getHashAddress(element);
	while(hashTable->element[address]!=NULLVALUE){
		address=getHashAddress(address+1);
		if(address==getHashAddress(element)){
			return -1;
		}
	}
	hashTable->element[address]=element;
	return 0;
}

/***********************************************************
 *查询哈希表
 *返回匹配的哈系表地址,返回-1表示没有找到目标
 ***********************************************************/
int searchHashTable(hashStruct *hashTable,int element)
{
	int address=getHashAddress(element);
	while(hashTable->element[address]!=element){
		address=getHashAddress(address+1);
		if(hashTable->element[address]==NULLVALUE||address==getHashAddress(element)){
			return -1;
		}
	}
	return address;
}

/***********************************************************
 *在哈希表中删除元素
 *成功返回元素地址,返回-1表示没有该元素
 ***********************************************************/
int removeElement(hashStruct *hashTable,int element)
{
	int address=searchHashTable(hashTable,element);
	if(address==-1){
		return -1;
	}
	hashTable->element[address]=NULLVALUE;
	return address;
}


static void outTable(int *table, int lenght)
{
	int i=0;
	for(i=0;i<lenght;i++)
	{
		printf("%d ",table[i]);
	}
	printf("\n") ;
}

int main()
{
	hashStruct *hashTable;
	hashTableInit(&hashTable);//初始化哈希表
	int table[25]={1,2,3,4,5,6,7,8,9,10,
						11,12,13,14,15,16,17,18,19,
						20,21,22,23,24,25} ;
	int i=0;
	//插入元素
	for(i=0;i<HASHSIZE;i++){
		insertElement(hashTable,table[i]);
	}
	printf("HashTable: ");
	outTable(hashTable->element,HASHSIZE);//输出哈系表
	printf("Remove return : %d\n",removeElement(hashTable,22));//删除元素22
	printf("Remove return : %d\n",removeElement(hashTable,100));//删除没有的元素11
	printf("New hashTable: ");
	outTable(hashTable->element,HASHSIZE);//输出新的哈系表
	//查找元素23,打印结果
	int element=23;
	int address=searchHashTable(hashTable,element);
	if (address==-1){
		printf("Have no match !\n") ;
	}
	else{
		printf("%d hashAddress is : %d\n",element,address) ;
	}
	return 0 ;
}

运行结果:

程序首先初始化了一个哈希表,然后把25个数字插入哈希表并打印哈希表,再删除元素22和100并返回删除结果,删除后打印新表,最后查询元素23并打印查询结果。

显示全文