【数据结构篇C++实现】- 哈希表

这篇具有很好参考价值的文章主要介绍了【数据结构篇C++实现】- 哈希表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

友情链接:C/C++系列系统学习目录



🚀一、哈希表的原理精讲

🚢(一)概念

哈希表:也叫做散列表。是根据关键字和值(Key-Value)直接进行访问的数据结构。也就是说,它通过关键字 key 和一个映射函数 Hash(key) 计算出对应的一个存储位置,然后把键值对映射到哈希表上的对应位置来访问记录,以加快查找的速度。这个映射函数叫做哈希函数(散列函数),用于存放记录的数组叫做 哈希表(散列表)。

哈希表是基于快速存取的角度设计的,也是一种典型的“空间换时间”的做法,本质上是由数组衍生而来,其核心就是哈希函数

几个核心概念:

  • 键(key): 组员的编号 如, 1 、 5 、 19 。 。 。
  • 值(value): 组员的其它信息(包含 性别、年龄和战斗力等)
  • 索引:数组的下标(0,1,2,3,4) ,用以快速定位和检索数据,同时可以把该数组称为索引数组
  • 哈希函数:将组员编号映射到索引上,一般采用求余法

c++哈希表,数据结构与算法,数据结构,散列表,c++,哈希表

图中,Key为28,通过哈希函数计算存放在哈希表中的位置,得到索引为:28 % 5 = 3,但是如此的话就会造成一个问题:如果还有个学生的学号为33,通过哈希函数计算后得到的数组存放位置下标也为3,这种情况就是待会要讲的哈希冲突

 

🚢(二)常见哈希函数的构造方法

⛳1.直接定址法

如果我们现在要对0~100岁的人口数字统计表,如表所示,那么我们对年龄这个关键字就可以直接用年龄的数字作为地址。此时f (key) =key。

c++哈希表,数据结构与算法,数据结构,散列表,c++,哈希表

如果我们现在要统计的是80后出生年份的人口数,如表所示,那么我们对出生年份这个关键字可以用年份减去1980来作为地址。此时f (key)=key-1980。

c++哈希表,数据结构与算法,数据结构,散列表,c++,哈希表

也就是说,我们可以取关键字的某个线性函数值为散列地址,即f (key) =axkey+b (a、b为常数)
这样的散列函数优点就是简单、均匀,也不会产生冲突,但问题是这需要事先知道关键字的分布情况,适合查找表较小且连续的情况。由于这样的限制,在现实应用中,此方法虽然简单,但却并不常用。

⛳2.数字分析法

例如当手机号码为关键字时,其11位数字是有规则的,此时是无需把11位数值全部当做散列地址,这时我们给关键词抽取, 抽取方法是使用关键字的一部分来计算散列存储位置的方法,这在散列函数中是常常用到的手段。
数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。这种方法适合于已知的关键字集合,若更换了关键字,则需要重新构造新的散列函数。

⛳3.平方取中法

这个方法计算很简单,假设关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227,用做散列地址。再比如关键字是4321,那么它的平方就是18671041,抽取中间的3位就可以是671,也可以是710,用做散列地址。平方取中法比较适合于不知道关键字的分布,而位数又不是很大的情况。

⛳4.除留余数法

这是一种最简单、最常用的方法,假定散列表表长为m,取一个不大于m但最接近或等于m的质数p,利用以下公式把关键字转换成散列地址。散列函数为H ( k e y ) = k e y % p ( p < = m ) 事实上,这方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。
除留余数法的关键是选好p,使得每个关键字通过该函数转换后等概率地映射到散列空间上的任一地址,从而尽可能减少冲突的可能性。

⛳5.随机数法

选择一个随机数,取关键字的随机函数值为它的散列地址。也就是H ( k e y ) = random ( k e y ) 这里random是随机函数。当关键字的长度不等时,采用这个方法构造散列函数是比较合适的。
 

