unordered_map,unordered_set在功能方面和map,set基本一致
unordered_map和unordered_set底层是哈希表,遍历无序,查找删除修改的效率为O(1)
map和set底层是红黑树,遍历有序,查找删除修改的效率为O(logN)
void set_test1()
{
// 迭代器遍历
set<int> s = { 3,1,6,7,8,2};
set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
// 3 1 6 7 8 2
cout << endl;
}
int main()
{
set_test1();
return 0;
}
字符串中的第一个唯一字符
class Solution
{
public:
int firstUniqChar(string s)
{
int hash[26] = {0};
for(auto ch : s)
{
hash[ch - 'a']++;
}
for(int i = 0;i < s.size();i++)
{
if(hash[s[i] - 'a'] == 1)
return i;
}
return -1;
}
};
N == 100 M == 200,N个数要存到M个空间中
除法散列法:h(key) = key % M
哈希冲突:3 % 200 = 3,203 % 200 = 3
哈希表中已经存了N个值,哈希表的大小为M,负载因子 = N/M,负载因子也叫装载因子/载荷因子,负载因子越大,哈希冲突越高,空间利用率越高,相反就越低。
比如是浮点数转为整数或者有符号的整数(负数)要转为正数,字符串转为整数。
一个好的哈希函数应该让N个关键字被等概率的均匀的散列分布到哈希表的M个空间中,但是实际中却很难做到,但是我们要尽量往这个方向去考量设计。
现在给一组数据:
{19,30,5,36,13,20,21,12} 等这一组值映射到M=11的表中
h(19) = 8,h(30) = 8,h(5) = 5,h(36) = 3,
h(13) = 2,h(20) = 9,h(21) =10,h(12) = 1
上面这组数据在8位置就冲突了
二次探测和线性探测非常类似
如果往左走回绕到哈希表尾,用减,比如3-9,到5的位置停下,-6 + M,M == 11, -6 + M = 5
// 二次探测
int flag = 1;
while (_tables[hashi]._state == EXIST)
{
// 存在进行二次探测,删除和空就退出
hashi = (hash0 + i*i*flag) % _tables.size();
if (hashi < 0)
hashi += _tables.size();
if (flag == 1)
{
flag = -1;
}
else
{
flag = 1;
++i;
}
}
h(19) = 8,h(30) = 8,h(5) = 5,h(36) = 3,
h(13) = 2,h(20) = 9,h(21) =10,h(12) = 1
蓝色的四个位置连续冲突
查找的原则:遇到删除,存在才继续找,遇到空就结束查找
状态标识:存在,空和删除,空和删除可以放值,空就线性探测
要注意给每个存储值的位置加一个状态标识,否则删除一些值以后,会影响后面冲突的值的查找。
如下图,我们删除30,会导致查找20失败,当我们给每个位置加⼀个状态标识{EXIST,EMPTY,DELETE} ,删除30就可以不用删除值,而是把状态改为 DELETE ,那么查找20时是遇到 EMPTY 才能停止,就可以找到20。
#pragma once
#include
// 枚举状态
enum State
{
EXIST,
EMPTY,
DELETE
};
template<class K,class V>
struct HashData
{
pair<K, V> _kv;
State _state = EMPTY;
};
template<class K,class V>
class HashTable
{
public:
// 构造
HashTable()
// 会取到53
:_tables(__stl_next_prime(0)),// 11是数据个数()
_n(0)
{
}
// 为了解决M是质数的问题
inline unsigned long __stl_next_prime(unsigned long n)
{
// Note: assumes long is at least 32 bits.
static const int __stl_num_primes = 28;
static const unsigned long __stl_prime_list[__stl_num_primes] = {
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473, 4294967291
};
const unsigned long* first = __stl_prime_list;
const unsigned long* last = __stl_prime_list + __stl_num_primes;
const unsigned long* pos = lower_bound(first, last, n);
// lower_bound取表中大于等于first的数
return pos == last ? *(last - 1) : *pos;
}
// 插入
bool Insert(const pair<K, V>& kv)
{
// 不支持冗余
if (Find(kv.first))
{
return false;
}
// 负载因子大于等于0.7就扩容
if (_n * 10 / _tables.size() >= 7)
{
// 扩容
//vector> newtables(_tables.size()*2);
//for (auto& data : _tables)
//{
// // 把旧表的数据映射到新表
// // 不能直接拷贝,因为映射关系还是原来的(会乱)
// if (data._state == EMPTY)
// {
// size_t hash0 = data._kv.first % newtables.size();
// // ...
// }
//}
//_tables.swap(newtables);
// 上面的方法代码过于冗余
// 把新表和旧表交换
HashTable<K, V> newht;
// newht._tables.resize(_table.size() * 2);
newht._tables.resize(__stl_next_prime(_table.size()));
for (auto& data : _tables)
{
// 把旧表的数据映射到新表
if (data._state == EMPTY)
{
// 相当于递归
newht.Insert(data._kv);
}
}
// 函数结束,newht销毁,数据交换给旧表
_tables.swap(newht._tables);
}
// key / M , M哈希表的空间大小
size_t hash0 = kv.first % _tables.size();
size_t hashi = hash0;
size_t i = 1;
while (_tables[hashi]._state == EXIST)
{
// 存在进行线性探测,删除和空就退出
hashi = (hash0 + i) % _tables.size();
++i;
}
// 二次探测
//int flag = 1;
//while (_tables[hashi]._state == EXIST)
//{
// // 存在进行二次探测,删除和空就退出
// hashi = (hash0 + i*i*flag) % _tables.size();
// if (hashi < 0)
// hashi += _tables.size();
// if (flag == 1)
// {
// flag = -1;
// }
// else
// {
// flag = 1;
// ++i;
// }
//}
_tables[hashi]._kv = kv;
_tables[hashi]._state = EXIST;
++_n;
return true;
}
// 查找
HashData<K, V>* Find(const K& key)
{
size_t hash0 = key % _tables.size();
size_t hashi = hash0;
size_t i = 1;
// Find等于空就找不到了
while (_tables[hashi]._state != EMPTY)
{
if (_tables[hashi]._state == EXIST
&& _tables[hashi]._kv.first == key)
{
return &_tables[hashi];
}
// 存在进行线性探测,删除和空就退出
hashi = (hash0 + i) % _tables.size();
++i;
}
return nullptr;
}
// 删除
bool Erase(const K& key)
{
HashData<K, V>* ret = Find(key);
if (key)
{
ret->_state = DELETE;
return true;
}
else
{
return false;
}
}
private:
vector<HashData<K, V>> _tables;
size_t _n;// 记录哈希表中的数据个数
};
#include"HashTable.h"
int main()
{
int a[] = { 19,30,5,36,13,20,21,12 };
HashTable<int, int> ha;
for (auto& e : a)
{
ha.Insert({ e,e });
}
ha.Erase(20);
if (ha.Find(30))
{
cout << "找到了" << endl;
}
if (ha.Find(20))
{
cout << "找到了" << endl;
}
else
{
cout << "没有找到" << endl;
}
return 0;
}