当前位置: 首页 > article >正文

CC44.【C++ Cont】哈希表的模拟实现

目录

1.知识回顾

2.题目

3.线性探测法

无穷大为什么取0x3f3f3f3f?

不建议使用0xffffffff或者0x7fffffff

设置成0x3f3f3f3f的好处

1.便于设置内存

2.值的数量级

哈希函数以及处理哈希冲突

哈希函数hash_func的设计

添加元素

查找元素

main函数

4.链地址法

哈希函数以及处理哈希冲突

哈希函数

添加元素

查找元素

main函数


1.知识回顾

参见CC43.【C++ Cont】哈希表的简介文章

2.题目

题目描述
维护一个数据结构,初始时为空.支持下面操作:
1 x 插入元素 x
2 x 查询 x 是否在数据结构中
现有n次操作,针对每次查询,输出是否在该数据结构中。
输入描述
第一行一个整数n,表示查询次数
之后 n 行,第i行两个整数op和x ,分别表示第op个操作以及元素 x 。
测试用例
12
1 1
1 2
1 3
1 4
1 5
2 2
1 6
2 5
1 7
2 8
2 4
2 10

下面使用两种方法来模拟实现:

3.线性探测法

模数的取法:最大数为10,因此找10*2==20(一般来说扩大两倍)后的质数:23,如果最大数为1e5,一般找+3后的1e5+3

则对于本题来说:创建一个有23个元素的数组hash_table,数组的每个int元素初始化为无穷大(一般取0x3f3f3f3f)

无穷大为什么取0x3f3f3f3f?

不建议使用0xffffffff或者0x7fffffff

按理来说,4字节的最大值应该是0xffffffff,但数组元素的类型为int,其取值为-2,147,483,648~2,147,483,647,其中2,147,483,647==0x7fffffff,那为什么无穷大不取0x7fffffff呢?

答:设置成0x7fffffff容易导致溢出的问题,可能会变成一个很小的负数,例如0x7fffffff+1用int类型表示,值为值为-2147483648,是一个很小的负数

设置成0x3f3f3f3f的好处

1.便于设置内存

一般来说设置数组元素的初始值是用for循环对每个元素单独设置,其实使用memset更快,因为0x3f3f3f3f是右4个0x3f组成的,便于设置内存空间,且速度比for循环快

测试两种方法的快慢:

#include <iostream>
#include <time.h>
using namespace std;
const int N = 9999999;
int hash[N];
int main()
{
	size_t beign1 = clock();
	for (int i = 0; i < N; i++)
		hash[i] = 0x3f3f3f3f;
	size_t end1 = clock();
	size_t beign2 = clock();
	memset(hash, 0x3f, sizeof(hash));
	size_t end2 = clock();
	cout <<"使用for循环设置所需要的时间" << end1 - beign1 <<"ms"<< endl;
	cout << "使用memset设置所需要的时间" << end2 - beign2 << "ms" << endl;
	return 0;
}

运行结果:

2.值的数量级

0x3f3f3f3f为1061109567,数量级为10的9次方级别的,而int类型的最大值2147483647也为10的9次方级别的,一般的数据不会大于10^9,且0x3f3f3f3f的两倍仍然小于int类型的最大值,无穷大加无穷大还是无穷大

数组初始化函数init

const int N=23,INF=0x3f3f3f3f;
int hash_table[N];
void init()
{
	memset(hash_table,0x3f,sizeof(hash_table));	
} 

哈希函数以及处理哈希冲突

之前在CC43.【C++ Cont】哈希表的简介文章中讲过除留余数法:hash(key) = key % N

但key有可能是负数,取模之后会变成负数!!!负数补正的操作:加上模数,但是正数加上模数会变大.因此统一再取一次模,简称(模加模),即

hash(key) = (key % N + N) % N

哈希函数hash_func的设计

例如返回数data映射的位置,则有hash(data) = (data % N + N) % N

如果直接像下面这样写会有问题,没有办法解决哈希冲突

int hash_func(int data)
{
	return (data % N + N) % N;
} 

改进:

//返回data映射的位置
int hash_func(int data)
{
	int id = (data % N + N) % N;//id<=N-1
	//h[id]!=INF找没有存过data的位置 
	//h[id]!=data找已经存过data的位置
	while (hash_table[id] != INF && hash_table[id] != data)
	{
		id++;//id<=N-1,id++最大为N,不可能超过N,不会有越界的风险
		if (id == N)//走到头了 
			id = 0;
	}
	return id;
}

注意while循环的条件!

添加元素

设添加元素data,显然要先找到data在哈希表中映射的位置,再为那个位置赋值

void insert(int data)
{
	int id = hash_func(data);
	hash_table[id] = data;
}

或者只写一行

hash_table[hash_func(data)]=data;

查找元素

bool find(int data)
{
	int id = hash_func(data);
	return hash_table[id] == data;
}

注:删除元素在这里不实现,比较麻烦

main函数

int main()
{
	init();//使用哈希表前一定要初始化
	int n;
	cin >> n;
	while (n--)
	{
		int op, x;
		cin >> op >> x;
		if (op == 1)
			insert(x);
		else if (find(x))
			cout << "yes" << endl;
		else
			cout << "no" << endl;
	}
	return 0;
}

运行结果:

4.链地址法

实现思想和C35.【C++ Cont】树的存储文章的链式前向星相同

mp[i]相当于头指针head[i]

哈希函数以及处理哈希冲突

哈希函数

int hash_func(int data)
{
	return (data % N + N) % N;//直接返回,不用像线性探测那样需要进一步处理
} 

添加元素

void insert(int data)
{
	int id=hash_func(data);//对对应在head[id]
	//先将data放入链表中
	id++;
	val[id]=data;
	//执行头插
	next[id]=head[id];
	head[id]=id; 
} 

查找元素

数字data,从链表的头head[id]开始向后查询即可

bool find(int data)
{
	int id=hash_func(data);//从链表的开头head[id]开始向后查询 
	for (int i=head[id];i;i=next[i])
		if (val[i]==data)
			return true;
			
	return false;
}

main函数

同之前写的

运行结果同上


http://www.kler.cn/a/590428.html

相关文章:

  • 利用github部署项目
  • 跨平台直播美颜SDK开发指南:如何兼容iOS、Android与Web
  • (笔记)Ubuntu 20编译Linux 4.19.262内核
  • Java创造型模式之原型模式详解
  • 基于 Docker 和 Flask 构建高并发微服务架构
  • uni-app+SpringBoot: 前端传参,后端如何接收参数
  • 解决git init 命令不显示.git
  • [特殊字符] 深度实战:Android 13 系统定制之 Recovery 模式瘦身指南
  • C++笔记-类和对象(下)
  • 苹果计划为 AirPods 配备实时对话翻译功能,或随 iOS 19 上线
  • 鸿蒙开发:什么是ArkTs?
  • 用旧的手机搭建 MQTT Broker-Node_red
  • 【Linux系列】文件压缩
  • 【前端效果】CSS实现动态渐变背景动画
  • 前端面试:babel-runtime 作用是啥?
  • 信创环境下TOP5甘特图工具对比:从功能到适配性测评
  • 蓝桥杯Python赛道备赛——Day7:动态规划(基础)
  • C++ map set pair
  • 《论分布式系统架构设计及其应用》架构师论文
  • Qt-QChart实现折线图