🚢(三)哈希冲突与处理方法

哈希函数可能会把两个或两个以上的不同关键字映射到同一地址,称这种情况为冲突,这些发生碰撞的不同关键字称为同义词。一方面,设计的好的哈希函数应尽量减少这样的冲突;另一方面,由于这样的冲突总是不可避免的,所以还要设计好处理冲突的方法。

⛳1.开放地址法

所谓的开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。

举例:就是当我们去教室上课,发现该位置已经存在人了,所以我们应该寻找新的位子坐下,这就是开放定址法的思路。如何寻找新的位置就通过以下几种方法实现。

(1)线性探测法

当我们的所需要存放值的位置被占了,我们就往后面一直加1并对m取模直到存在一个空余的地址供我们存放值,取模是为了保证找到的位置在0~m-1的有效空间之中。

它的公式是:
f i ( k e y ) = ( f ( k e y ) + d i ) M O D m ( d i = 1 , 2 , 3 , . . . . . . , m − 1 ) f_{i}(key)=(f(key)+d_{i})MODm(d_{i}=1,2,3,......,m-1) fi(key)=(f(key)+di)MODm(di=1,2,3,......,m1)

c++哈希表,数据结构与算法,数据结构,散列表,c++,哈希表

存在问题:出现非同义词冲突(两个不相同的哈希值,抢占同一个后续的哈希地址)被称为堆积或聚集现象。

(2)平方探测法

当我们的所需要存放值的位置被占了,会前后寻找而不是单独方向的寻找。

公式:
h ( x ) = ( H a s h ( x ) + i ) m o d ( H a s h t a b l e . l e n g t h ) ; ( i 依次为 + ( i 2 ) 和 − ( i 2 ) ) h(x)=(Hash(x) +i)mod (Hashtable.length);(i依次为+(i^2)和-(i^2)) h(x)=(Hash(x)+i)mod(Hashtable.length);i依次为+(i2)(i2)
c++哈希表,数据结构与算法,数据结构,散列表,c++,哈希表

(3)再哈希法

同时构造多个不同的哈希函数,等发生哈希冲突时就使用第二个、第三个……等其他的哈希函数计算地址,直到不发生冲突为止。虽然不易发生聚集,但是增加了计算时间。

总结:

c++哈希表,数据结构与算法,数据结构,散列表,c++,哈希表

⛳2.链地址法

将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。

例如,关键字序列为{ 12 , 67 , 56 , 16 , 25 , 37 , 22 , 29 , 15 , 47 , 48 , 34 },我们用除留余数法构造散列函数H ( k e y ) = k e y % 12 ,用拉链法处理冲突,建立的表如下图所示。

我们将每一条链表称为哈希桶,数组成员为每一个索引值相同的多个元素

c++哈希表,数据结构与算法,数据结构,散列表,c++,哈希表

⛳3.公共溢出区法

这个方法其实就更加好理解,就是把凡是冲突的家伙额外找个公共场所待着。我们为所有冲突的关键字建立了一个公共的溢出区来存放

就前面的例子而言,我们共有三个关键字37 , 48 , 34与之前的关键字位置有冲突,那么就将它们存储到溢出表中,如下图所示。

c++哈希表,数据结构与算法,数据结构,散列表,c++,哈希表

如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是非常高的。

🚀二、哈希表的算法实现

为了减少哈希冲突的现象出现,这里我们就讲解一下链地址这种处理方法的实现,一般我们称为哈希链表

c++哈希表,数据结构与算法,数据结构,散列表,c++,哈希表文章来源地址https://www.toymoban.com/news/detail-782267.html

1.哈希链表的结构体定义

#define DEFAULT_SIZE 3 //默认哈希表大小是3
 
typedef struct _ListNode
{
	int key;
	int data;
	struct _ListNode *next;
}ListNode,*List,*Element;//List作为每个哈希桶的头结点,不保存元素
 
