目录

哈希表的源代码

哈希表模板参数的控制

string类型不好取哈希值

哈希函数的默认成员函数

✍️构造函数

✍️拷贝构造函数

✍️赋值运算符重载函数

✍️析构函数

相关迭代器的实现

💡构造函数

💡解引用操作

💡->操作

💡判断是否相等的操作

💡重载++元算符

unoredered_map的模拟实现

unordered_set的模拟实现

最终实现代码

🔖哈希表的实现代码

🔖迭代器的实现代码

🔖unordered_map的实现代码

🔖unordered_set的实现代码


哈希表的源代码

我们这里的实现就是使用我们在数据结构初阶上面的代码来实现的,我们这里采用的开散列的方式来实现哈希表的,有兴趣的友友可以看看这一篇博客——哈希表

#pragma once 
#include <cstddef>
#include <iostream>
#include <vector>
using namespace std;


template <class K, class V>
struct HashNode {
    pair<K, V> _kv;
    HashNode<K, V>* _next;
    HashNode(const pair<K, V>& kv) : _kv(kv), _next(nullptr) {}
};

template <class K, class V>
class HashTable {
    public:
        HashTable() : _table(11), _n(0) {}
        typedef HashTable<K, V> Node;
        bool Insert(const pair<K, V>& kv) {
            Node* pos = Find(kv.first);
            if(pos) {
                return false;
            }
            // 刚刚超过了负载因子
            if(_n == _table.size()) {
                vector<Node*> newTable(_table.size() * 2);
                for(size_t i = 0; i < _table.size(); i++) {
                    Node* cur = _table[i];
                    while(cur) // 遍历原来哈希桶里面的节点
                    {
                        Node* next = cur->_next;
                        size_t hashi = cur->_kv.first % newTable.size();
                        cur->_next = newTable[hashi];
                        newTable[hashi] = cur;
                        cur = next;
                    }
                    _table[i] = nullptr; // 置空
                }
                _table.swap(newTable); // 交换
            }
            // 开始插入键值对
            size_t hashi = kv.first % _table.size();
            // 也是头插
            Node* newnode = new Node(kv);
            newnode->_next = _table[hashi];
            _table[hashi] = newnode;
            _n++;
            return true;
        }
        HashNode<K, V>* Find(const K& key) {
            size_t hashi = key % _table.size();
            HashNode<K, V>* cur = _table[hashi];
            while(cur) {
                if(cur->_kv.first == key) {
                    return cur;
                }
                cur = cur->_next;
            }
            return nullptr;
        }
        bool Erase(const K& key) {
            size_t hashi = key % _table.size();
            Node* prev = nullptr;
            Node* cur = _table[hashi];
            while(cur) {
                if(cur->_kv.first == key) {
                    if(prev == nullptr) // 头节点
                    {
                        _table[hashi] = cur->_next;
                    }else // 在中间
                    {
                        prev->_next = cur->_next;
                    }
                    delete cur;
                    _n--;
                    return true;
                }else {
                    prev = cur;
                    cur = cur->_next;
                }
            }
            return false;
        }
    private:
        vector<Node*> _table;
        size_t _n;
};

哈希表模板参数的控制

我们这里的模板控制逻辑其实是和我们在红黑树封装map和set是一样的,我们这里实现的unordered_map是KV模型,但是我们的unordered_set实现的K模型。

我们这里也是将我们的哈希表的第二个参数设置成了T,然后我们在实现我们的unordered_map的时候,我们就是传入key和value的键值对作为我们的T:

template<class K, class V>
class unordered_map {
public:
    // ...
private:
    HashTable<K, pair<K, V>> _ht
};

当我们实现的unordered_set的时候,我们传入的就是key和key了:

template<class K>
class unordered_set {
public:
    // ...
private:
    HashTable<K, K> _ht;
};

我们这里的T的取值完全就是取决于,我们的实现的需要:

于是我们的哈希表的节点也是有了相应的变化:

template<class T>
struct HashNode {
    T _data;
    HashNode<T>* _next;
    HashNode(const T& data) : _data(data), _next(nullptr) {}
};

