很多小伙伴为了刷题发愁
今天为大家推荐一款刷题神奇哦:刷题面试神器牛客
各大互联网大厂面试真题。从基础到入阶乃至原理刨析类面试题 应有尽有,赶快来装备自己吧!助你面试稳操胜券,solo全场面试官
函数声明 | 功能介绍 |
---|---|
iterator find(const K& key) | 返回key在哈希桶中的位置 |
size_t count(const K& key) | 返回哈希桶中关键码为key的键值对的个数 |
注意:unordered_map中key是不能重复的,因此count函数的返回值最大为1
函数声明 | 功能介绍 |
---|---|
insert | 向容器中插入键值对 |
erase | 删除容器中的键值对 |
void clear() | 清空容器中有效元素个数 |
void swap(unordered_map&) | 交换两个容器中的元素 |
函数声明 | 功能介绍 |
---|---|
size_t bucket_count()const | 返回哈希桶中桶的总个数 |
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(N),搜索的效率取决于搜索过程中元素的比较次数
理想的搜索方法是可以不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素,则复杂度为O(1)非常的高效,而计数排序用的即是这种思想
哈希的思想就是信息压缩的思想,可以将一些信息量庞大的数据通过特殊的哈希函数压缩成信息量比较小的数据,再通过哈希桶,位图等容器存储起来。
根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
搜索元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比 较,若关键码相等,则搜索成功
对于两个数据元素的关键字 和 (i != j),有 != ,但有:Hash( ) == Hash( ),
即:不同关键字通过
相同哈希哈数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞。
把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。
发生哈希冲突该如何处理呢?
引起哈希冲突的一个原因可能是:哈希函数设计不够合理。 哈希函数设计原则:
哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0 到m-1之间
哈希函数计算出来的地址能均匀分布在整个空间中
哈希函数应该比较简单
取关键字的某个线性函数为散列地址:Hash(Key)= A*Key + B
优点:简单、均匀 缺点:需要事先
知道关键字的分布情况 使用场景:适合查找比较小且连续的情况
2. 除留余数法–(常用)
设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函 数:Hash(key) = key%
p(p<=m),将关键码转换成哈希地址
闭散列也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去
从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止,即newindexi=(index+i)%capacity
线性探测实现非常简单,但是一旦发生哈希冲突,所有的冲突连在一起,容易产生数据堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低
从发生的冲突的位置开始,不逐个往后找,而是以平方个位置找,即计算位置为newindexi=(index+i^2)%capacity
二次探测可以较为有效的方式减小哈希冲突的概率
使用除留余数定制法时,对于扩容后的哈希表对应的哈希函数的除数的值会发生相应的改变,导致下一次查找定制的位置可能不同,所以需要对原来的数据进行再次映射到新的位置上
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中
通过哈希函数找到对应的映射位置,然后头插 ,但是在插入之前需要进行遍历桶节点查看是否存在与插入的值相同的节点,没有才进行头插。
通过哈希函数映射到对应的位置,进行对该位置通的遍历再进行删除或查找
除留余数法,最好模一个素数
//获取下一个质数(接近二倍开辟),比较科学减少冲突的取扩容大小的方式
size_t GetNextPrime(size_t prime)
{
const int PRIMECOUNT = 28;
static const size_t primeList[PRIMECOUNT] =
{
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
};
size_t i = 0;
for (; i < PRIMECOUNT; ++i)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[i];
}
//哈希储存的数据类型
template<class K,class V>
struct HashNode
{
pair<K,V> _kv;
HashNode* _next;
HashNode(const pair<K,V>& kv)
:_kv(kv)
, _next(nullptr)
{}
};
//取值比较仿函数及其特化
template<class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return key;
}
};
template<>
struct HashFunc<string>
{
size_t operator()(const string& str)
{
size_t hash = 0;
for (int i = 0; i < str.size(); i++)
{
hash = hash * 131 + str[i];
}
return hash;
}
};
//获取下一个质数(接近二倍开辟),比较科学减少冲突的取扩容大小的方式
size_t GetNextPrime(size_t prime)
{
const int PRIMECOUNT = 28;
static const size_t primeList[PRIMECOUNT] =
{
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
};
size_t i = 0;
for (; i < PRIMECOUNT; ++i)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[i];
}
template<class K, class V, class Hash=HashFunc<K>>
class HashTable
{
typedef HashNode<K,V> Node;
public:
typedef HashTable<K, V, Hash> HT;
HashTable()
:_n(0)
{}
HashTable(const HT& ht)//拷贝构造
:_n(ht._n)
{
if (ht._table.size() == 0)//空栈
return;
_table.resize(ht._table.size(), nullptr);//开辟空间并初始化
for (int i = 0; i < ht._table.size(); i++)//遍历深拷贝
{
Node* cur = ht._table[i];
while (cur)//遍历桶
{
Node* newnode = new Node(cur->_kv);
newnode->_next = _table[i];//头插
_table[i] = newnode;
cur = cur->_next;
}
}
}
HT& operator=(const HT& ht)//赋值重载(现代式)
{
if (&ht != this)
{
HT temp(ht);//拷贝构造
_table.swap(temp._table);
_n = ht._n;
}
return *this;
}
~HashTable()//析构-释放资源
{
for (int i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_table[i] = nullptr;//置空
}
_n = 0;
}
bool Insert(const pair<K,V>& kv)
{
Hash hf;
//空哈希或者负载因子达到1时进行扩容
if (_table.size() == _n)
{
//size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;
size_t newsize = GetNextPrime(_table.size());//获取新的扩容大小
vector<Node*> newdata;
newdata.resize(newsize, nullptr);//开新的数组并扩容
//将原数组中的节点重新映射插入到新数组
for (size_t i = 0; i < _table.size(); ++i)
{
Node* cur = _table[i];
while (cur)//挂有节点
{
Node* next = cur->_next;//记录下一个节点
size_t index = hf(cur->_kv.first) % newsize;//重新计算下标
cur->_next = newdata[index];//头插
newdata[index] = cur;
cur = next;//移动
}
_table[i] = nullptr;//原数组置空
}
_table.swap(newdata);
}
size_t index = hf(kv.first) % _table.size();
Node* cur = _table[index];//遍历查看桶数据知否相等
while (cur)
{
if (cur->_kv.first == kv.first)//相等
return false;
else
cur = cur->_next;
}
//头插
Node* newnode = new Node(kv);
newnode->_next = _table[index];
_table[index] = newnode;
++_n;
return true;
}
Node* Find(const K& key)
{
//空哈希
if (_table.size() == 0)
return nullptr;
Hash hf;
size_t index = hf(key) % _table.size();
Node* cur = _table[index];
while (cur)
{
if (cur->_kv.first == key)
return cur;
else
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
//空哈希
if (_table.size() == 0)
return false;
Hash hf;
size_t index = hf(key) % _table.size();
Node* cur = _table[index];
Node* parent = nullptr;
while (cur)
{
if (cur->_kv.first == key)
{
if (parent == nullptr)//头结点进行头删
_table[index] = cur->_next;
else
parent->_next = cur->_next;
delete cur;
--_n;
return true;
}
else
{
parent = cur;
cur = cur->_next;
}
}
return false;
}
private:
vector<Node*> _table;
size_t _n;
};
namespace ymh
{
template<class K , class Hash = HashFunc<K>>
class unordered_set
{
struct SetOfKey
{
const K& operator()(const K& key)const
{
return key;
}
};
typedef HashNode<K> Node;
public:
typedef HashTable<K, K, Hash, SetOfKey> HT;
typedef typename HT::Iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
pair<iterator, bool> insert(const K& key)
{
return _ht.Insert(key);
}
/*V& operator[](const K& key)
{
auto ret = insert(make_pair(key, V()));
return ret.first->second;
}*/
Node* find(const K& key)
{
return _ht.Find(key);
}
bool erase(const K& key)
{
return _ht.Erase(key);
}
private:
HT _ht;
};
void test_unordered_set()
{
unordered_set<int> set;
int arr[] = { 1,2,22,34,3,4,54,4,2,6,18,48,56,16,45,16,23,156,49,153,45,81,6,6,16,16,151,84894,11,6 };
for (auto e : arr)
{
set.insert(e);
}
for (auto& e : set)
{
cout << e << endl;
}
auto ret = set.find(4);
cout << ret << endl;
set.erase(4);
for (auto& e : set)
{
cout << e << endl;
}
for (auto e : arr)
{
set.erase(e);
}
for (auto& e : set)
{
cout << e<< endl;
}
}
}