哈希表hash_table

news2024/11/26 19:39:04
一个人为什么要努力? 我见过最好的答案就是:因为我喜欢的东西都很贵,我想去的地方都很远,我爱的人超完美。

文章目录

  • 哈希表的引出
    • unordered系列的关联式容器
  • 底层结构
    • 哈希的概念
  • 开放寻址法
  • 拉链法(哈希桶)
    • 拉链法的结构
    • 什么是拉链法
  • 总结

哈希表的引出

unordered系列的关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2 N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,本文中只对unordered_map和unordered_set进行介绍,unordered_multimap和unordered_multiset可查看文档介绍。

底层结构

unordered系列的查询效率高是因为底层运用了哈希结构

哈希的概念

顺序表以及平衡树中元素的关键码和数据的大小没有直接对应的关系,因此我们在顺序表和平衡树中需要对关键码或者是数据进行逐一比对,在平衡树中我们使用关键码的大小关系从而减少比对次数,而顺序表我们只能逐一对数据进行比对才可以确定我们想要的元素,我们发现查找中我们中间会因为查找大量的无关元素而浪费时间,平衡树和顺序表的区别就在于,顺序表是逐个查找而平衡树则是通过不断的判断查找的方向从而减少查询的次数。所以查找的效率主要在于能不能减少无谓的查找。
理想情况下理想的情况下的查找是不需要经过任何比对,直接通过可以直接找到数据元素的,但是这种方式是理想的我们无法做到只能尽可能的接近理想状态,那么这里就引出了我们的哈希表。
哈希表就是通过键值对和数据的映射关系从而可以在接近O(1)的时间内找到对应的数据。
那么哈希表的插入等情况是怎么进行的呢?其实就是通过特定的函数来算出该数据的关键码从而在关键码中插入。
列如我们的函数设为关键码=数据%10然后我们要插入的数值为 1,2,13,16。那么我们该如何进行插入呢其实很简单,那就是用1%10,2%10,13%10,16%10,算出来他们的关键码(关键码其实就是可以理解为要插入的数据的下标值)然后通过关键码进行存储

搜索元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

对于上面的这个方法我们减少了关键码的比较因此搜索的速度非常的快,但是这里也会有问题那就是冲突,因为我们在插入的时候是可能会出现一对多的情况的,比如说上面的(%10)我们会知道20%10,30%10都是0这里就出现了冲突也就是说不同的关键字通过相同的函数进行计算可能会得到相同的关键码,那么这里处理冲突就分为两种了,拉链法(哈希痛)和开放寻址法。

开放寻址法

首先讲解一下开放寻址法开放寻址法其实就是当前的位置产生冲突的时候就去找下一个位置,就像我们去蹲坑这个坑位有人了我们就去下一个坑位一直到最后一个坑位都有人的话我们就去第一个坑位继续往后看,这里我们会发现开放寻址法的话必须保证这个厕所有坑位,那么其实我们哈希表的底层结构中是有办法保证,他肯定有空余位置的。
在这里插入图片描述
这里的线段编号代表的是查找空余坑位的次数。那么用代码的表示其实就是下面这样字

	bool Insert(const pair<K, V>& kv)
	{
		// 扩容
		//if ((double)_n / (double)_table.size() >= 0.7)
		if (_n*10 / _table.size() >= 7)
		{
			size_t newSize = _table.size() * 2;
			// 遍历旧表,重新映射到新表
			HashTable<K, V, HashFunc> newHT;
			newHT._table.resize(newSize);

			// 遍历旧表的数据插入到新表即可
			for (size_t i = 0; i < _table.size(); i++)
			{
				if (_table[i]._state == EXIST)
				{
					newHT.Insert(_table[i]._kv);
				}
			}

			_table.swap(newHT._table);
		}

		// 线性探测
		HashFunc hf;
		size_t hashi = hf(kv.first) % _table.size();
		while (_table[hashi]._state == EXIST)
		{
			++hashi;
			hashi %= _table.size();
		}

		_table[hashi]._kv = kv;
		_table[hashi]._state = EXIST;
		++_n;

		return true;
	}

上面的插入代码用了模板泛型编程我给解读一下首先哈希表的插入我们首先就是要根据数据和函数从而计算出我们的关键码,另外就是我们在插入的时候为了避免表满了的情况我们设置的会有一个值也就是(当前插入节点)/(总长度)这样一个比值,当这个比值我们一般设为0.7当比值大于等于0.7的时候我们就会对原来的哈希表进行扩容。但是这里有个问题那就是我们在进行扩容的时候由于我们在计算关键码的时候做分母的值一般为目前容器的容量那么当我们扩容后这个容器的容量就会产生变化此时已经插入进入的值在扩容后的容器中位置是会发生改变的。那么有什么好的解决方法呢?

其实很简单我们只需要再开辟一个新容器然后把原来的容器中的值插入到新容器中再让新容器与就容器进行swap一下就可以了。(上面的代码中写的有)

拉链法(哈希桶)

