哈希表(闭散列、拉链法--哈希桶)

       哈希表,也称散列表,是一种通过key值来直接访问在内存中的存储的数据结构。它通过一个关键值的函数(被称为散列函数)将所需的数据映射到表中的位置来访问数据。

关于哈希表,主要为以下几个方面:

一、哈希表的几种方法

1、直接定址法:取关键字key的某个线性函数为散列地址,如Hash(key) = key  或 Hash(key) = A*key+B;A,B为常数

2、除留取余法:关键值除以比散列表长度小的素数所得的余数作为散列地址。Hash(key) = key % p;

3、平均取中法:先计算构成关键码的标识符的内码的平方,然后按照散列表的大小取中间的若干位作为散列地址。

4、折叠法:把关键码自左到右分为位数相等的几部分,每一部分的位数应与散列表地址位数相同,只有最后一部分的位数可以短一些。把这些部分的数据叠加起来,就可以得到具有关键码的记录的散列地址。分为移位法和分界法。

5、随机数法:选择一个随机函数,取关键字的随机函数作为它的哈希地址。

6、数学分析法:设有N个d位数,每一位可能有r种不同的符号。这r种不同的符号在各位上出现的频率不一定相同,可能在某些位上分布均匀些,每种符号出现的机会均等;在某些位上分布不均匀,只有某几种符号经常出现。可根据散列表的大小,选取其中各种符号分布均匀的若干位作为散列地址。


在这里,我们建哈希表的方法用除留取余法为例。

尽管有这么多种方法,但是不同的key值可能会映射到同一散列地址上。这样就会造成哈希冲突/哈希碰撞


那么遇到哈希冲突我们该如何处理呢?


二、处理哈希冲突的闭散列方法

1、线性探测:当不同的key值通过哈希函数映射到同一散列地址上时,检测当前地址的下一个地址是否可以插入,如果可以的话,就存在当前位置的下一个地址,否则,继续向下一个地址寻找,地址++。

2、二次探测:是针对线性探测的一个改进,线性探测后插入的key值太集中,这样造成key值通过散列函数后还是无法正确的映射到地址上,太集中也会造成查找、删除时的效率低下。因此,通过二次探测的方法,取当前地址加上i^2,可以取到的新的地址就会稍微分散开。

如:此时的散列表长度为10:


看到以上的例子之后,我们只是存入int型的数据时,计算余数寻找地址是比较容易的。如果我们存入的是字典值(string类型),那么我们就要通过string类型来转化成数值来计算地址。这里用到了BKDR哈希算法(字符串哈希算法)。

同时,经研究表明,通过素数表作为哈希表的长度可以降低哈希冲突。


三、闭散列实现代码

主要实现如下:

#pragma once
#include<vector>
#include<iostream>
using namespace std;
#include<assert.h>
#include<string>enum Status
{EXIST,DELETE,EMPTY
};template<class K,class V>
struct HashTableNode
{K _key;V _value;Status _status;HashTableNode(const K& key = K(), const V& value = V()):_key(key),_value(value),_status(EMPTY){}
};template<class K>
struct __HashFunc
{size_t operator()(const K& key){return key;}
};template<>
struct __HashFunc<string>
{size_t BKDRHash(const char* str){register size_t hash = 0;while(*str){hash = hash*131 + *str;++str;}return hash;}size_t operator()(const string& str){return BKDRHash(str.c_str());}
};
template<class K,class V,class _HashFunc = __HashFunc<K>>
class HashTable
{typedef HashTableNode<K,V> Node;
public:HashTable(size_t size):_size(0){assert(size > 0);//	_tables.resize(size);_tables.resize(GetPrime());}~HashTable(){}size_t HashFunc(const K& key){_HashFunc hf;size_t va = hf(key);return va % _tables.size();}void Swap(HashTable<K,V,_HashFunc>& ht){_tables.swap(ht._tables);swap(_size,ht._size);}void _CheckCapacity(){if(_tables.size() == 0 || _size*10 / _tables.size() >= 7){size_t OldSize = _tables.size();//	size_t NewSize = _tables.size()*2+3;size_t NewSize = GetPrime();HashTable<K,V,_HashFunc> ht(NewSize);for(size_t i = 0; i < OldSize; i++){if(_tables[i]._status == EXIST){ht.Insert(_tables[i]._key,_tables[i]._value);}}this->Swap(ht);}}pair<Node*,bool> Insert(const K& key,const V& value){_CheckCapacity();size_t index = HashFunc(key);//线性探测/*while(_tables[index]._status ==  EXIST ){if(_tables[index]._key == key)return make_pair((Node*)NULL,false);++index;if(index == _tables.size()){index = 0;}}*///二次探测size_t i = 0;size_t first = index;while(_tables[index]._status ==  EXIST ){if(_tables[index]._key == key)return make_pair((Node*)NULL,false);++i;index = first + i*i;index %= _tables.size();}++_size;_tables[index]._key = key;_tables[index]._value = value;_tables[index]._status = EXIST;return make_pair(&_tables[index],true);}Node* Find(const K& key,const V& value){size_t index = HashFunc(key);while(_tables[index]._status != EMPTY){if(_tables[index]._key == key && _tables[index]._status == EXIST){return &_tables[index];}else{++index;if(index == _tables.size()){index = 0;}}}return NULL;}bool Remove(const K& key,const V& value){Node* tmp = Find(key,value);if(tmp){tmp->_status = DELETE;return true;}return false;}size_t GetPrime(){// 使用素数表对齐做哈希表的容量,降低哈希冲突const int _PrimeSize = 28;static const unsigned long _PrimeList[_PrimeSize] ={53ul, 97ul, 193ul, 389ul, 769ul,1543ul, 3079ul, 6151ul, 12289ul, 24593ul,49157ul, 98317ul, 196613ul, 393241ul,786433ul,1572869ul, 3145739ul,6291469ul, 12582917ul,25165843ul,50331653ul, 100663319ul, 201326611ul, 402653189ul,805306457ul,1610612741ul, 3221225473ul, 4294967291ul};for(size_t i = 0; i < _PrimeSize; i++){if(_tables.size() < _PrimeList[i])return _PrimeList[i];}return 0;}
private:vector<Node> _tables;size_t _size;
};void HashTest()
{HashTable<int,int> ht(10);ht.Insert(89,0);ht.Insert(18,0);ht.Insert(49,0);ht.Insert(58,0);ht.Insert(9,0);cout<<ht.Remove(58,0)<<endl;if(ht.Find(49,0))cout<<ht.Find(9,0)->_key<<endl;HashTable<string,string> ht1(10);ht1.Insert("sort","排序");ht1.Insert("left","左边");ht1.Insert("right","右边");ht1.Insert("up","上边");if(ht1.Find("sort","排序"))cout<<ht1.Find("sort","排序")->_key<<endl;cout<<ht1.Remove("sort","排序")<<endl;
}


四、处理哈希冲突的开链法(哈希桶)

当用线性探测和二次探测时,总是在一个有限的哈希表中存储数据,当数据特别多时,效率就比较低。因此采用拉链法的方式来降低哈希冲突。



还有一种情况是,当一个链上链的数据过多时,我们可以采用红黑树的方式来降低高度,保持平衡且不至于过载。


五、哈希桶的实现方式(考虑到存储整形和字符串型)

#pragma once
#include<iostream>
using namespace std;
#include<vector>
#include<string>namespace HashBucket
{template<class K>struct __HashFunc{size_t operator()(const K& key){return key;}};template<>struct __HashFunc<string>{size_t BKDRHash(const char* str){register size_t hash = 0;while(*str){hash = hash*131 + *str;++str;}return hash;}size_t operator()(const string& str){return BKDRHash(str.c_str());}};template<class K,class V,class _HashFunc>class HashTable;template<class K,class V>struct HashNode{pair<K,V> _kv;HashNode<K,V> *_next;HashNode(const pair<K,V>& kv):_kv(kv),_next(NULL){}};template<class K,class V,class Ref,class Ptr>struct HashIterator{typedef HashNode<K,V> Node;typedef HashIterator<K,V,Ref,Ptr> Self;Node* _node;HashTable<K,V,__HashFunc<K>>* _ht;HashIterator(Node* node,HashTable<K,V,__HashFunc<K>>* ht):_node(node),_ht(ht){}Ref operator*(){return _node->_kv;}Ptr operator->(){return &_node;}bool operator!=(const Self& s) const{return _node != s._node;}Self& operator++(){_node = Next(_node);return *this;}Node* Next(Node* node){Node* next = node->_next;if(next)return next;else{size_t index = _ht->HashFunc(node->_kv.first)+1;for(;index < _ht->_tables.size();++index){next = _ht->_tables[index];if(next){return next;}}return NULL;}}};template<class K,class V,class _HashFunc = __HashFunc<K>>class HashTable{typedef HashNode<K,V> Node;public:typedef HashIterator<K,V,pair<K,V>&,pair<K,V>*> Iterator;typedef HashIterator<K,V,const pair<K,V>&,const pair<K,V>*> ConstIterator;friend struct Iterator;friend struct ConstIterator;public:HashTable():_size(0){_tables.resize(GetNextPrime());}~HashTable(){Clear();}void Clear(){Node* cur = NULL;Node* del = NULL;for(size_t index = 0; index < _tables.size(); ++index){cur = _tables[index];if(cur == NULL){continue;}while(cur){del = cur;cur = cur->_next;delete del;del = NULL;}}}Iterator Begin(){Node* cur = _tables[0];for(size_t index = 0; index < _tables.size();++index){cur = _tables[index];if(cur){return Iterator(cur,this);}}return Iterator((Node*)NULL,this);}Iterator End(){return Iterator((Node*)NULL,this);/*Node* cur = _tables[_tables.size()-1];while(cur){if(cur == NULL){return Iterator(cur,this);}cur = cur->_next;}return Iterator((Node*)NULL,this);*/}size_t HashFunc(const K& key){_HashFunc hf;size_t va = hf(key);return va % _tables.size();}void Swap(HashTable<K,V,_HashFunc>& ht){_tables.swap(ht._tables);swap(_size,ht._size);}void _CheckCapacity(){//负载因子为1时,扩容if(_size == _tables.size()){size_t index = GetNextPrime();HashTable<K,V> tmp;tmp._tables.resize(index);Node* cur = NULL;for(;index < _tables.size();++index){cur = _tables[index];while(cur){tmp.Insert(cur->_kv);cur = cur->_next;}}this->Swap(tmp);}}size_t GetNextPrime(){// 使用素数表对齐做哈希表的容量,降低哈希冲突const int _PrimeSize = 28; static const unsigned long _PrimeList[_PrimeSize] ={53ul, 97ul, 193ul, 389ul, 769ul,1543ul, 3079ul, 6151ul, 12289ul, 24593ul,49157ul, 98317ul, 196613ul, 393241ul,786433ul,1572869ul, 3145739ul,6291469ul, 12582917ul,25165843ul,50331653ul, 100663319ul, 201326611ul, 402653189ul,805306457ul,1610612741ul, 3221225473ul, 4294967291ul};for(size_t i = 0; i < _PrimeSize; i++){if(_tables.size() < _PrimeList[i])return _PrimeList[i];}return 0;}public:pair<Iterator,bool> Insert(pair<K,V> kv){_CheckCapacity();size_t index = HashFunc(kv.first);Node* cur = _tables[index];while(cur){if(cur->_kv.first == kv.first){return make_pair(Iterator(cur,this),false);}cur = cur->_next;}Node* tmp = new Node(kv);tmp->_next = _tables[index];_tables[index] = tmp;_size++;return make_pair(Iterator(tmp,this),true);}Node* Find(const K& key){size_t index = HashFunc(key);Node* cur = _tables[index];while(cur){if(cur->_kv.first == key){return cur;}cur = cur->_next;}return NULL;}bool Erase(const K& key){size_t index = HashFunc(key);Node* prev = NULL;Node* cur = _tables[index];Node* del = NULL;while (cur){if(cur->_kv.first == key){if(prev == NULL){_tables[index] = cur->_next;}else{prev->_next = cur->_next;}delete cur;cur = NULL;_size--;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _tables;size_t _size;};void HashTest(){HashTable<int,int> ht;ht.Insert(make_pair<int,int>(89,0));ht.Insert(make_pair<int,int>(18,0));ht.Insert(make_pair<int,int>(49,0));ht.Insert(make_pair<int,int>(58,0));ht.Insert(make_pair<int,int>(9,0));cout<<ht.Erase(58)<<endl;if(ht.Find(49))cout<<ht.Find(9)->_kv.first<<endl;HashTable<int,int>::Iterator it = ht.Begin();while(it != ht.End()){cout<<(*it).first<<":"<<(*it).second<<endl;++it;}HashTable<string,string> ht1;ht1.Insert(make_pair<string,string>("sort","排序"));ht1.Insert(make_pair<string,string>("left","左边"));ht1.Insert(make_pair<string,string>("right","右边"));ht1.Insert(make_pair<string,string>("up","上边"));cout<<ht1.Erase("up")<<endl;cout<<ht1.Find("sort")->_kv.second<<endl;HashTable<string,string>::Iterator it1 = ht1.Begin();while(it1 != ht1.End()){cout<<(*it1).first<<":"<<(*it1).second<<endl;++it1;}}
};


六、运行结果(编译运行环境为:vs2013)














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

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

相关文章

僵尸进程的产生和SIGCHLD信号

核心句子 子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自 定义SIGCHLD信号的处理函数。 僵尸进程的产生&#xff1a; #include "head.h" #include <unistd.h> #include <signal.h>int main() {key_t key ftok(&quo…

海量数据处理--位图(BitMap)

对于海量数据这个词&#xff0c;大家不难理解吧。主要是针对给定的数据量特别大&#xff0c;占用内存特别大的情况。那么和位图有什么关系呢。看下面一个腾讯的海量数据的例子吧。 例&#xff1a;给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0…

ps命令与top命令参数意义详解

文章目录1.ps -l2.ps aux3.top面试经常被问道&#xff0c;特别是top。1.ps -l 参数解释F代表这个程序旗标 (process flags)&#xff0c;说明这个程序的总结权限&#xff0c;常见号码有&#xff1a;o 若为 4 表示此程序的权限为 root &#xff1b;o 若为 1 则表示此子程序仅进行…

哈希拓展--布隆过滤器

一、问题概述 布隆过滤器是由布隆提出来的&#xff0c;是由一个很长的二进制序列和一系列的映射函数组成。主要用于检测一个元素是否在一个集合中。当然在设计计算机软件时&#xff0c;我们也经常会判断一个元素是否在一个集合中。比如&#xff1a;在字处理软件中&#xff0c;…

排序(Sort)--【一】

排序&#xff0c;对于大家再熟悉不过了吧。我们之前在学习c语言的时候接触过的冒泡排序&#xff0c;选择排序等。今天给大家介绍两种新的排序。 1、直接插入排序 升序排列&#xff1a;将第一个数确定好&#xff0c;从下标为1的数开始插入&#xff0c;如果插入的数比前一个数大…

快速排序--全集

快速排序&#xff1a;一听名字就知道这种排序很快的&#xff0c;是吧&#xff1f;没错&#xff0c;它是一种效率比较高的排序算法。 快速排序采用的是分治的思想。 比如&#xff0c;将一串数中的一个元素作为基准&#xff0c;然后将比它小的数排在它的左边&#xff0c;比它大…

task_struct结构体查找

网上有很多解析task_struct结构体的文章&#xff0c;可是都没有说这个结构体到底在哪里&#xff1f; 这个结构体位于头文件 shced.h cd / find -name sched.h 显示结果如下 注意只有 位于内核中的include 才是正确的。 /usr/src/kernels/2.6.32-431.el6.i686/include/linux…

闹钟函数alarm()的解释与实践

alarm 定义 也称为闹钟函数&#xff0c;它可以在进程中设置一个定时器&#xff0c;当定时器指定的时间到时&#xff0c;它向进程发送SIGALRM信号。可以设置忽略或者不捕获此信号&#xff0c;如果采用默认方式其动作是终止调用该alarm函数的进程。 #include "head.h&quo…

Linux下如何设置权限让用户只删除自己的文件(粘滞位)

之前我们知道如何针对用户和用户组来设置文件权限。通常是用三个八进制来设置权限的&#xff0c;这里我要说的是&#xff0c;其实是由四个八进制表示的。其中第一个八进制我们通常是忽略的。第二个到第四个是对应于SUID,SGID,sticky-bit。 SUID&#xff1a;设置了SUID 位的文件…

Vim简单配置

vim配置&#xff1a; &#xff08;在Centos6.5下配置vim&#xff09; 1.找到用户的主工作目录&#xff0c;ls看是否有.vimrc文件&#xff0c;有的话打开即可。没有的话自己touch一个。vim进入.vimrc中&#xff1a; set nu 设置行数 colorscheme desert syntax enabl…

[WPS笔试题]实现栈的push,pop,max且时间复杂度为O(1)

今天做了一下WPS的笔试题&#xff0c;遇到了一道关于栈的题&#xff0c;觉得挺有意思的&#xff0c;就写篇博客分享一下吧~~ 题目要求&#xff1a;要求实现栈的数据结构&#xff0c;在该类型中实现一个能够得到栈的最大元素的max函数&#xff0c;在该栈中&#xff0c;调用max,…

[剑指Offer]替换空格

今天看题的时候&#xff0c;遇到一个替换空格的题目&#xff0c;分析一下哈。 题目要求&#xff1a;把字符串中的每个空格替换成“%20”。例如输入“we are happy”&#xff0c;则输出“we%20are%20happy”。 解题思路&#xff1a;我们首先想到的是&#xff1a;移位思想。遇到…

C语言关键字 ISO/ANSI C90 C99 C11

面试考点 https://blog.csdn.net/csdn_kou/article/details/81113215 * 有的常用的我们都不知道是关键字&#xff0c;比如sizeof.这是面试中的考点&#xff0c;要注意。 * 同时当回答C语言中有多少关键字时&#xff0c;要回答前题条件&#xff0c;时针对哪一个版本

关于sudo

之前&#xff0c;我们使用sudo的时候&#xff0c;是因为其用户本身具有root权限&#xff0c;所以可以sudo后执行相关操作&#xff0c;但是对于普通用户来说&#xff0c;它是既不具有sudo权限&#xff0c;又不在sudo用户组中&#xff0c;那么我们来研究一下如何将新创建的用户添…

Bash入门

Bash简介&#xff1a; Bash&#xff08;GNU Bourne-Again Shell&#xff09;是一个为GNU计划编写的Unix shell&#xff0c;它是许多Linux平台默认使用的shell。 shell是一个命令解释器&#xff0c;是介于操作系统内核与用户之间的一个绝缘层。准确地说&#xff0c;它也是能力…

线程之售票系统pthread_mutex,_lock,_unlock

先看一下这篇文章 https://blog.csdn.net/csdn_kou/article/details/81148268 四个人同时买票票&#xff0c;引出线程 #include "head.h" int ticket 100; void * route(void *arg) {char *id (char *)arg;while(1){if(ticket>0){usleep(1000);printf("…

Bash基本语法

1. 变量赋值 a375 hello$a 这里需要注意的是&#xff0c;等号两边不能有空格 还有一个例子是这样的 例1&#xff1a; 结果为&#xff1a; 关于上述&#xff0c;主要有如下几点&#xff1a; $hello和${hello}是一样的&#xff0c;在bash中如果遇到空格&#xff0c;tab键时&a…

关于fd和fp(fd:file descirptor fp:file pointor)

通常&#xff0c;我们在输入数据或输出数据的设备为键盘或者显示器。当然&#xff0c;我们比较熟悉的输入输出&#xff0c;可能就是对于文件的操作&#xff0c;还有直接从终端输出&#xff0c;显示到显示器上。在C语言中&#xff0c;我们使用fopen,fclose,fread,fwrite对文件进…

[linux]wait详解

wait&#xff1a;进程等待 主要有两种等待方式&#xff1a;阻塞式等待和非阻塞式等待 阻塞式等待&#xff1a;如果子进程正在运行&#xff0c;父进程将会一直等待着子进程运行结束&#xff0c;并且自己什么事都不干 非阻塞式等待&#xff1a;如果子进程正在运行&#xff0c;…