哈希表的理论和实践——查找

哈希表
如何在查找元素的过程中,不与给定的关键字进行比较,就能确定所查找元素的存放位置。
建立一种数据元素的关键字与存放地址之间的对应关系,通过数据元素的关键字直接确定其存放的位置。
哈希表 就是根据哈希函数和解决冲突的方法将元素的关键字映射在一个有限的且连续的地址
构造哈希函数主要是为了使哈希地址尽可能地均匀分布以减少冲突的可能性,并使计算的方法尽可能简便,以提高效率。
1.直接地址法 h(key)=x*key+1; 缺点:造成内存的大量浪费 优点:计算比较简单且不会发生冲突
2.平方取中法 就是将关键字的平方得到的值的其中几位作为哈希函数的地址
3.折叠法 折叠法是将关键字平均分割为若干等分,最后一个部分如果不够可以空缺,然后将这几个等分叠加求和作为哈希地址。这种方法主要用在关键字的位数特别多且每一个关键字的位数大体相当的情况
例如:分割23478245983
234
782
459
83
h(key)=1558
然后去掉进位,将558作为关键字key的哈希地址。
4.除留余数法 最常用的求哈希函数的方法。
除留余数法主要是通过对关键字取余,将得到的余数作为哈希地址。其主要的方法:设哈希表长为m,p为小于等于m的数,则哈希函数为h(key)=key%p。
例如给定一组关键字{75,150,123,183,230,56,37,91},设哈希表表长为14,取p=13则这组关键字的哈希地址存储情况为:
hash 0 1 2 3 4 5 6 7 8 9 10 11 12 13
地址 183 56 227 123 149 230 75 37 91
处理冲突的方法
1.开放定址法 解决冲突比较常用的方法。开放地址法就是利用哈希表中的空地址存储产生冲突的关键字。
hi=(h(key)+di)%m 其中i=1,2,…,m-1
h(key)为哈希函数;m为哈希表长;di为地址常量。地址常量di可以用以下三种方法:
线性探测再散列:在冲突发生时,地址常量di依次取1,2,…,m-1自然数列,
二次探测再散列:在冲突发生时,地址常量di依次取自然数的平方
伪随机数再散列:在冲突发生时,地址常量di依次随机数序列
2.再哈希法
再哈希法就是在冲突发生时,利用另一个哈希函数再次求哈希函数的地址,直到冲突不再发生为止。
hi=rehash(key),i=1,2,…n;
rehash为不同的哈希函数。
3.链地址法
将具有相同散列地址的关键字用一个线性链表存储起来。优点:在哈希表中增加元素和删除元素比较方便。
哈希表的查找和分析
表中已填入的记录越多,再继续填充记录时,发生冲突的可能性越大,即查找时进行关键字查找的比较次数就会越多。
重点:第n个位置到第i个没有数据的位置距离
哈希表的理论和实践——查找_第1张图片案例
例子1;给定一组元素的关键字hash[]={23,35,23,56,123,39,342,90},利用除留余数法和线性探测再哈希法将元素存储在哈希表中,并查找给定的关键字,求平均长度
m为11,p为11
失败时的平均查找长度:ASL失败=(3+2+1+2+1+5+4+3+2+1)/10=12/5

Hash 0 1 2 3 4 5 6 7 8 9 10
地址 23 35 12 56 123 39 342 90
冲突次数 1 1 3 4 4 1 7 7
失败 1 9 8 7 6 5 4 3 2 1 1
成功时的平均查找长度:ASL成功=(13+3+42+72)/8=3.5
失败时的平均查找长度:ASL失败=(1+9+8+7+6+5+4+3+2+1+1)/11=4.27
例子2:将关键字序列(7,8,30,11,18,9,14)散列存储在哈希表中,哈希表的存储空间是一个下标为0开始的一维数组,哈希函数为H(key)=(key
3)MOD7,处理冲突采用线性探测再哈希法,要求填充因子为0.7
(1)请画出构造的哈希表。
(2)分别计算等概率情况下查找成功和查找失败时平均查找长度
分析:已知条件装填因子a=0.7,m为10
Hash 0 1 2 3 4 5 6 7 8 9
地址 7 14 8 11 30 18 9
冲突次数 1 2 1 1 1 3 3
失败 3 2 1 2 1 5 4 3 2 1
成功时的平均查找长度:ASL成功=(1+2+1+1+1+3+3)/7=12/7