拉链法的结构

上面我们讲了开放寻址法,开放寻址法有什么缺点呢?他的缺点就是说我们在寻找坑位的时候可能需要我们找到末尾再从头开始找就像我们去厕所的时候会发生可能你从这个位置一直找到最后一个坑位之后再回头才发现原来第一个坑位就是空余的。因此这时候就会导致我们查找的效率较慢,那么有什么办法呢?拉链法再处理的时候就比较不错。

什么是拉链法

如果我们把开放寻址法看成一个一维数组的话那么拉链法就是一个二维的数组,我觉得用二维数组也可以很好的讲述拉链法我给大家写一个很朴素的模仿拉链法的代码大家可以看一下

#include<iostream>
using namespace std;
int num[1010][1010];//假设num是我们要插入元素的容器这里呢我们"假设!!!!"当这个位置是0的时候就代表没有元素插入
int main()
{
	int N = 101;//假设我们的公式为(关键码)i=n(存储的数据)%N(101也是假设)
	int n;
	cin >> n;
	int i = n % N;//找到了要插入的位置是第i列
	for (int j = 0; j < 1010; j++)//从第i列的第一行往下找
	{
		if (num[j][i] == 0)
		{
			num[j][i] = n;
		}
	}
	return 0;
}

正如上面的代码所示就是一个朴素的拉链法那么我们在实际中应该是什么的组合呢?相信大家不难知道我们实际上的组合应该是vector+list的组合代码如下

bool insert(const T& data)
		{
			HashFunc func;
			if (Find(data.first))
			{
				return false;
			}
			if (_n == _table.size())//当原来的容器满了的时候
			{
				vector<Node*>newtable;//开辟一个新容器
				size_t newsize = _n * 2;//设置新容器的容量
				newtable.resize(newsize, nullptr);//开辟容器
				for (int i = 0; i < _table.size(); i++)//讲原来容器中的值插入到新容器中
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = func(cur->data) % newsize;
						cur->_next = newtable[hashi];
						newtable[hashi] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newtable);//将新就容器进行swap
			}
			size_t hashi = func(data) % _table.size();
			Node* cur = new Node(data);
			cur->_next = _table[hashi];
			_table[hashi] = cur;
			++_n;
			return true;
		}

那么这里也有扩容,为什么这里也有扩容呢,是因为拉链法也有一个极端情况那就是很多数据甚至是全部数据在一条链上因此拉链法也有扩容而拉链法的扩容条件一般就是当插入元素个数与链表长度相同的时候。我们需要扩容。

总结

哈希表的优点
哈希最大的优点我相信就是哈希减少了比较的次数,从而使我们的查找效率都更加的快速。

哈希的缺点
哈希的缺点的我认为比较明显的一个就是当我们插入元素的时候可能会遇到扩容那么就会导致某一个元素插入的时候会比较慢但是总体而言利大于弊。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1053184.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++进阶】:C++11

C11 一.统一列表的初始化1.{}初始化2.initializer_list 二.声明1.decltype2.nullptr 三.右值引用和移动语义1.左值和右值1.转义语句2.完美转发 四.可变参数模板1.基本概念2.STL里emplace类接口 五.lambda表达式六.新的类功能 一.统一列表的初始化 1.{}初始化 在C98中&#xf…

CSS文本属性和Emmet语法

CSS文本属性 有预定的颜色值 red,green,blue 十六进制 #ff00000,#FF5500 ,#29D794 RGB代码 rgb(255,0,0)或rgb(100%,0%,0%) <head> <style>p {text-align: right;//让字体向右移动text-decoration: normal;}a {text-decoration: none;//去掉连接的下划线color: …

beego---ORM相关操作

Beego框架是go语言开发的web框架。 **那什么是框架呢&#xff1f;**就是别人写好的代码&#xff0c;我们可以直接使用&#xff01;这个代码是专门针对某一个开发方向定制的&#xff0c;例如&#xff1a;我们要做一个网站&#xff0c;利用 beego 框架就能非常快的完成网站的开发…

【随笔记】C++ condition_variable 陷阱

问题说明 通过 std::condition_variable 来实现超时等待&#xff0c;会受到系统时间变化的影响&#xff0c;系统时间倒退修改就会导致延后唤醒&#xff0c;系统时间提前将会导致提前被唤醒&#xff0c;返回结果仍为超时。 这种问题只有在系统时间发生变化的时候才会出现&…

MyBatisPlus(七)等值查询

等值查询 条件查询&#xff1a;使用 Wrapper 对象&#xff0c;传递查询条件。 QueryWrapper&#xff08;不要使用&#xff09; 代码 Testvoid eq() {QueryWrapper<User> wrapper new QueryWrapper<>();wrapper.eq("name", "张三");List<…

装饰器模式详解和实现(设计模式 二)

装饰器模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许你动态地将对象添加到现有对象中&#xff0c;以提供额外的功能&#xff0c;同时又不影响其他对象。 实现示例 1.定义一个接口或抽象类&#xff0c;表示被装饰对象的公共接口 //抽…

