目录
一、unordered_map
1.1 特性
1.2 接口
1.21 构造函数
1.22 iterator find(const K& key)
1.23 insert
1.24 operator[]
1.25 erase
1.26 find
1.3 哈希概念
1.31闭散列哈希表
1.32开散列哈希表
二、部分功能模拟实现
hashtable.h
unordered_map.h
unordered_set.h
一、unordered_map
1.1 特性
a.可以存储键值对,pair<K,V>类型
b.可以按照任意指定的方式对元素排序
c.通过key访问单个元素比map快
d.重载了operator[],可以通过key直接访问到value
e.是一个前向迭代器,支持++,但不支持--操作,其实也可以支持,不过代价太大了,效率会很低,可以想象成单链表。所以干脆不支持了。
1.2 接口
这里介绍需要注意的接口,其余的接口基本上看一眼文档就能了解
1.21 构造函数
支持迭代器区间构造,
支持给定初始的键值对构造
支持拷贝构造。
用法实例,这里用的是cplusplus.com的示例代码,本节用法示例中都是。
// constructing unordered_maps
#include <iostream>
#include <string>
#include <unordered_map>
typedef std::unordered_map<std::string,std::string> stringmap;
stringmap merge (stringmap a,stringmap b) {
stringmap temp(a); temp.insert(b.begin(),b.end()); return temp;
}
int main ()
{
stringmap first; // empty
stringmap second ( {{"apple","red"},{"lemon","yellow"}} ); // init list
stringmap third ( {{"orange","orange"},{"strawberry","red"}} ); // init list
stringmap fourth (second); // copy
stringmap fifth (merge(third,fourth)); // move
stringmap sixth (fifth.begin(),fifth.end()); // range
std::cout << "sixth contains:";
for (auto& x: sixth) std::cout << " " << x.first << ":" << x.second;
std::cout << std::endl;
return 0;
}
1.22 iterator find(const K& key)
此处返回的是key在哈希桶的位置,返回的是一个迭代器。
1.23 insert
(1)插入返回的是pair,pair的first是一个迭代器,指向的是插入成功/失败后的位置,成功就是插入成功的值的位置,失败就是end()(基本上是这个)
second是bool值,插入成功是1,失败是0。下面代码在VS编译后也验证了unordered_map是不能插入相同的key值的。第二个ttest2是插入失败的。
1.24 operator[]
方括号重载。
int main ()
{
std::unordered_map<std::string,std::string> mymap;
mymap["Bakery"]="Barbara"; // new element inserted
mymap["Seafood"]="Lisa"; // new element inserted
mymap["Produce"]="John"; // new element inserted
std::string name = mymap["Bakery"]; // existing element accessed (read)
mymap["Seafood"] = name; // existing element accessed (written)
mymap["Bakery"] = mymap["Produce"]; // existing elements accessed (read/written)
name = mymap["Deli"]; // non-existing element: new element "Deli" inserted!
mymap["Produce"] = mymap["Gifts"]; // new element "Gifts" inserted, "Produce" written
for (auto& x: mymap) {
std::cout << x.first << ": " << x.second << std::endl;
}
return 0;
}
当哈希表中没有该元素时,使用方括号重载会插入该元素,并返回该元素的second的引用,当哈希表中有该元素时,返回它的second的引用.
下图只是为了方便理解,实际中它哈希表中用的哈希函数(为了避免哈希冲突)哪种排序我不知道。所以单词的顺序可能有差别,但是键值对是这样发生改变的。
1.25 erase
给定要删除的元素的迭代器,给定要删除的元素迭代器区间,给定要删除的元素的key值,都可以删除掉。
int main ()
{
std::unordered_map<std::string,std::string> mymap;
// populating container:
mymap["U.S."] = "Washington";
mymap["U.K."] = "London";
mymap["France"] = "Paris";
mymap["Russia"] = "Moscow";
mymap["China"] = "Beijing";
mymap["Germany"] = "Berlin";
mymap["Japan"] = "Tokyo";
// erase examples:
mymap.erase ( mymap.begin() ); // erasing by iterator
mymap.erase ("France"); // erasing by key
mymap.erase ( mymap.find("China"), mymap.end() ); // erasing by range
// show content:
for ( auto& x: mymap )
std::cout << x.first << ": " << x.second << std::endl;
return 0;
}
示例中,通过迭代器的开始删除了第一个;通过key,删除了key为France的键值对;通过迭代器区间,删除了两个。
1.26 find
查找,可以通过key值查找,返回的是迭代器,const函数就是const迭代器。
1.3 哈希概念
哈希是一种一一映射的概念,有点像数学中的函数。通过建立映射关系,就可以通过这种法则,快速找到映射的另一个。
哈希表运用了哈希概念。它把待存储的数据和哈希表中的下标建立映射关系,提到下标,那么存储的容器当然是顺序表,通过数据的值本身就能飞快访问到数据所存储的位置。这种访问是时间复杂度是O(1)。
对于整型数据,整数1可以存在下标为1的位置,其他整数也以此类推。但数量过大的整数或者分布不太密集的整数可能会造成空间的浪费。因此需要有更加好的法则来建立一一映射的关系。
这种法则就是对数据进行初步处理之后,得到一个相应的下标,再把这个数存在相应的下标中。
这种处理数据的哈希函数有很多。处理后的下标可能是两个相同的数,比如哈希函数是原始数据%10,那么1%10==1,101%10==1,那么这两个数看起来似乎都要存在1,数据就会造成覆盖,这时就会造成哈希冲突。当然也可以把函数设置的复杂一点比如加上^&多种运算来建立映射关系。
再好的哈希函数都无法避免哈希冲突,因为数字是无限多的,而顺序表的容量是有限的。我们只能尽量避免哈希冲突。
1.31闭散列哈希表
线性探测解决。
线性探测的思路:如果1%10==1,1放在下标为1的位置,101%10==1,1的位置已经有值了,那么101就往1的后面没有值的位置找。如果2位置是空的,那么101就存在2这里了。但是2的位置被占领以后,如果有102%10==2,那么102就不能存在2的位置,依然要往后找,这样如果哈希冲突的数据比较多,很多数就被迫放在后面,那么查找的时间就消耗多一点,需要一直往后找。
所以一般情况下,不会让这个哈希表太满,数据/容量=0.7左右。这个值也叫负载因子。
后面又有了开散列来解决哈希冲突。
1.32开散列哈希表
它是一个顺序表,每个节点是一个指针,下面挂着的都是指针。相当于这个哈希表是一个指针数组。
如果遇到哈希冲突,就把这个值进行头插。需要找的时候,就在这个链表上找。这个方法一般设置
负载因子为1,在元素数据等于哈希表容量时,就要扩容。这样每个节点下也不会说挂太多值。在好的哈希函数之下,即使有哈希冲突,节点下面挂的节点并不会太多,查找的效率很高,已经可以看作O(1)的时间复杂度了。
二、部分功能模拟实现
本次主要模拟实现开散列的哈希表。
分析:
1.准备四个文件,一个是哈希表的头文件,然后再装迭代器,再装unordered_map和unordered_set,最后放在test.cpp文件里进行测试。
2.主要实现插入,删除,查找和扩容功能。
3.迭代器要实现前置++和后置++等常用功能。
细节很多,可以多了解一下官方的功能,然后再写。
hashtable.h
#pragma once
#include <iostream>
using namespace std;
#include <vector>
#include <string>
#include <assert.h>
#include <utility>
template<class K>
class HashFunc//哈希函数,仿函数,传一个val返回val
{
public:
size_t operator()(const K& val)
{
return val;
}
};
template<>
class HashFunc<string>//特化,如果是字符串,走这个仿函数
{
public:
size_t operator()(const string& s)
{
const char* str = s.c_str();
unsigned int seed = 131; // 31 131 1313 13131 131313
unsigned int hash = 0;
while (*str)
{
hash = hash * seed + (*str++);
}
return hash;//把字符串经过一些处理转变为unsigned int,达到映射,减少哈希冲突
}
};
namespace OpenHash
{
template<class K>
struct HashBucketNode//哈希桶中的节点
{
HashBucketNode(const K& data)
: _pNext(nullptr), _data(data)
{}
HashBucketNode<K>* _pNext;
K _data;
};
template<class K, class T, class KeyOfValue, class HF>
class HashBucket;
// 注意:因为哈希桶在底层是单链表结构,所以哈希桶的迭代器不需要--操作
//map和set中迭代器是相同的,所以都放到hashtable.h中来实现了
template <class K, class T, class KeyOfValue, class HF>
struct HBIterator
{
typedef HashBucket<K, T, KeyOfValue, HF> HashBucket;
typedef HashBucketNode<T>* PNode;
public:
typedef HBIterator<K, T, KeyOfValue, HF> Self;
HBIterator(PNode pNode = nullptr, HashBucket* pHt = nullptr)//实现一下构造
:_pNode(pNode)
,_pHt(pHt)
{
}
Self& operator++()//前置++
{
// 当前迭代器所指节点后还有节点时直接取其下一个节点
if (_pNode->_pNext)
{
_pNode = _pNode->_pNext;
}
else
{
// 找下一个不空的桶,返回该桶中第一个节点
size_t bucketNo = _pHt->HashFunc(KeyOfValue()(_pNode->_data)) + 1;
for (; bucketNo < _pHt->BucketCount(); ++bucketNo)
{
if (_pNode = _pHt->_table[bucketNo])
break;
}
}
return *this;
}
Self operator++(int)//实现后置++
{
Self it = *this;
if (_pNode->_pNext)
_pNode = _pNode->_pNext;
else
{
// 找下一个不空的桶,返回该桶中第一个节点
size_t bucketNo = _pHt->HashFunc(KeyOfValue()(_pNode->_data)) + 1;
for (; bucketNo < _pHt->BucketCount(); ++bucketNo)
{
if (_pNode = _pHt->_table[bucketNo])
break;
}
}
return it;
}
T& operator*()
{
return _pNode->_data;
}
T* operator->()
{
return &_pNode->_data;
}
bool operator==(const Self& it) const
{
return _pNode == it._pNode;
}
bool operator!=(const Self& it) const
{
return _pNode != it._pNode;
}
PNode _pNode; // 当前迭代器关联的节点
HashBucket* _pHt; // 哈希桶--主要是为了找下一个空桶时候方便
};
// 本文所实现的哈希桶中key是唯一的
template<class K, class T, class KeyOfValue, class HF>
class HashBucket
{
//把迭代器列为友元,这样迭代器就可以使用哈希桶中的成员了
template <class K, class T, class KeyOfValue, class HF>
friend struct HBIterator;
typedef HBIterator<K, T, KeyOfValue, HF> iterator;
typedef HashBucketNode<T> Node;//把节点重命名为Node
typedef Node* PNode;
typedef HashBucket< K, T, KeyOfValue, HF> Self;
public:
size_t GetNextPrime(const 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
};
// 获取比prime大那一个素数
size_t i = 0;
for (; i < PRIMECOUNT; ++i)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[PRIMECOUNT-1];
}
HashBucket(size_t capacity)
: _table(GetNextPrime(capacity))
, _size(0)
{}
HashBucket()
:_table(GetNextPrime(0))
,_size(0)
{}
//添加了迭代器
iterator begin()
{
for (size_t i = 0; i < _table.size();++i)
{
if(_table[i])
{
return iterator(_table[i], this);
}
}
return end();
}
iterator end()
{
return iterator(nullptr, this);
}
~HashBucket()
{
Clear();
}
// 哈希桶中的元素不能重复
//返回的节点从Node*改为pair<iterator,bool>
pair<iterator,bool> Insert(const T& data)
{
CheckCapacity();//检查一下容量,满了扩容
KeyOfValue kov;
size_t index = HashFunc(kov(data));//取data映射的位置
PNode cur = _table[index];//这个位置的指针
while (cur)//指针不为空
{
if (cur->_data == data)
{
return { { cur, this }, false };
}
cur = cur->_pNext;
}
//出来以后,没有返回,说明这里没有重复值
//插入
PNode newnode = new Node(data);
newnode->_pNext = _table[index];
_table[index] = newnode;
++_size;
return { {newnode, this}, true };
}
// 删除哈希桶中为data的元素(data不会重复)
bool Erase(const K& key)
{
//1.如果哈希表中没有元素,删除失败
if (_table.size() == 0)
{
return false;
}
//2.找data的映射值
KeyOfValue kov;
size_t index = HashFunc(key);
PNode cur = _table[index];
PNode prev = nullptr;
while (cur)
{
if (kov(cur->_data) != key)
{
prev = cur;
cur = cur->_pNext;
}
else
{
break;
}
}
//找到了
if (prev)//cur不是头节点
{
prev->_pNext = cur->_pNext;
}
else
{
_table[index] = cur->_pNext;
}
delete cur;
_size--;
return true;
}
iterator Find(const K& key)
{
if (_table.size() == 0)
{
return iterator(nullptr,this);
}
KeyOfValue kov;
size_t index = HashFunc(key);
index %= _table.size();
PNode cur = _table[index];
while (cur)
{
if (kov(cur->_data) == key)
{
return iterator(cur,this);
}
cur = cur->_pNext;
}
return iterator(nullptr,this);
}
size_t Size()const
{
return _size;
}
bool Empty()const
{
return 0 == _size;
}
void Clear()
{
size_t i = 0;
for (; i < _table.size();++i)
{
PNode cur = _table[i];
while (cur)
{
PNode next = cur->_pNext;
delete cur;
cur = next;
}
_table[i] = nullptr;
}
_size = 0;
}
size_t BucketCount()const
{
return _table.capacity();
}
void Swap(Self& ht)
{
_table.swap(ht._table);
swap(_size, ht._size);
}
size_t BucketSize(const K& key)
{
return HashFunc(key);
}
private:
size_t HashFunc(const K& data)
{
return HF()(data) % _table.capacity();
}
void CheckCapacity()
{
if (_size == _table.size())
{
size_t newcapacity = GetNextPrime(_table.capacity());
Self newtable(newcapacity);
for (size_t i = 0; i < _table.size();++i)
{
PNode cur = _table[i];
while (cur)
{
newtable.Insert(cur->_data);
cur = cur->_pNext;
}
}
Swap(newtable);
}
}
private:
vector<Node*> _table;
size_t _size; // 哈希表中有效元素的个数
};
}
unordered_map.h
这里typedef 的时候是pair<const K, V>,不是pair<K , V>,请注意。
#pragma once
#include "hashtable.h"
namespace ting
{
// unordered_map中存储的是pair<K, V>的键值对,K为key的类型,V为value的类型,HF哈希函数类型
// unordered_map在实现时,只需将hashbucket中的接口重新封装即可
template<class K, class V, class HF = HashFunc<V>>
class unordered_map
{
// 通过key获取value的操作
struct mapKeyOfValue
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename OpenHash::HBIterator<K, pair<const K, V>, mapKeyOfValue, HF> iterator;
typedef typename OpenHash::HashBucket<K, pair<const K, V>, mapKeyOfValue, HF> HT;
//typedef OpenHash::HBIterator<K, pair<K,V>, KeyOfValue, HF> iterator;
//typename typedef HT::Iterator iterator;
unordered_map() : _ht()
{}
iterator begin() { return _ht.begin(); }
iterator end() { return _ht.end(); }
// capacity
size_t size()const { return _ht.size(); }
bool empty()const { return _ht.empty(); }
// Access
V& operator[](const K& key)
{
pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
return ret.first->second;
}
const V& operator[](const K& key)const
{
pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
return ret.first->second;
}
// lookup
iterator find(const K& key) { return _ht.Find(key); }
size_t count(const K& key) { return _ht.Count(key); }
// modify
pair<iterator, bool> insert(const pair<K,V> value)
{
return _ht.Insert(value);
}
iterator erase(iterator position)
{
return _ht.Erase(position);
}
// bucket
size_t bucket_count() { return _ht.BucketCount(); }
size_t bucket_size(const K& key) { return _ht.BucketSize(key); }
private:
HT _ht;
};
void maptest()
{
unordered_map<string, string> m;
m.insert(make_pair("left",""));
m.insert(make_pair("right",""));
m.insert(make_pair("medium",""));
for (auto e : m)
{
cout << e.first << endl;
}
}
}
unordered_set.h
#pragma once
#include "hashtable.h"
namespace ting
{
//unordered_set中存储的是K类型,HF哈希函数类型
// unordered_set在实现时,只需将hashbucket中的接口重新封装即可
template<class K, class HF = HashFunc<K>>
class unordered_set
{
// 通过key获取value的操作
struct setKeyOfValue
{
const K& operator()(const K& data)
{
return data;
}
};
public:
typedef typename OpenHash::HBIterator<K, K, setKeyOfValue, HF> iterator;
typedef typename OpenHash::HashBucket<K, K, setKeyOfValue, HF> HT;
unordered_set() : _ht() {}
iterator begin() { return _ht.begin(); }
iterator end() { return _ht.end(); }
// capacity
size_t size()const { return _ht.size(); }
bool empty()const { return _ht.empty(); }
///
// lookup
iterator find(const K& key) { return _ht.Find(key); }
size_t count(const K& key) { return _ht.Count(key); }
/
// modify
pair<iterator, bool> insert(const K& value)
{
return _ht.Insert(value);
}
bool erase(const K& key)
{
return _ht.Erase(key);
}
// bucket
size_t bucket_count() { return _ht.BucketCount(); }
size_t bucket_size(const K& key) { return _ht.BucketSize(key); }
private:
HT _ht;
};
void settest()
{
unordered_set<int> s1;
s1.insert(1);
s1.insert(2);
s1.insert(3);
for (auto e : s1)
{
cout << e << endl;
}
unordered_set<int>::iterator it = s1.find(1);
it = s1.begin();
while (it != s1.end())
{
cout << *it << endl;
++it;
}
}
}