哈希表(散列)

哈希表(散列)

定义

  • 哈希表
    哈希表(散列表)是一种将关键字key与地址或其它数据建立起直接映射关系的数据结构。哈希表通过哈希函数(hash函数)将关键字key进行运算并映射到散列表中的某一位置,建立起这样的映射关系可以加快对数据的查找和访问。
  • 哈希函数
    哈希函数的功能是建立其关键字key与哈希表中存储位置的映射关系。
    将关键字按一定算法转换成数值,再对数值做一定处理得出hashcode,hashcode代表了数据对象在哈希表中存储的索引位置index。
    一个好的hash函数应具备两个条件:尽可能地将数据元素均匀地分布在hash表中,这样做的好处是可以减少hash冲突;hash函数对关键字的映射运算的时间复杂度应是常数,不能太大。
  • 哈希冲突
    由于hash函数对不同关键字的映射结果可能是相同的,也就是说,不同的key,经过hash(key)后得出的hashcode可能相同,这样一来两个不同的数据对象就映射到了hash表中的同一存储位置,这样的现象叫做hash冲突。

解决hash冲突的方式

1.链式分离法

该方法的主要思想是建立链表来解决hash冲突,将在同一位置产生hash冲突的元素链接起来组成链表,再将链表插入到hash表中的对应位置。这样一来当我们需要访问查找一个关键字对应的数据时,就需要先通过hash函数获得其索引位置,然后遍历该索引位置对应的链表,逐个节点对关键字key进行比较最终完成查找。

链式分离法的优缺点:
这里引用一下别的博主的文章:
https://blog.csdn.net/ldw662523/article/details/79567817

  • 链地址法的优点
    与开放定址法相比,拉链法有如下几个优点:
    ①链地址法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
    ②由于链地址法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
    ③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而链地址法中可取α≥1,且结点较大时,链地址法中增加的指针域可忽略不计,因此节省空间;
    ④在用链地址法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在 用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。
  • 链地址法的缺点
    链地址法的缺点是:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

链式分离法的C语言代码实现:

#include <stdio.h>
#include <stdlib.h>
 //定义数据对象 
 typedef struct stu
 {
 	char name[50];
 	int score;
  } stu;
//定义数据表 
  typedef struct stu_list
  {
  	struct stu * stus;
  	int size;
  }stu_list;
//定义链表(解决哈希冲突)
typedef struct linknode
{
	char name[50];
	int score;
	struct linknode * next;
} linknode,*linklist;
//定义哈希表
typedef struct HashTable
{
	linknode ** elements;
	int table_size;
 } HashTable;
linklist linklist_add(linklist l,stu s)
{
	if(l==NULL)
	{
		linklist head=(linklist)malloc(sizeof(linknode));
		head->next=(linklist)malloc(sizeof(linknode));
		strcpy(head->next->name,s.name);
		head->next->score=s.score;
		head->next->next=NULL;
		return head;
	}
	linknode * p=l,*q;
	while(p->next!=NULL)
	{
		p=p->next;
	}
	q=(linklist)malloc(sizeof(linknode));
	strcpy(q->name,s.name);
	q->score=s.score;
	q->next=NULL;
	return l;
}
//初始化哈希表
HashTable *Init_HashTable(int table_size)
{
	HashTable * HT=(HashTable *)malloc(sizeof(HashTable));
	HT->table_size=table_size;
	HT->elements=(linknode **)calloc(table_size,sizeof(linknode *));
	return HT;
 }
 //创建数据表
 stu_list* Create_stulist(int size)
 {
 	if(size<0)
	 {
	 	printf("表的大小非法!");
	 	getchar();
	 	return;
	  } 
	printf("\n创建数据表:\n");
 	int i,j,k;
 	stu_list * sl=(stu_list*)malloc(sizeof(stu_list));
 	sl->size=size;
 	sl->stus=(stu_list *)calloc(size,sizeof(stu));
 	for(i=0;i<size;i++)
 	{
 		printf("请输入学生姓名:\n");
 		scanf("%s",sl->stus[i].name);
 		printf("请输入学生成绩:\n") ;
 		scanf("%d",&sl->stus[i].score);
	 }
	 return sl;
 }
//定义hash函数
int hash(char * key,int key_size,int table_size)
{
	int i,j,k,hashcode=0;
	for(i=0;i<key_size;i++)
	{
		hashcode = hashcode *37 +(int) key[i];
	}
    hashcode %= table_size;
	if(hashcode<0) hashcode += table_size;
	return hashcode;
}
//创建哈希表
HashTable *CreateHashTable(stu_list * sl,int table_size)
{
	int i,j,k; 
	int index=0,len=0;
	linklist l;
	stu s;
	HashTable *ht=Init_HashTable(table_size);
	for(i=0;i<sl->size;i++)
	{
		s=sl->stus[i];
		len=strlen(s.name);
		index=hash(s.name,len,table_size);
		l=ht->elements[index];
		l=linklist_add(l,s);
		ht->elements[index]=l;
	}
	return ht;
 }
 //根据关键字在哈希表中查找数据 
