哈希函数
哈希函数用于将一个大数(手机号码)或字符串映射为一个可以作为哈希表索引的较小整数的函数。也就是,对一个输入stdin
可以转换成另外一个索引比较小的stdout
。用公式表达就是stdout=H(stdin)。采用哈希函数可以进行加密等操作。当前的比特币挖矿的原理就是每一个比特币都是通过哈希函数加过密的,所以我们只有不断的去尝试加密后的数字,来得到加密前的币。
一个好的哈希函数应该满足以下几个条件:
1. 执行效率要高(效率高,对于一段很长的字符串或者二进制文本也能快速计算出哈希值。
2. 散列结果应当具有同一性(输出值尽量均匀,越均匀冲突就越少),输出是尽可能的无序而且均匀的。
3. 雪崩效应,也就是说微小的输入值变化使得输出值发生巨大的变化。
4. 从哈希函数的输出值不可反向推导出原始的数据。(不可反向推导)(解决办法就是结果尝试,也就是“挖矿”。
5. 同样的输入能得到同样的输出,但是一点点改变则发生巨大变化。
6. 输入是无限的,输出是有限的。
hash碰撞
哈希碰撞就是总有可能性存在使得两个不一样的数进行哈希函数之后的输出是一样的,这就是哈希碰撞即使这个可能性很小,但是我们也不能忽视。
哈希碰撞的解决方法有以下几种:
- 链地址法;
- 开放地址法
- 再哈希
在STL库中的hashtable解决哈希碰撞的方法是链地址法。所以本次Blog也采用链地址法来解决哈希碰撞问题。
链地址法
在STL库中的Hashtable,底层数据结构如上图。先用一个vector来存放哈希之后再取模的数,这样可以极大的降低空间。每个vector中维护了一个单链表,单链表将hash结果相同的数串起来。因为单链表的插入和删除的时间复杂度是O(1),但是查询复杂度是O(n),但是通常碰撞的链表长度都不会太长,所以可以近似看成是查询复杂度是O(1)。所以基于链地址法实现的hashtable的查询复杂度是O(1)。
简单链地址法代码实现
本次实现和STL中的实现并不相同,是STL方法的一个简化版本,旨在说明原理。
#include<iostream>
#include<vector>
#include<list>
using namespace std;
class Hash{
int N; //哈希表的长度N
list<int> * table;
public:
Hash(int v); //构造
void insertKey(int x);
void deleteKey(int x);
int hashFunction(int x){
return (x%N);
}
void display();
};
Hash::Hash(int b){
this->N=b;
table=new list<int>[N];
}
void Hash::insertKey(int key){
int index=hashFunction(key);
table[index].push_back(key);
}
void Hash::deleteKey(int key){
int index=hashFunction(key); //找到索引下标
list<int>::iterator i=table[index].begin(); //准备在链表中寻找;
for(i;i!=table[index].end();i++){
if(*i==key){
break;
}
}
if(i !=table[index].end()){
table[index].erase(i);
}
}
void Hash::display(){
for(int i=0;i<N;i++){
cout<<i;
for(auto x:table[i]){
cout<<"-->"<<x;
}
cout<<endl;
}
}
int main(){
vector<int> test;
test={10,9,8,7,5,6,3,4,5,62,1,95};
int len=test.size();
Hash h(len);
for(int i=0;i<len;i++){
h.insertKey(test[i]);
}
h.display();
}
此代码最终实现的结果是:
删除一个Key值5的结果;