typedef struct _HashTable
{
	List *Lists;//保存所有哈希桶头结点的数组,是个二级指针
	int tableSize;//数组大小 也即哈希桶的个数
}HashTable;

2.哈希函数

//求余数法 根据key 定为桶的位置
int Hash(int key, int tableSize)
{
	return (key%tableSize);
}

3.初始化哈希链表

//初始化哈希表
HashTable * InitHashTable(int tableSize)
{
	HashTable *ht=new HashTable;
	if (!ht) return NULL;
 
	tableSize <= 0 ? DEFAULT_SIZE : tableSize;//如果哈希表数组大小小于等于0 则取默认大小
	ht->tableSize = tableSize;
 
	ht->Lists = new List[tableSize];
	if (!ht->Lists)
	{
		delete ht;
		return NULL;
	}
 
	//为数组中每一个哈希桶的头结点初始化
	for (int i=0;i<tableSize;i++)
	{
		ht->Lists[i] = new ListNode;
		if (!ht->Lists[i])
		{
			delete ht->Lists;
			delete ht;
			return NULL;
		}
		
		//并将头结点置空
		memset(ht->Lists[i], 0, sizeof(ListNode));
	}
 
	return ht;
	
}

4.哈希表查找元素

//根据key值查找哈希表中元素
Element Find(HashTable *ht,int key)
{
 
	int i = Hash(key, ht->tableSize);
	Element tmp = NULL;
    tmp = ht->Lists[i]->next; //取出key所在数据桶的第一个元素
 
	while (tmp!=NULL)
	{
		if (tmp->key == key) return tmp;//找到相同元素则将元素返回
		else tmp = tmp->next;//没有找到则继续找下一个
	}
 
	return tmp;
}

5.哈希表插入元素

//插入元素
bool InsertHashTable(HashTable* &ht, int key, int value)
{
	
	if (Find(ht, key) != NULL)//如果找到了相对应的key的元素 则证明插入元素已经因为存在 因为键值key是惟一的
	{
		printf("Element has existed !");
		return false;
	}
	
	Element L = ht->Lists[Hash(key,ht->tableSize)];//找出 key所对应哈希桶的头结点
    
	//采用头插法插入元素
	Element q = new ListNode;
	if (!q) return false;
 
	q->data = value;
	q->key = key;
	q->next = L->next;
	L->next = q;
 
	return true;
 
}

6.哈希表删除元素

bool DeleteHashNode(HashTable* &ht, int key)
{
	int i = Hash(key, ht->tableSize);
 
	List L = ht->Lists[i];//找到key所在哈希桶的头结点
	Element e = L->next;//找到第一个元素
 
	while (e != NULL && e->key!=key)
	{
		L = e;
		e = e->next;
	}
 
	if (e == NULL)
		return false; //如果没有找到key所对应的元素 返回false
 
	//从桶链表中删除元素e
	L->next = e->next;
	delete e;
	return true;	
}

7.程序清单

// 哈希表Hash.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//Author:See QQ3492625357 
//代码为手写,程序通过简单测试,其中可能有误或不当之处欢迎指正
 
#include <iostream>
 
#define DEFAULT_SIZE 3 //默认哈希表大小是3
 
typedef struct _ListNode
{
	int key;
	int data;
	struct _ListNode *next;
}ListNode,*List,*Element;//List作为每个哈希桶的头结点,不保存元素
 
typedef struct _HashTable
{
	List *Lists;//保存所有哈希桶头结点的数组
	int tableSize;//数组大小 也即哈希桶的个数
}HashTable;
 
//求余数法 根据key 定为桶的位置
int Hash(int key, int tableSize)
{
	return (key%tableSize);
}
 