我们的实现哈希表里面时常会需要获得我们的键值,但是我们这里统一使用了T类作为我们存储的值的类型,这个T可能是键值对,也可能是键值,但是我们的哈希表是不知道的,所以我们要在实现的结构里面提供一个仿函数给哈希表。

所以我们的unordered_map容器就要给哈希表提供一个仿函数,来返回我们的键值:

template<class K, class V>
class unordered_map {
    struct MapKeyOfT
    {
        const K& operator()(const pair<K, V>& kv)
        {
            return kv.first;
        }
    }
private:
    HashTable<K, pair<K, V>, MapKeyOfT> _ht;
};

虽然我们的unordered_set容器的传入的T就是我们要的键值,但是我们的底层哈希表是不知道的,我们统一使用我们的仿函数来获取key值,所以我们的unordered_set还是要实现一份,即使就是返回自身:

template<class K>
class unordered_set {
    struct SetKeyOfT {
        const K& operator()(const K& key) {
            return key;
        }
    }
private:
    HashTable<K, K, SetKeyOfT> _ht;
};

string类型不好取哈希值

我们上面也已经提到了,我们的哈希表的实现取出哈希值使用的函数是除留余数的方法,但是我们的string类型并不是整数,所以我们就要给string类提供一个哈希函数,但是我们的字符串不可能一一对应一个数字,因为字符串的组成是无穷无尽的,所以不管我们使用了,什么方法我们就会有发生哈希冲突的可能,只是我们的冲突概率不同而已。

我们的计算机大佬们在经过了实验之后,发明了BKDHash算法,这个算法在实际的使用中效果明显要好于其他的算法,所以一直被一些高级语言使用,比如我们的Java。

所以我们的哈希表的模版参数中还有增加一个仿函数,就是我们的哈希函数,由于我们使用

template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
class HashTable

如果我们没有传入这个仿函数,我们就使用了默认的仿函数,也就是直接返回我们的key值,但是我们有的时候会使用到string类型,所以我们个string类设计了一个类模板的特化:

非string:

template<class K>
struct HashFunc {
    size_t operator()(const K& key) {
        return (size_t)key;
    }
};

string:

template<>
struct HashFunc<string> {
    size_t operator()(const string& s) {
    {
        // BKDR
        size_t hash = 0;
        for(auto ch : s) {
            hash += ch;
            hash *= 131;
        }
        return hash;
    }
};

哈希函数的默认成员函数

✍️构造函数

我们实现哈希表的时候,主要就是两个成员变量,一个就是我们存哈希节点的vector,一个就是我们哈希表里面的有效元素个数:

vector<Node*> _table;
size_t _n = 0;

我们这里的构造函数默认是将我们的vector开到我们预处理的空间大小数组的第一个元素:

HashTable()
    :_table(__stl_next_prime(0)
    , _n(0){}

✍️拷贝构造函数

我们这里的拷贝构造使用的是深拷贝,也就是拷贝节点重新插入。

实现的步骤如下:

1、将要拷贝给的对象的哈希表的大小调整为拷贝的对象的大小。

2、将节点一个一个的创建并添加进入要拷贝给的对象中。

3、更新我们的有效数据个数

代码如下:

HashTable(const HashTable& ht) {
    _table.resize(ht._table.size());
    for(size_t i = 0; i < ht._table.size(); i++) {
        if(ht._table[i]) {
            Node* cur = ht._table[i];
            while(cur) {
                Node* temp = new Node(cur->_data);
                temp->_next = _table[i];
                _table[i] temp;
                cur = cur->_next;
            }
        }
    }
    _n = ht._n;
}

✍️赋值运算符重载函数

我们一般地想要实现赋值运算符重载函数,一般的是间接地调用我们之前实现的拷贝构造函数来实现,也就是传值传参之后,交换临时变量和我们的被赋值对象的成员,然后函数调用结束之后我们的原来的值也就被自动地释放掉了。

实现代码如下:

HashTable& operator=(HashTable ht) {
    _table.swap(ht._table);
    swap(_n, ht._n);
    return *this;
}

✍️析构函数

我们这里的析构主要是将我们创建出来的节点一个一个的释放掉。

~HashTable() {
    for(size_t i = 0; i < _table.size(); i++) {
        Node* cur = _table[i];
        while(cur) {
            Node* next = cur->_next;
            delete cur;
            cur = next;
        }
        _table[i] = nullptr;
    }
}

相关迭代器的实现

我们这里封装的主要是正向的迭代器,由于我们这里要实现++运算符的重载,所以我们这里要存储哈希表的地址,方便我们找到下一个哈希桶。

迭代器:

template <class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct HTIerator {
    typedef HashNode<T> Node;
    typedef HashTable<K, T, KeyOfT, Hash> HT;
    typedef HTIterator<K, T, Ref, Ptr, keyOfT, Hash> Self;
    
    Node* _node;
    const HT* _ht;
};

💡构造函数

HTIterator(Node* node, HT* pht) 
    :_node(node)
    , _pht(pht){}

💡解引用操作

Ref operator*() {
    return _node->data;
}

💡->操作

我们只要返回我们的节点指针即可

Ptr operator->() {
    return &_node->_data;
}

💡判断是否相等的操作

bool operator!=(const Self& s) {
    return _node != s._node;
}
bool operator==(const Self& s) {
    return _node==s._node;
}

💡重载++元算符

我们这里实现的++运算符的逻辑如下:

1、如果我们的当前节点不是当前哈希桶的最后一个节点,就++到我们哈希桶的下一个节点即可。

2、如果是当前我们哈希桶的最后一个节点了,我们就++到下一个非空哈希桶的第一个节点的位置。

3、所有的哈希桶都遍历过了,我们就给当前的位置的迭代器的指针置空即可。

4、上面的操作完毕后,返回当前对象的引用。

代码如下:

Self& operator++() {
    if(_node->_next) {
        _node = _node->_next;
    }else {
        KeyOfT kot;
        Hash hash;
        size_t hashi = hash(kot(_node->_data)) % _ht->_table.size();
        hashi++;
        while(hashi < _ht->_table.size())
        {
            _node = _ht->_table[hashi];
            if(_node) // 找到了一个非空的桶了
                break;
            else // 没找到继续
                hashi++;
        }
        if(hashi == _ht->_table.size()) // 所有的桶都找完了
        {
            _node = nullptr;
        }
    }
    return *this;
}
        

实现完了上面这几个操作之后,我们就要来实现我们的哈希表的两个中要的迭代器相关函数了:

一个是begin,一个是end

template<class K, class T, class KeyOfT, class Hash>
class HashTable {
    template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
    friend struct HTIterator; // 友员类
    typedef HashNode<T> Node;
public:
    typedef HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
    iterator begin() {
        if(_n == 0) {
            return end();
        }
        size_t i = 0;
        while(i < _table.size()) {
            if(_table[i]) {
                return iterator(_table[i], this);
            }
        }
        return end();
    }
    iterator end() {
        return iterator(nullptr, this);
    }
private:
    vector<Node*> _table;
    size_t _n = 0; 
}

unoredered_map的模拟实现

#include "HashTable.h"


template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
    struct MapKeyOfT
    {
        const K& operator()(const pair<K, V>& kv)
        {
            return kv.first;
        }
    };
public:

    typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;

    iterator begin()
    {
        return _ht.Begin();
    }

    iterator end()
    {
        return _ht.End();
    }

    V& operator[](const K& key)
    {
        pair<iterator, bool> ret = insert({ key, V() });
        return ret.first->second;
    }

    pair<iterator, bool> insert(const pair<K, V>& kv)
    {
        return _ht.Insert(kv);
    }

    iterator Find(const K& key)
    {
        return _ht.Find(key);
    }

    bool Erase(const K& key)
    {
        return _ht.Erase(key);
    }

private:
    HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};

unordered_set的模拟实现

#include "HashTable.h"

template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
    struct MapKeyOfT
    {
        const K& operator()(const pair<K, V>& kv)
        {
            return kv.first;
        }
    };
public:

    typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;

    iterator begin()
    {
        return _ht.Begin();
    }

    iterator end()
    {
        return _ht.End();
    }

    V& operator[](const K& key)
    {
        pair<iterator, bool> ret = insert({ key, V() });
        return ret.first->second;
    }

    pair<iterator, bool> insert(const pair<K, V>& kv)
    {
        return _ht.Insert(kv);
    }

    iterator Find(const K& key)
    {
        return _ht.Find(key);
    }

    bool Erase(const K& key)
    {
        return _ht.Erase(key);
    }

private:
    HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};

最终实现代码

🔖哈希表的实现代码

#include <iostream>
#include <vector>
using namespace std;
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		// BKDR
		size_t hash = 0;
		for (auto ch : s)
		{
			hash += ch;
			hash *= 131;
		}

		return hash;
	}
};

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);
	return pos == last ? *(last - 1) : *pos;
}
template<class T>
struct HashNode
{
    T _data;
    HashNode<T>* _next;

    HashNode(const T& data)
        :_data(data)
        , _next(nullptr)
    {}
};


template<class K, class T, class KeyOfT, class Hash>
class HashTable
{
    // 友元声明
    template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
    friend struct HTIterator;

    typedef HashNode<T> Node;
public:
    typedef HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
    typedef HTIterator<K, T, const T&, const T*, KeyOfT, Hash> ConstIterator;

    Iterator Begin()
    {
        if (_n == 0)
            return End();

        for (size_t i = 0; i < _tables.size(); i++)
        {
            Node* cur = _tables[i];
            if (cur)
            {
                return Iterator(cur, this);
            }
        }

        return End();
    }

    Iterator End()
    {
        return Iterator(nullptr, this);
    }


    ConstIterator Begin() const
    {
        if (_n == 0)
            return End();

        for (size_t i = 0; i < _tables.size(); i++)
        {
            Node* cur = _tables[i];
            if (cur)
            {
                return ConstIterator(cur, this);
            }
        }

        return End();
    }

    ConstIterator End() const
    {
        return ConstIterator(nullptr, this);
    }

    HashTable()
        :_tables(__stl_next_prime(0))
        , _n(0)
    {}

    // 拷贝构造和赋值重载也需要

    ~HashTable()
    {
        for (size_t i = 0; i < _tables.size(); i++)
        {
            Node* cur = _tables[i];
            while (cur)
            {
                Node* next = cur->_next;
                delete cur;

                cur = next;
            }

            _tables[i] = nullptr;
        }
    }

    pair<Iterator, bool> Insert(const T& data)
    {
        KeyOfT kot;
        Iterator it = Find(kot(data));
        if (it != End())
            return { it, false};

        Hash hash;

        // 负载因子 == 1时扩容
        if (_n == _tables.size())
        {
            /*HashTable<K, V> newht;
            newht._tables.resize(__stl_next_prime(_tables.size() + 1));
            for (size_t i = 0; i < _tables.size(); i++)
            {
                Node* cur = _tables[i];
                while (cur)
                {
                    newht.Insert(cur->_kv);
                    cur = cur->_next;
                }
            }

            _tables.swap(newht._tables);*/
            vector<Node*> newTable(__stl_next_prime(_tables.size()+1));
            for (size_t i = 0; i < _tables.size(); i++)
            {
                Node* cur = _tables[i];
                while (cur)
                {
                    Node* next = cur->_next;
                    // 头插到新表
                    size_t hashi = hash(kot(cur->_data)) % newTable.size();
                    cur->_next = newTable[hashi];
                    newTable[hashi] = cur;

                    cur = next;
                }

                _tables[i] = nullptr;

            }

            _tables.swap(newTable);
        }

        size_t hashi = hash(kot(data)) % _tables.size();
        // 头插
        Node* newnode = new Node(data);
        newnode->_next = _tables[hashi];
        _tables[hashi] = newnode;
        ++_n;

        return { Iterator(newnode, this), false };
    }