具体实现代码:

#include
#include
#include
#include
typedef int KeyType;
typedef struct{
	KeyType key;
	int hi;
}DataType;
typedef struct {
	DataType *data;
	int tableSize;
	int curSize;
}HashTable; 
void CreateHashTable(HashTable *H,int m,int p,int hash[],int n);
int SearchHash(HashTable H,KeyType k);
void DisplayHash(HashTable H,int m);
void HashASL(HashTable H,int m);
void DisplayHash(HashTable H,int m);
//构造一个哈希表
void CreateHashTable(HashTable *H,int m,int p,int hash[],int n){
	int i,sum,addr,di,k=1;
        H->data = (DataType *)malloc(m * sizeof(DataType));
        if(H->data == NULL)
        {
                exit(-1); 
        }
        for(i=0; i<m; i++)
        {
                H->data[i].key = -1;
                H->data[i].hi = 0;
        }
        for(i=0; i<n; i++)
        {
                sum = 0;
                addr = hash[i] % p;
                di = addr;
                if(H->data[addr].key == -1)
                {
                        H->data[addr].key = hash[i];
                        H->data[addr].hi = 1;
                }
                else
                {
                        do
                        {
                                di = (di + k)%m;
                                sum += 1;
                        }while((H->data[di].key != -1));
                        H->data[di].key = hash[i];
                        H->data[di].hi = sum + 1;
                }
        }
        H->curSize = n;
        H->tableSize = m;
} 
//在哈希表中查找关键字 
int SearchHash(HashTable H,KeyType k){
	int d,dl,m;
	m=H.tableSize;
	d=dl=k%m;
	while(H.data[d].key!=-1){
		if(H.data[d].key==k){
			return d; 
		}else{
			d=(d+1)%m; 
		}
		if(d==dl){
			return 0;
		}
	} 
	return 0;
}
void HashASL(HashTable H,int m){
	float average=0;
	int i;
	for(i=0;i<m;i++){
		average=average+H.data[i].hi;
	}
	average=average/H.curSize;
	printf("平均查找长度ASL=%2f",average);
	printf("\n");
}
void DisplayHash(HashTable H,int m){
	int i;
	printf("哈希表地址:");
	for(i=0;i<m;i++){
		printf("%-5d",i);
	}
	printf("\n");
	printf("关键字key:");
	for(i=0;i<m;i++){
		printf("%-5d",H.data[i].key);
	}
	printf("\n");
	printf("冲突次数:");
	for(i=0;i<m;i++){
		printf("%-5d",H.data[i].hi);
	}
	printf("\n");
}
int main(){
	int hash[]={23,35,12,56,123,39,342,90};
	int m=11,p=11,n=8,pos;
	KeyType k;
	HashTable H;
	printf("关键字在哈希表中的位置为:\n");
	CreateHashTable(&H,m,p,hash,n);
	printf("关键字在哈希表中的位置为:\n");
	DisplayHash(H,m);
	k=123;
	pos=SearchHash(H,k);
	printf("关键字%d在哈希表中的位置为:%d\n",k,pos);
	HashASL(H,m);
	return 1;
}

效果图:
哈希表的理论和实践——查找_第2张图片总结:
静态查找主要包括:顺序表、有序顺序表和索引顺序表。
顺序表的查找是指从表的 第一个元素开始与给定关键字比较,直到表的最后。
有序顺序表的查找是指在查找过程中如果给定关键字大于表的元素,就可以停止查找,说明表中不存在该元素。
索引顺序表的查找是为主表建立一个索引,根据索引确定元素所在的范围,这样可以有效提高查找的效率。
动态查找主要包括二叉排序树、平衡二叉树、B-树和B+树。这些都是利用二叉树和树的特点对数据元素集合进行排序,通过将元素插入到二叉树和树中建立二叉树或树,然后通过对二叉树或树的遍历按照从小到大输出元素的序列。
哈希表是利用哈希函数的映射关系直接确定要查找元素的位置,大大减少了与元素的关键字的比较次数。建立哈希表的方法:直接定址法、平方取中法、折叠法和除留余数法等,最常用的是除留余数法。解决冲突可以常用的方法有两个:开发定址法和链地址法。开发定址法是利用哈希表中的空地址存储发生冲突的关键字,解决冲突可以利用地址增量解决,

你可能感兴趣的:(数据结构和算法,数据结构,算法,hash,C语言)