//初始化哈希表
HashTable * InitHashTable(int tableSize)
{
	HashTable *ht=new HashTable;
	if (!ht) return NULL;
 
	tableSize <= 0 ? DEFAULT_SIZE : tableSize;//如果哈希表数组大小小于等于0 则取默认大小
	ht->tableSize = tableSize;
 
	ht->Lists = new List[tableSize];
	if (!ht->Lists)
	{
		delete ht;
		return NULL;
	}
 
	//为数组中每一个哈希桶的头结点初始化
	for (int i=0;i<tableSize;i++)
	{
		ht->Lists[i] = new ListNode;
		if (!ht->Lists[i])
		{
			delete ht->Lists;
			delete ht;
			return NULL;
		}
		
		//并将头结点置空
		memset(ht->Lists[i], 0, sizeof(ListNode));
	}
 
	return ht;
	
}
 
//根据key值查找哈希表中元素
Element Find(HashTable *ht,int key)
{
 
	int i = Hash(key, ht->tableSize);
	Element tmp = NULL;
    tmp = ht->Lists[i]->next; //取出key所在数据桶的第一个元素
 
	while (tmp!=NULL)
	{
		if (tmp->key == key) return tmp;//找到相同元素则将元素返回
		else tmp = tmp->next;//没有找到则继续找下一个
	}
 
	return tmp;
}
 
//插入元素
bool InsertHashTable(HashTable* &ht, int key, int value)
{
	
	if (Find(ht, key) != NULL)//如果找到了相对应的key的元素 则证明插入元素已经因为存在 因为键值key是惟一的
	{
		printf("Element has existed !");
		return false;
	}
	
	Element L = ht->Lists[Hash(key,ht->tableSize)];//找出 key所对应哈希桶的头结点
    
	//采用头插法插入元素
	Element q = new ListNode;
	if (!q) return false;
 
	q->data = value;
	q->key = key;
	q->next = L->next;
	L->next = q;
 
	return true;
 
}
//删除元素
bool DeleteHashNode(HashTable* &ht, int key)
{
	int i = Hash(key, ht->tableSize);
 
	List L = ht->Lists[i];//找到key所在哈希桶的头结点
	Element e = L->next;//找到第一个元素
 
	while (e != NULL && e->key!=key)
	{
		L = e;
		e = e->next;
	}
 
	if (e == NULL)
		return false; //如果没有找到key所对应的元素 返回false
 
	//从桶链表中删除元素e
	L->next = e->next;
	delete e;
	return true;	
}
int main()
{
	int elem[] = { 100,200,300,400,500,600,700,800,900 };
 
	HashTable *HT = NULL;
	HT = InitHashTable(DEFAULT_SIZE);
	if (!HT) return -1; 
 
	for (int i=0;i< sizeof(elem) / sizeof(elem[0]);i++)
	{
		InsertHashTable(HT, i, elem[i]);
	}
	
	//输出测试
	for (int i = 0; i < sizeof(elem) / sizeof(elem[0]); i++)
	{
		Element tmp = Find(HT, i);
		if (tmp!=NULL)
		{
			std::cout << tmp->data << " ";
		}
		
	}
	std::cout << std::endl;
 
	//把key=3删除 测试输出结果
	DeleteHashNode(HT, 3);
	std::cout << "删除key=3 value=400 后 输出哈希表" << std::endl;
	for (int i = 0; i < sizeof(elem) / sizeof(elem[0]); i++)
	{
		Element tmp = Find(HT, i);
		if (tmp != NULL)
		{
			std::cout << tmp->data << " ";
		}
 
	}
	std::cout << std::endl;
 
	system("pause");
}