    Iterator Find(const K& key)
    {
        KeyOfT kot;
        Hash hash;
        size_t hashi = hash(key) % _tables.size();
        Node* cur = _tables[hashi];
        while (cur)
        {
            if (kot(cur->_data) == key)
            {
                return Iterator(cur, this);
            }

            cur = cur->_next;
        }

        return End();
    }

    bool Erase(const K& key)
    {
        KeyOfT kot;
        size_t hashi = key % _tables.size();
        Node* prev = nullptr;
        Node* cur = _tables[hashi];
        while (cur)
        {
            if (kot(cur->_data) == key)
            {
                if (prev == nullptr)
                {
                    // 头结点
                    _tables[hashi] = cur->_next;
                }
                else
                {
                    // 中间节点
                    prev->_next = cur->_next;
                }

                delete cur;
                --_n;

                return true;
            }
            else
            {
                prev = cur;
                cur = cur->_next;
            }
        }

        return false;
    }
private:
    vector<Node*> _tables; // 指针数组
    size_t _n = 0;		   // 表中存储数据个数
};

🔖迭代器的实现代码

// 前置声明
template<class K, class T, class KeyOfT, class Hash>
class HashTable;

template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct HTIterator
{
    typedef HashNode<T> Node;
    typedef HashTable<K, T, KeyOfT, Hash> HT;
    typedef HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;

    Node* _node;
    const HT* _ht;

    HTIterator(Node* node, const HT* ht)
        :_node(node)
        ,_ht(ht)
    {}

    Ref operator*()
    {
        return _node->_data;
    }

    Ptr operator->()
    {
        return &_node->_data;
    }

    bool operator!=(const Self& s)
    {
        return _node != s._node;
    }

    // 16:46
    Self& operator++()
    {
        if (_node->_next)
        {
            // 当前桶还有数据,走到当前桶下一个节点
            _node = _node->_next;
        }
        else
        {
            // 当前桶走完了,找下一个不为空的桶
            KeyOfT kot;
            Hash hash;
            size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
            ++hashi;
            while (hashi < _ht->_tables.size())
            {
                _node = _ht->_tables[hashi];

                if (_node)
                    break;
                else
                    ++hashi;
            }

            // 所有桶都走完了,end()给的空标识的_node
            if (hashi == _ht->_tables.size())
            {
                _node = nullptr;
            }
        }

        return *this;
    }

};

🔖unordered_map的实现代码

#include "HashTable.h"


template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
    struct MapKeyOfT
    {
        const K& operator()(const pair<K, V>& kv)
        {
            return kv.first;
        }
    };
public:

    typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;

    iterator begin()
    {
        return _ht.Begin();
    }

    iterator end()
    {
        return _ht.End();
    }

    V& operator[](const K& key)
    {
        pair<iterator, bool> ret = insert({ key, V() });
        return ret.first->second;
    }

    pair<iterator, bool> insert(const pair<K, V>& kv)
    {
        return _ht.Insert(kv);
    }

    iterator Find(const K& key)
    {
        return _ht.Find(key);
    }

    bool Erase(const K& key)
    {
        return _ht.Erase(key);
    }

private:
    HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};

🔖unordered_set的实现代码

#include "HashTable.h"

template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
    struct MapKeyOfT
    {
        const K& operator()(const pair<K, V>& kv)
        {
            return kv.first;
        }
    };
public:

    typedef typename HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;

    iterator begin()
    {
        return _ht.Begin();
    }

    iterator end()
    {
        return _ht.End();
    }

    V& operator[](const K& key)
    {
        pair<iterator, bool> ret = insert({ key, V() });
        return ret.first->second;
    }

    pair<iterator, bool> insert(const pair<K, V>& kv)
    {
        return _ht.Insert(kv);
    }

    iterator Find(const K& key)
    {
        return _ht.Find(key);
    }

    bool Erase(const K& key)
    {
        return _ht.Erase(key);
    }

private:
    HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};

更多推荐