精华内容
下载资源
问答
  • 这意味着我们需要时刻留意哈希表的尺寸以及当前表中已有的元素数量。因为一旦哈希表中有太多元素,也将很难找到可用的位置来存放我们新插入的元素,因此这里我们需要引入一个重要的术语,负载系数(Load Factor)负载...

    开放寻址是其中一种缓解散列冲突的编程技术,当使用开放寻址作为冲突解决技术时,键值对存储在表(数组)中,而不是像单独链表那样的数据结构中。这意味着我们需要时刻留意哈希表的尺寸以及当前表中已有的元素数量。因为一旦哈希表中有太多元素,也将很难找到可用的位置来存放我们新插入的元素,因此这里我们需要引入一个重要的术语,负载系数(Load Factor)

    负载系数

    4e4286bc17493b8ae6f14ae425984721.png


    其实就是表中已有元素个数和表尺寸的比例,我们要密切关注这个系数的是因为哈希表的O(1)恒定时间行为假设负载因子k保持一定的固定值,这意味着一旦k>阈值,我们就需要增加表的大小(理想情况下是指数增长,例如,两倍)

    517b898d56cdeb4e6334a45e1eec71dd.png


    在上图中,你会看到有两种缓解冲突的方法,即单独链表和线性探测(Linear Probing),在开放寻址(线性探测)技术看来,一旦达到某个阀值,它的时间复杂度就会呈现指数级恶化的趋势

    当我们想要将键值对插入哈希表时,我们对键进行哈希处理并获得该键值对所属位置的原始位置。如果我们的键被散列到的位置被占用(此时出现了冲突),对于开放寻址来说,同一个位置中不允许有两个键的,这不是数组的工作方式,我们要做的是使用一个探测序列函数(Probing Seque Function) 这里简称p(x),因为我们已从散列函数获取了冲突点的所在位置,现在我们使用p(x)进行探测直到在沿途发现一个空闲的位置为止

    探测函数

    您可以提出无限数量的探测序列,这里仅提及一些常见的探测函数:

    • 线性探测(Linear Probing):p(x)= kx + b其中a,b是常数
    • 二次探测(Quaratic Probing):p(x)= ax ^ 2 + bx + c,其中a,b,c是常数
    • 双重散列(Double Hashing):p(k,x)= x * h(k),其中h(k)是辅助s散列函数
    • 伪随机数发生器(Pseudo Random Number Generator): p(k,x)= x*RNG(h(k),x)其中RNG是以H(k)作为种子的随机数生成器函数

    本篇仅介绍线性探测函数进行线性探测,因此给定输入参数x,当我们进行探测时,我们通常会将变量x初始化为0或1作为一个起点,如果我们找不到空闲的位置,会依次将x增加1,对以上所有这些探测函数都是一样的

    开放寻址的通用算法

    接下来,这是一个通用的开放寻址插入算法,假设我们有一个表的尺寸为n,开放寻址算法首先会初始化变量x=1,因为x是一个变量,我们要用它来探测,每当我们未能到达闲置的位置时,都需要递增x,然后我们通过散列函数获得keyHash,而实际上我们首先要查看表的索引,当表索引被占用意味着它不为空,那么新索引就是我们散列的最初位置(keyHash所指向的起始索引)加上探测函数的总和再于表尺寸N取模运算得到整数,由于我们总是回到表里,在循环中要递增x。下一次当我们在不同的位置探测时,在while循环中,最终我们会找到一个空闲的位置

    x=1keyHash=h(k)index=keyHashwhile table[index]!=NULL:      index=(keyHash+p(k,x)) mod N      x=x+1insert(k,v,index) 

    死循环地狱(Chaos with Cycle)

    由于我们知道负载系数被控制在一定的范围内,所以这里有个问题,就是开放寻址中的探测函数存在缺陷--死循环地狱,以表尺寸N为模的大多数随机选择的探测序列将产生比表大小N更短的循环。当您尝试插入一个键-值对并且循环中的所有存储桶都被占用时,这将成为灾难性问题,因为您将陷入无限循环,这在一些老外谈及哈希表的相关文章中有一个非常有趣的昵称叫死循环地狱(Chaos with Cycle)

    为了生动说明什么叫死循环地狱,我们这里看一个例子,这里有一个尺寸为12的哈希表,并且使用开放寻址插入了一些键值对,,该哈希表已经部分填充。 占用的单元格填充有键值对(Ki,Vi)和带有空令牌Φ的空单元格,如下图所示

    7002536b795afe4966059f7707786b97.png

    假设我们使用探测序列函数p(x)=4x,并且在表中插入一个新的键值对,并且该键值对的散列值为8,即h(x)=8这意味着我们会在索引8的位置插入该键值对,但是该位置已被占用,因为这里已经有简直对(k5,v5),所以我们该怎么办呢?接下来,我们需要进行探测,所以我们计算: index=h(k)+p(1)=8+4 mod 12=0

    此时,如下图,此时探测函数会跳转到索引为0的位置,糟糕的是索引1的位置也被占用了,因为(k1,v1)已经存在.

    a78a462e9e581b904abb5dc9cd3cc92b.png
    • 当x=2时,即index=h(k)+p(2)=(8+8) mod 12=4,探测函数会跳跃到索引4的位置,同样这里也是被占用的,如此类推
    • 当x=3时,即index=h(k)+p(3)=(8+12) mod 12=8,p(x)跳跃到索引8的位置,该位置被占用
    • 当x=4时,即index=h(k)+p(4)=(8+16) mod 12=0,p(x)跳跃到索引0的位置,该位置被占用
    • 当x=5时,即index=h(k)+p(5)=(8+20) mod 12=4,p(x)跳跃到索引4的位置,该位置被占用
      .....

    这样尽管我们具有探测函数,但这种特定的情况下它一直在一个死循环里面一直做一些毫无意义的事情。

    由这个例子我们可知探测函数存在缺陷,他们产生的周期短于表的尺寸,因此,我们要如何处理产生小于表大小的周期的探测功能?一般来说,一致的看法是我们不处理这个问题,相反,我们通过将探测函数的范围限制在那些产生长度为N的循环的函数上来避免这个问题,我们选择的那些产生的周期正好为N的探测函数,并且这些探测函数确实存在。

    线性探测、二次探测和双重散列等技术都受到死循环地狱问题的影响,这就是为什么与这些方法一起使用的探测函数非常特殊的原因。这是一个很大的话题,将是接下来几篇文章会重点讲述这些,我们目前需要做的是重新定义非常具体的探测函数,这些函数会产生一个循环长度为表尺寸N,并且避免无法插入元素或陷入无限循环

    注意,开放寻址对使用的哈希函数和探测函数非常敏感。如果使用单独的链接作为冲突解决方法,则不必担心此问题。

    小结

    我们本篇用一个反例生动地介绍了开放寻址插入算法的底层是由探测函数和散列函数相互作用的结果,同时我们也介绍了一些探测函数的固有缺陷,就是死循环地狱,下一篇我们会详细讨论线性探测函数的原理,敬请期待。


    链接:https://www.jianshu.com/p/b8c47701dd07

    展开全文
  • C++哈希表的实现

    2021-01-23 21:22:15
    C++哈希表的实现前言源码如下: 前言 本篇文章为笔者的读书笔记,未经允许请勿转载。如果对你有帮助记得点个赞(●’◡’●) 本文主要讲的哈希表的创建和使用, 源码如下: main #include <iostream> #...

    C++哈希表的实现


    前言

    本篇文章为笔者的读书笔记,未经允许请勿转载。如果对你有帮助记得点个赞(●’◡’●)
    本文主要讲的哈希表的创建和使用。哈希表的存储方式是不可逆,存储的value是经过哈希算法得到,所以只是获得存储中的value是毫无意义的,它只是密码经过哈希算法后得到的数值。


    源码如下:

    main

    #include <iostream>
    #include<string>
    #include"HashTable.hpp"
    using namespace std;
    void test()
    {
        //创建一个有15个节点的哈希表;
        HashTable<string> ht(15);
        
        bool is;
        cout << boolalpha;
        is = ht.insert(11, "A");
        cout << is << endl;
        is = ht.insert(22, "B");
        cout << is << endl;
        is = ht.insert(33, "C");
        cout << is << endl;
        is = ht.insert(44, "D");
        cout << is << endl;
        is = ht.insert(45, "D");
        cout << is << endl;
        is = ht.insert(55, "E");
        cout << is << endl;
        is = ht.insert(66, "F");
        cout << is << endl;
        is = ht.insert(77, "G");
        cout << is << endl;
        is = ht.insert(88, "H");
        cout << is << endl;
    
        cout << "-------------------------" << endl;
        ht.out();
        cout << "-------------------------" << endl;
    
        try
        {
            cout << ht.search(44) << endl;
        }
        catch (const char* str)
        {
            cout << str << endl;
        }
        ht.remove(45);
    
        cout << "-------------------------" << endl;
        ht.out();
        cout << "-------------------------" << endl;
    
        HashTable<string> ht1(ht);//调用拷贝构造
        try
        {
            cout << ht1.search(45) << endl;
        }
        catch (const char* str)
        {
            cout << str << endl;
        }
        cout << "-------------------------" << endl;
        ht1.out();
    
    }
    int main()
    {
        test();
        system("pause");
        return 0;
    }
    测试结果:
    true
    true
    true
    true
    true
    true
    true
    true
    true
    -------------------------
    [0]->nil
    [1]->(11:A)->(55:E)->nil
    [2]->nil
    [3]->nil
    [4]->nil
    [5]->nil
    [6]->nil
    [7]->(88:H)->nil
    [8]->(44:D)->nil
    [9]->(45:D)->nil
    [10]->nil
    [11]->(77:G)->nil
    [12]->(66:F)->nil
    [13]->(22:B)->(33:C)->nil
    [14]->nil
    -------------------------
    D//查询的数据
    -------------------------
    [0]->nil
    [1]->(11:A)->(55:E)->nil
    [2]->nil
    [3]->nil
    [4]->nil
    [5]->nil
    [6]->nil
    [7]->(88:H)->nil
    [8]->(44:D)->nil
    [9]->nil
    [10]->nil
    [11]->(77:G)->nil
    [12]->(66:F)->nil
    [13]->(22:B)->(33:C)->nil
    [14]->nil
    -------------------------
    没有这个数据//删除的数据
    -------------------------
    [0]->nil
    [1]->(11:A)->(55:E)->nil
    [2]->nil
    [3]->nil
    [4]->nil
    [5]->nil
    [6]->nil
    [7]->(88:H)->nil
    [8]->(44:D)->nil
    [9]->nil
    [10]->nil
    [11]->(77:G)->nil
    [12]->(66:F)->nil
    [13]->(22:B)->(33:C)->nil
    [14]->nil
    

    HashTable.hpp

    #pragma once
    #include<list>
    #include<utility>//pair
    template <class T>
    class HashTable
    {
    	public:
    		HashTable(int len):length(len),count(0) 
    		{
    			data = new hash_table[length]();//堆内存
    			//hash_table data[length]();栈内存
    
    		}
    		//拷贝构造
    		HashTable(const HashTable& ht):length(ht.length)
    		{
    			this->data = new hash_table[length]();
    			for (int i = 0; i < length; i++)
    			{
    				this->data[i] = ht.data[i];
    			}
    		}
    		~HashTable()
    		{
    			if (data)
    			{
    				delete[] data;
    				data = nullptr;
    			}
    		}
    		//插入数据
    		bool insert(int key, T val)
    		{
    			//通过hash运算再求余的方法插入位置,也可以用其他办法,自由发挥。
    			int base = hash(key) % length;
    			for (auto e:data[base])
    			{
    				if (e.first == key)
    				{
    					return false;
    				}
    			}
    			//数组里面的每一个元素都是一个list,二list里面的每一个元素是pair。
    			data[base].push_back({ key,val });
    			return true;
    		}
    		//查找数据
    		T search(int key)
    		{
    			int base = hash(key) % length;
    			for (auto e : data[base])
    			{
    				if (e.first == key)
    				{
    					return e.second;
    				}
    			}
    			throw "没有这个数据";
    		}
    		//删除数据
    		bool remove(int key)
    		{
    			int base = hash(key) % length;
    			for (auto it = data[base].begin(); it != data[base].end(); it++)
    			{
    				if (it->first == key)
    				{
    					data[base].erase(it);
    					return true;
    				}
    			}
    			return false;
    		}
    		//输出
    		void out()
    		{
    			for (int i = 0; i < length; i++)
    			{
    				std::cout << "[" << i << "]" << "->";
    				for (const auto& e : data[i])
    				{
    					std::cout <<"("<< e.first << ":" << e.second <<")"<< "->";
    				}
    				std::cout << "nil\n";
    			}
    		}
    private:
    	using hash_table = std::list<std::pair<int, T>>;
    	int length = 0;
    	int count = 0;
    	hash_table * data=nullptr;
    	//计算hash值的函数,对于频繁访问的hash函数可以用inline来使用更少的栈内存
    	inline int hash(int key)
    	{	
    		return key^10;
    	}
    };
    

    哈希表的实现原理不懂的可以参考一下
    可视化哈希表

    展开全文
  • C++哈希

    2020-08-24 10:32:03
    哈希表原理3.c++哈希表接口的使用 1.什么是哈希表 散列表(Hash table 也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过关键码值映射到表中一个位置来访问记录,以加快查找的...

    1.什么是哈希表

    散列表(Hash table 也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

    2.哈希表原理

    给定表M,存在函数f(key),对任意给定的关键字key,代入函数后若能得到包含关键字的记录在表中的地址,则称表M为哈希表(Hash Table),函数f(key)为哈希函数。
    基本概念:
    (1)若关键字为k,则其值存放在f(k)的存储位置上。由此,不需要比较便可直接取得所查记录。称这个对应关系f为散列函数,按这个意思建立的表为散列表。
    (2)对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为碰撞。具有相同函数值的关键字对该散列函数来说乘坐同义词。综上所述,根据散列函数f(k)和处理碰撞的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址中的“像”作为记录在表中的存储位置,这种表便称为散列表,这一映射过程称为散列造表或散列,所得的存储位置称散列地址。
    (3)若对于关键字集合中的任一关键字,经散列函数映射到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数,这就使关键字经过散列函数得到一个“随机的地址”,从而减少碰撞。
    几种常见的哈希表设计方法以及解决哈希冲突的方法:
    哈希表的原理和使用
    完美哈希(重点,面试中经常会被问到)
    当关键字的集合是一个不变的静态集合(Static)时,哈希技术还可以用来获取出色的最坏情况性能。如果某一种哈希技术在进行查找时,其最坏情况时间复杂度是O(1) ,则称其为完美哈希(Perfect Hashing)。
    完美哈希表的设计:https://blog.csdn.net/tiankong_/article/details/76769230

    3.c++中哈希表接口的使用

    头文件#include<hash_map>,并非标准库中的,但绝大部分都实现
    http://blog.csdn.net/tiankong_/article/details/76718467

    展开全文
  • 利用C++哈希表的方法实现电话号码查询系统 如何运用哈希表的算法 深刻理解哈希表 利用C++哈希表的方法实现电话号码查询系统 如何运用哈希表的算法 深刻理解哈希表
  • c++ 哈希

    2020-10-26 11:10:00
    文章目录前言一、什么是哈希/散列?二、什么是哈希冲突?如何解决1.开放定制法(闭散列)2.拉链法(开散列)三、如果哈希表中冲突得很厉害怎么办?四、unordered_map和unordered_set跟map/set的区别?总结面试题: 前言...


    前言

    在学习完map和set后,我们会惊讶于它的效率O(logn)。
    但是unordered_map和unordered_set的效率是O(1),而它的底层原理就是哈希表

    一、什么是哈希/散列?

    构造一种存储结构,通过某种函数使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)

    二、什么是哈希冲突?如何解决

    不同关键字通过相同哈希哈数计算出相同的哈希地址,会导致哈希冲突

    1.开放定制法(闭散列)

    1. 线性探测
      线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
    2. 二次探测
      找下一个空位置的方法为: = ( + )% m,或者: = ( - )% m。其中:i = 1,2,3…, 是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表的大小

    2.拉链法(开散列)

    开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

    三、如果哈希表中冲突得很厉害怎么办?

    1.​控制负载因子
    ​2.使用拉链法,如果一个冲突链到达一定长度,就不用链表,转换成红黑树

    四、unordered_map和unordered_set跟map/set的区别?

    一个底层是哈希表,时间复杂度O(1),遍历出来无序

    ​一个是红黑树,时间复杂度O(logN) ,遍历出来有序

    map和set是双向迭代器,unordered_map和unordered_set是单向迭代器

    总结

    应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a <= 0.7,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间。

    面试题:

    给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中?
    解决方案:
    1.排序,二分查找
    2.set/unordered_set(红黑树/哈希表)存起来再查找
    以上方案的问题:数据量太大,放不到内存(16G)
    那么解决方法是:位图,高效且节省空间 (500M)

    展开全文
  • C++ 哈希

    2021-02-22 20:35:28
    哈希表的基本介绍: 散列表( Hash table,也叫哈希表)是根据关键码值(key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,...
  • c/c++ 哈希表 hashtable

    2018-08-15 06:54:00
    c/c++ 哈希表 hashtable 概念:用key去查找value 实现hash函数有很多方法,本文用除留余数法。 除留余数法的概念: 取一个固定的基数的余数,注意不能用偶数,用偶数的话,分布会不均匀 发生冲突时,用链地址法解决 ...
  • Hashed Sharding哈希分片使用哈希索引来在分片集群中对数据进行划分。哈希索引计算某一个字段的哈希值作为索引值,这个值被用作片键。哈希分片以减少定向操作和增加广播操作作为代价,分片集群内的数据分布更加均衡...
  • c++ 哈希_浅谈哈希

    2020-11-24 03:40:52
    1 简介哈希表:又称散列表,是一种根据给定的关键字来计算关键字在表中地址的数据结构,即散列表建立了关键字和存储地址之间的一种直接映射关系。哈希函数:又称散列函数,将给定关键字映射为该关键字对应的地址的...
  • 分布式哈希(Distributed Hashing)分布式环境中,需要进行分布式哈希来进行负载均衡,减少忙碌服务器的负载。例如,对一个键(key)做了哈希后,需要确定它保存在哪个服务器上面。一致性散列(consistent hash)函数的...
  • C++哈希查找法

    2019-06-05 12:52:24
    思路:这里的哈希表为数组。 哈希函数:h=key%m 构建随机数组初始化为-1并根据哈希函数放在数组指定下标。 查找数据:求数据的哈希值,若匹配成功返回数组下标,否则线性探测下一个位置。 #include<iostream>...
  • 分布式哈希表与传统的哈希表在功能上是类似的,他们最关键的功能只有两个:保存数据和获取数据。保存数据当某个节点得到了新加入的数据 K/V,它会先计算自己与新数据的 key 之间的距离;然后再计算它所知道的其它...
  • 1 散列表(哈希表)它用一个散列函数把字典的数对映射到一个散列表的具体位置。散列表的每一个位置叫做桶;对关键字为k的数对,f(k)是起始桶;桶的数量等于散列表的长度或大小。因为散列表可以把若干个关键映射到同一...
  • C++哈希表用法

    千次阅读 2019-08-31 11:08:18
    #include<iostream> #include<unordered_map> using namespace std; int main(){ unordered_map<int,string>...//创建一个key为string类型,value为int类型的unordered_map ... ...
  • 在这种情况下,称为Pass The Hash(传递哈希)的技术被广泛应用,使审核员成为计算机上的管理员。HTLM协议NTLM协议是在Microsoft环境中使用的一种身份验证协议,特别是,它允许用户向服务器证明自己是谁,以便使用该...
  • C++ 哈希表的原理

    2021-01-15 15:01:56
    Hash也称为散列、哈希。 其基本的原理就是把任意长度的输入、通过Hash算法变成固定长度的输出。 这个映射的规则就是对应的Hash算法,而原始数据映射后的二进制串就是哈希值 比如常用加密方式:MD5和SHA都是Hash算法...
  • 首先哈希算法主要是用来查找元素,效率非常快 原理: 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快...
  • 哈希率是比特币网络的处理能力的衡量单位,代表矿商在区块链上确认事务的速度。哈希率是衡量比特币网络安全的重要指标,为了安全,比特币网络必须进行高强度的数学运算。哈希率越高,它就越能抵御诸如51%攻击等的...
  • C++标准库中使用的unordered_map底层实现是哈希表,关于哈希表的一些基础知识,我看了公众号代码随想录里面的推文:《关于哈希表,你该了解这些!》,有了基本的认识。 哈希表是什么:哈希表是根据关键码的值而直接...
  • 哈希的引入及概念 哈希表的构造 哈希冲突的产生 解决哈希冲突的方法及代码实现 一.哈希的引入及概念 引入:当在顺序结构(如数组)或者平衡树(平衡二叉树)中查找一个元素时,必须要经过多次与内部元素进行比较的过程...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,416
精华内容 1,366
关键字:

c++哈希

c++ 订阅