到了这里,关于【数据结构篇C++实现】- 哈希表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构与算法】04 哈希表 / 散列表 (哈希函数、哈希冲突、链地址法、开放地址法、SHA256)

    一种很好用,很高效,又一学就会的数据结构,你确定不看看? 莫慌,每个概念都很好理解。 哈希表( Hash Table ),也称为 散列表 ,是一种数据结构, 用于存储键值对(key-value pairs) 。 键值对是一种数据结构,用于将键(key)与对应的值(value)相关联。在键值对中,键

    2024年02月09日
    浏览(59)
  • C++:【数据结构】哈希表

    哈希表(hash table)又叫散列表,是一种很实用的数据结构。首先来看一下百度给出的定义:散列表,是 根据关键码值(Key value)而直接进行访问的数据结构 。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放

    2024年02月03日
    浏览(35)
  • 【数据结构与算法C++实现】3、排序算法

    原视频为左程云的B站教学 外层循环 :n个数需要冒n-1个泡上去,剩下的一个必然是最小的。所以外层循环执行n-1轮 内层循环 :比大小,第1个泡需要比n-1次,第2个泡,比较n-2次… 选择: 每次从待排序序列中选择 最小的一个 放在已排序序列的后一个位置 原理类似于对扑克牌

    2024年02月11日
    浏览(44)
  • 手撕哈希表(HashTable)——C++高阶数据结构详解

    小编是双非本科大一菜鸟不赘述,欢迎米娜桑来指点江山哦(QQ:1319365055) 🎉🎉非科班转码社区诚邀您入驻🎉🎉 小伙伴们,打码路上一路向北,彼岸之前皆是疾苦 一个人的单打独斗不如一群人的砥砺前行 这是我和梦想合伙人组建的社区,诚邀各位有志之士的加入!! 社

    2023年04月08日
    浏览(24)
  • C++中的常见数据结构 --- 队列、栈、列表

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 队列、栈、列表是其中三个最为基础和常用的数据结构,它们在编程的世界中被广泛应用,为算法和数据处理提供了不可或缺的支持。今天来简单的介绍一下!以及他们在C++中的简单用法! 队列是一种常见

    2024年01月22日
    浏览(32)
  • [算法与数据结构]:LRU Cache 的原理与C++实现

    ​ LRU全称是Least Recently Used,即 最近最久未使用,是一种简单的缓存策略。顾名思义,LRU 算法会选出最近最少使用的数据进行淘汰。 ​ 那么什么是缓存(Cache)呢?缓存是一种提高数据读取性能的技术,可以有效解决存储器性能和容量的矛盾,是一种空间换时间的设计思想,比

    2024年01月20日
    浏览(32)
  • 【C++ | 数据结构】从哈希的概念 到封装C++STL中的unordered系列容器

    引入: 顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O( l o g 2 N log_2 N l o g 2 ​ N ),搜索的效率取决于搜索过程中元素的比较次数。 尽管平

    2024年01月22日
    浏览(33)
  • C++数据结构与算法详解:链表、栈、队列、树、二叉树和图结构的实现与应用

    链表是一种常见的数据结构由一系列节点按顺序连接而成,一般每个节点包含一个数据元素和一个指向下一个节点的引用。 链表有多种类型: 单向链表:每个节点只有一个指向下一个节点的引用 双向链表:每个节点有一个指向前一个节点和一个指向后一个节点的引用 循环链

    2024年02月04日
    浏览(31)
  • 哈希表-散列表数据结构

    哈希表也叫散列表,哈希表是根据关键码值(key value)来直接访问的一种数据结构,也就是将关键码值(key value)通过一种映射关系映射到表中的一个位置来加快查找的速度,这种映射关系称之为哈希函数或者散列函数,存放记录的数组称之为哈希表。 哈希表采用的是一种转换思

    2024年01月21日
    浏览(39)
  • 数据结构与算法—一维数组、二维数组、矩阵、顺序串、链接串的C++代码实现

    1、一维数组:ArrayOneD.h 数组这种数据结构可以看作线性表的推广。数组一般采用顺序存储的方法表示。 这是一个模板类 ArrayOneD 的实现,用于表示一维数组。它包括了 构造函数、拷贝构造函数、析构函数、重载下标运算符、重载赋值运算符、求数组长度、重新设置数组长度

    2024年02月07日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包