[数据结构] 哈希表 (开放寻址法+拉链法)
哈希表
哈希表的基本概念
哈希表 Hash table 是一种提供快速查找和插入元素的数据结构,也称散列表。哈希表是基于数组的扩展,一般利用数组的下标作为哈希表的键值。
哈希表存储的是由键(key)和值(value)组成的数据。键值 key 是由哈希函数得到的。
哈希函数
除留余数法
除留余数法是一种广泛运用的哈希函数,其用插入元素 mod 哈希表长度来得到关键字。由于哈希表是基于数组的,假如要插入负数元素,在C、C++中取模后还是负数,但是数组的下标不可以为负,所以哈希函数写成下列形式:
其中 x 为插入元素, N 为哈希表长度。这样取模后,得到的是非负数。
除留余数法函数
int Hash(int x){
return ((x % N) + N) % N;
}
处理哈希冲突
哈希表中每个键值很可能对应了多个元素,所以哈希冲突不可避免,常用解决哈希冲突的方法有两种,分别为 开放地址法 和 拉链法 。
开放寻址法
开放寻址法即当元素不能直接存放在哈希函数计算出来的数组下标时,就需要寻找其他位置来存放。
我们一般采用线性探测,当对应键位置 k 已经存在元素时,则将 k 进行加一,探测下一个位置是否为空。但这样的处理冲突方法比较低效。
开放寻址法代码
#include<iostream>
#include<cstring>
using namespace std;
static const int N = 14, inf = 0x3f3f3f3f;
int h[N];
void init(){
memset(h, 0x3f, sizeof(h));
}
int Hash(int x){
return ((x % N) + N) % N;
}
bool find(int x){
int k = Hash(x);
if(h[k] == inf) return false; //当前键值对应为空
while(h[k] != x){
k = (k + 1) % N;
if(h[k] == inf || k == Hash(x))
return false; //回到起点
}
return true;
}
void insert(int x){
int k = Hash(x);
while(h[k] != inf) k = (k + 1) % N;
h[k] = x;
}
int main(){
init();
for(int i = 9; i <= 15; i++)
insert(i);
for(int i = 7; i <= 13; i++)
if(find(i))
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
cout<<endl;
}
拉链法
拉链法是比较常用的处理哈希冲突的方法。我们使用一个数组,数组的每个位置存放一个链表,这样当对应键存在多个元素需要存放时,可以很方便地将元素插入到对应链表中。
拉链法使用的结构和图的领接表很像,后续也称之为邻接表吧。
拉链法 邻接表
#include<iostream>
#include<cstdlib>
using namespace std;
static const int N = 7;
typedef struct node{
int val;
node *next;
}node;
node* h[N]; //邻接表
void init(){
for(int i = 0; i < N; i++){
h[i] = new node;
h[i]->val = -1;
h[i]->next = NULL;
}
}
int Hash(int x){
return ((x % N) + N) % N;
}
bool find(int x){
int k = Hash(x);
for(node* p = h[k]->next; p; p = p->next)
if(p->val == x)
return true;
return false;
}
void insert(int x){
int k = Hash(x);
node* cur = new node;
cur->val = x;
cur->next = h[k]->next; //头插法
h[k]->next = cur;
}
int main(){
init();
for(int i = 1; i <= 17; i++)
insert(i);
for(int i = 15; i <= 20; i++)
if(find(i))
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
cout<<endl;
for(int key = 0; key <= 6; key++){
node* t = h[key]->next;
cout<<"key = "<<key<<": ";
while(t){
cout<<t->val<<' ';
t = t->next;
}
cout<<endl;
}
}
拉链法 vector邻接表
相关试题 Acwing 840.模拟散列表
也可以用 vector 来实现邻接表。
#include<iostream>
#include<vector>
using namespace std;
const int N = 100003;
vector<int> h[N];
void insert(int x){
int k = (x % N + N) % N; //余数可能为负数
h[k].push_back(x);
}
bool find(int x){
int k = (x % N + N) % N;
for(auto &cur : h[k])
if(cur == x)
return true;
return false;
}
int main(){
cin.tie(0); cout.tie(0);
int n; cin>>n;
while(n--){
char op;
int x;
cin>>op>>x;
if(op == 'I')
insert(x);
else{
if(find(x))
puts("Yes");
else
puts("No");
}
}
}
拉链法 链式前向星
用链式前向星相比 vector 容器更快,这种链式结构在竞赛中用得比较多。
#include<iostream>
#include<cstring>
using namespace std;
const int N = 100003;
int Hash[N], e[N], ne[N], idx;
void insert(int x){
int k = (x % N + N) % N; //余数可能为负数
e[idx] = x;
ne[idx] = Hash[k];
Hash[k] = idx++;
}
bool find(int x){
int k = (x % N + N) % N;
for(int i = Hash[k]; i != -1; i = ne[i])
if(e[i] == x)
return true;
return false;
}
int main(){
cin.tie(0); cout.tie(0);
int n; cin>>n;
memset(Hash, -1, sizeof(Hash));
while(n--){
char op;
int x;
cin>>op>>x;
if(op == 'I')
insert(x);
else{
if(find(x))
puts("Yes");
else
puts("No");
}
}
}
一切都是命运石之门的选择,本文章来源于博客园,作者:MarisaMagic,出处:https://www.cnblogs.com/MarisaMagic/p/17093103.html,未经允许严禁转载