CSS 滚动驱动动画 view-timeline-inset

view-timeline-inset 语法例子&#x1f330; 正 scroll-padding 为正正的 length正的 percentage 负 scroll-padding 为负负的 length负的 percentage 兼容性 view-timeline-inset 在使用 view() 时说过, 元素在滚动容器的可见性推动了 view progress timeline 的进展. 默认…

数据结构—快速排序(续)

引言&#xff1a;在上一篇中我们详细介绍了快速排序和改进&#xff0c;并给出了其中的一种实现方式-挖坑法 但其实快速排序有多种实现方式&#xff0c;这篇文章再来介绍其中的另外两种-左右指针法和前后指针法。有了上一篇挖坑法的启示&#xff0c;下面的两种实现会容易许多。 …

面试记录_

1&#xff1a;面试杉岩数据&#xff08;python开发&#xff09; 1.1.1 选择题 for(int i0;i<n;i){for(int j0;j<n;jji) } }O(n) * (O(0) O(n/1) O(n/2) O(n/3) ... O(n/n)) 在最坏情况下&#xff0c;内部循环的迭代次数为 n/1 n/2 n/3 ... n/n&#xff0c;这是…

电脑找不到vcruntime140_1.dll丢失的解决方法-一键修复教程

vcruntime140_1.dll是一个动态链接库文件&#xff0c;它是Microsoft Visual C Redistributable的一部分。这个库文件包含了一些运行时函数&#xff0c;用于支持各种软件程序的正常运行。当一个程序需要调用这些函数时&#xff0c;它会通过加载vcruntime140_1.dll文件来实现。因…

MySQL基础进阶

文章目录 MySQL基础进阶 约束 \color{red}{约束} 约束约束的概念和分类约束的概念约束的分类 非空约束概念语法 唯一约束概念语法 主键约束概念语法 数据库设计 \color{red}{数据库设计} 数据库设计软件的研发步骤数据库设计概念数据库设计的步骤表关系一对一一对多&#xff08…

Vue3父子组件数据传递

getCurrentInstance方法 Vue2中&#xff0c;可以通过this来获取当前组件实例&#xff1b; Vue3中&#xff0c;在setup中无法通过this获取组件实例&#xff0c;console.log(this)打印出来的值是undefined。 在Vue3中&#xff0c;getCurrentInstance()可以用来获取当前组件实例…

el-menu 导航栏学习(1)

最简单的导航栏学习跳转实例效果&#xff1a; &#xff08;1&#xff09;index.js路由配置&#xff1a; import Vue from vue import Router from vue-router import NavMenuDemo from /components/NavMenuDemo import test1 from /components/test1 import test2 from /c…

1200*B. Sorted Adjacent Differences(构造)

Problem - 1339B - Codeforces 解析&#xff1a; 题目要求每相邻两个值差的绝对值相等或递增。 先排序&#xff0c;可以想到我们先取两侧的数肯定相距最远&#xff0c;然后靠中心每次取两个数&#xff0c;这样符合题目要求。 直接遍历&#xff0c;先取的是答案靠后的数据&…

基于微信小程序的校园快递代取系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言用户微信小程序端的主要功能有&#xff1a;配送员微信小程序端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获…

python爬取沈阳市所有肯德基餐厅位置信息

# 爬取沈阳所有肯德基餐厅位置信息 import requests import json import reurl http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?opkeyword headers {User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0…

Ipa Guard使用手册

使用手册 开始使用ipa guard代码混淆界面介绍文件混淆-界面介绍安装和登录Ipa Guard 相关教程 下载安装Ipa Guardipaguard注册和登录 下载安装Ipa Guard 可以前往ipaguard工具官网下载&#xff0c;工具是免费下载&#xff0c;免费体验使用的。下载地址是https://www.ipaguard.…

关于工作中爬取网站的一些思路记录

声明&#xff1a;只是因为工作中需要&#xff0c;且基本不会对别人的网站构成什么不好的影响&#xff0c;做个思路记录&#xff01;&#xff01;&#xff01; 尊重网站所有者、控制请求频率、遵守网站规则、尊重个人隐私 平常工作中难免会遇到需要爬取别人网站的需求&#xff0…

华为云云耀云服务器L实例评测 | 实例评测使用之硬件性能评测:华为云云耀云服务器下的硬件运行评测

华为云云耀云服务器L实例评测 &#xff5c; 实例评测使用之硬件性能评测&#xff1a;华为云云耀云服务器下的硬件运行评测 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀…

linux系统中wifi移植方法

第一&#xff1a;移植wifi现象 在linux系统的RK3399中空板上&#xff0c;确认rk3399中控板linux系统已经可以正常运行。本操作是在rk3399中控板上的WIFI模块&#xff0c;linux内核加载wifi驱动后&#xff0c;再配置上正确的wifi密码&#xff0c;就可以实现rk3399中控板通过wifi…