int get_socre_bykey(char * key,int len,int table_len,HashTable *ht)
{
	int index=hash(key,len,table_len);
	linklist p=ht->elements[index];
	if(p==NULL) 
	{
		printf("NULL"); return;
	}
	p=p->next;
	while(p!=NULL)
	{
	    if(strcmp(p->name,key)==0)
	    {
	    	return p->score;
		}
		p=p->next;
	}
	printf("\n查找失败,未查找到相关内容!");
	return -1;

}
main()
{
	int hashcode;
	int n,i;
	int table_size=5;
	stu_list * sl=Create_stulist(3);
	HashTable * ht=CreateHashTable(sl,table_size);
	char name[50]; 
	printf("\n请输入要查找的内容:"); 
	scanf("%s",name); 
	int socre=get_socre_bykey(name,strlen(name),table_size,ht);
	if(socre!=-1)
	printf("\nsocre=%d",socre);
}

2.开放定址法

1.线性探测法
假设hash表足够大时,当元素间产生了hash冲突,那么可以通过再探测的方式,在表中探测出一个空的位置,将元素存入。而用于探测的函数是线性的探测方式则为线性探测法,即第i次探测的下一个位置可以表示为:index+=f(i),其中f是关于i的线性函数。
线性探测法的优点是,可以将表中的所有位置都探测到,对表内空间的利用率高,但是缺点同样非常明显,即容易产生聚集,造成表内局部的位置大量的产生冲突而造成多次的探测,降低了查找和插入的效率。

线性探测法的c语言代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//定义数据表和hash表 
typedef struct stu
{
	char name[50];
	int score;
}stu;
typedef struct hashtable
{
	int tablesize;
	int currentsize;
	stu * elements;
	int * flag;
}hashtable;
//hash函数
int hash(char * key,int len,int tablesize)
{
	int i,j,k;
	int hashcode=0;
	for(i=0;i<len;i++)
	{
		hashcode+=(int)key[i];
	}
	hashcode %= tablesize;
	if(hashcode<0) hashcode+=tablesize;
	return hashcode;
 } 
//初始化hash表 
hashtable * Init_hashtable(int size)
{
	if(size<=0)
	{
		printf("表长非法!\n");
		return NULL;
	}
	hashtable * ht=(hashtable *)calloc(1,sizeof(hashtable));
	ht->tablesize=size;
	ht->currentsize=0;
	ht->elements=(stu *)calloc(size,sizeof(stu));
	ht->flag=(int *)calloc(size,sizeof(int));
	return ht;
}
//向hash表中存入数据 (线性探测法)
int put(stu s,hashtable * ht)
{
	if(ht==NULL)
	{
		printf("hash表不存在!\n");
		return -1; 
	}
	if(ht->currentsize>=ht->tablesize)
	{
		printf("hash表已满!\n");
		return -1;
	}
	int hashcode=hash(s.name,strlen(s.name),ht->tablesize);
	if(ht->flag[hashcode]==0)
	{
		ht->elements[hashcode]=s;
		ht->flag[hashcode]=1;
		return 1;
	}
	else
	{
		while(ht->flag[++hashcode]!=0);
		ht->elements[hashcode]=s;
		ht->flag[hashcode]=1;
		return 1;
	}
}
//从hash表中查找获取元素
int get(char * key,int len,hashtable *ht)
{ 
	if(ht==NULL)
	{
		printf("hash表不存在!\n");
		return -1; 
	}
	int hashcode=hash(key,strlen(key),ht->tablesize);
    while(ht->flag[hashcode]!=0)
    {
    	if(strcmp(key,ht->elements[hashcode].name)==0)
    	{
    		return ht->elements[hashcode].score;
		}
		hashcode++;
	}
	return -1;
 } 
 main()
 {
 	int tablesize=13;
 	hashtable *ht=Init_hashtable(tablesize);
 	stu s1={"xiaohua",88};
	stu s2={"xiaogou",59};
	stu s3={"goudan",99};
	put(s1,ht);
	put(s2,ht);
	put(s3,ht);
	int score = get(s2.name,strlen(s2.name),ht);
	if(score!=-1)
	{
		printf("查找结果为:%d\n",score);
	}
	else
	{
		printf("未查找到相关内容!\n");
	}
 }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值