【C++从入门到踹门】第十八篇(下):使用哈希表封装unorder_map和unordered_set

目录

  • 模板参数
  • 哈希表的迭代器实现
  • 完善哈希表
    • 哈希表的默认成员函数
    • 引入迭代器
  • 封装unordered_map和unordered_set
    • unordered_set
    • unordered_map

【C++从入门到踹门】第十八篇(下):使用哈希表封装unorder_map和unordered_set_第1张图片

完整代码已上传至gitee:unordered_set 和 unordered_map 实现

模板参数

unorder_set是K模型的容器,unorder_map是KV模型的容器

要想使用哈希表同时适配K模型和KV模型,就只能对哈希表的模板参数给出控制。同时unorder_set 和 unorder_map 需要给出解析key的仿函数KeyOfValue

框架如下:

  • 哈希表 HashTable 的基本框架

    其中find,insert,erase函数已在上篇中实现,这里还需略作修改

namespace OpenHash//开散列的命名空间,防止与std冲突
{
	//Key转size_t仿函数
	template<class K>
	struct Hash
	{
		const size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};

    //针对string类型的key的特化
	template<>
	struct Hash<string>
	{
		const size_t operator()(const string& key)
		{
			size_t hash = 0;
			for (size_t i = 0; i < key.size(); ++i)
			{
				hash *= 131;
				hash += key[i];
			}
			return hash;
		}
	};

    //哈希数据类型,统一为Value类型,
    //Value是什么由传入的是map还是set决定
	template <class Value>
	struct HashData
	{
		//链表指针
		HashData<Value>* _next;
		//链表结点数据
		Value _data;
		HashData(const Value& data) :_data(data), _next(nullptr)
		{}
	};

    //哈希表
    template <class K, class Value, class KeyOfValue,class HashFunc = Hash<K> >
	class HashTable
	{
		typedef HashData<Value> Node;
	public:
		HashTable() = default;//显示指定生成默认构造

		//拷贝构造
		HashTable(const  HashTable& ht)
		{
			//...
		}

		//赋值重载
		HashTable& operator=(const HashTable ht)
		{
            //...
		}


		//析构
		~HashTable()
		{
            //...
		}

		Node* find(const K& key)
		{
			//...
		}

		bool insert(const Value& data)
		{
			//...
		}

		bool erase(const K& key)
		{
			//...
		}


	private:
		vector<Node*> _table;//指针数组
		size_t _n = 0;//实际存放的元素数量
	};

}
  • unordered_map 基本框架
namespace mystd
{
	template <class K,class Value,class HashFunc=OpenHash::Hash<K>>
	class unordered_map
	{
		struct MapKOfValue//unordered_map提供的key解析仿函数
		{
			//从pair中解析出key
			const K operator()(const pair<const K, Value>& kv)
			{
				return kv.first;
			}
		};
	public:
	private:
		OpenHash::HashTable<K, pair<K,Value>, MapKOfValue, HashFunc> _ht;
	};
}
  • unordered_set 基本框架
namespace mystd
{
	template <class K,class HashFunc=OpenHash::Hash<K>>
	class unordered_set
	{
		struct SetKOfValue//unordered_set提供的key解析仿函数
		{
			//直接返回key
			const K& operator()(const K& k)
			{
				return k;
			}
		};
	public:
	private:
		OpenHash::HashTable<K, K, SetKOfValue, OpenHash::Hash<K>> _ht;
	};
}

一个类型做map/set的Key的要求,能支持比较大小

一个类型做unordered_map/unordered_set的Key的要求,能支持转换成整型(为了取模),并可以支持相等比较

哈希表的迭代器实现

迭代器是对哈希表中元素指针的封装,以支持其移动操作和解引用操作。

  • 迭代器基本框架
namespace mystd
{
    //迭代器
	template <class K, class Value, class KeyOfValue, class HashFunc = Hash<K> >
	struct HashIterator
	{
		typedef HashData<Value> Node;
		typedef HashIterator<K, Value, KeyOfValue, HashFunc> Self;
		typedef HashTable<K, Value, KeyOfValue, HashFunc> HT;//迭代器需要哈希表以提供遍历

		Node* _node;//结点指针
		HT* _pht;//迭代器所属哈希表的地址

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

重载 operator== 和 operator!=

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

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

重载 operator * 和 operator->

public:
    //返回结点数据的引用
    Value& operator*()
    {
        return _node->_data;
    }
    // 返回结点数据地址
    Value* operator->()
    {
        return &_node->_data;
    }

重载operator++

步骤:

  • 如果当前结点不是当前哈希桶的最后一个结点,则直接往下走;
  • 如果当前结点时当前哈希桶的最后一个结点,那么要找到下一个哈希桶的第一个结点(注意边界控制)
public:
    Self& operator++()
    {

        //桶中若还有数据,就往当前桶后面走
        if (_node->_next)
        {
            _node=_node->_next;
        }
        else//当前桶走完了 换下一个桶
        {
            KeyOfValue kov;//解析key仿函数实例化
            HashFunc hf;//key转size_t仿函数实例化

            //计算当前在哪个桶 
            size_t index = hf((kov(_node->_data))) % _pht->_table.size();
            index++;
            while (index < _pht->_table.size())//防止越界
            {
                if (_pht->_table[index])
                {
                    _node = _pht->_table[index];
                    return *this;
                }
                else
                {
                    index++;
                }
            }
            _node = nullptr;
        }
        return *this;
    }

完善哈希表

哈希表的默认成员函数

构造函数

默认的构造函数

HashTable() = default;//显示指定生成默认构造

拷贝构造函数

由于结点类型为指针。且指针指向了开辟的空间,所以需要深拷贝

  1. 将新表的大小调整为旧表同样大小
  2. 遍历哈希桶逐个开辟空间拷贝
HashTable(const  HashTable& ht)
{
    _n = ht._n;
    _table.resize(ht._table.size());
    for (int i = 0; i < ht._table.size(); ++i)
    {
        Node* cur = ht._table[i];
        while (cur)
        {
            Node* newnode = new Node(cur->_data);
            //头插
            newnode->_next = _table[i];
            _table[i] = newnode;

            cur = cur->_next;
        }
    }
}

赋值重载

通过传值参数的拷贝构造,置换参数临时对象和当前对象的成员变量即可

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

析构函数

遍历结点,逐个delete

//析构
~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;
    }
}

引入迭代器

迭代器实现后需要将其引入哈希表中

  1. 由于封装的unordered_map 和 unordered_set需要访问迭代器,所以在哈希表中,迭代器的typedef应该置于public区域中。
  2. 在迭代器中访问了哈希表的成员变量_table,所以需要将迭代器声明为哈希表的友元。
  3. 哈希表中,find函数返回类型为迭代器,insert函数的返回值将改为 pair.
template <class K, class Value, class KeyOfValue,class HashFunc = Hash<K> >
class HashTable
{
    typedef HashData<Value> Node;

    template <class K, class Value, class KeyOfValue, class HashFunc>
    friend struct HashIterator;// 将迭代器声明为友元
public:
    typedef HashIterator< K, Value, KeyOfValue, HashFunc> iterator;//引入迭代器

    //...

};

再添加begin和end函数后,改造完毕的哈希表的完整形态如下:

template <class K, class Value, class KeyOfValue,class HashFunc = Hash<K> >
class HashTable
{
    typedef HashData<Value> Node;

    template <class K, class Value, class KeyOfValue, class HashFunc>
    friend struct HashIterator;
public:
    typedef HashIterator< K, Value, KeyOfValue, HashFunc> iterator;


    HashTable() = default;//显示指定生成默认构造

    //拷贝构造

    HashTable(const  HashTable& ht)
    {
        _n = ht._n;
        _table.resize(ht._table.size());
        for (int i = 0; i < ht._table.size(); ++i)
        {
            Node* cur = ht._table[i];
            while (cur)
            {
                Node* newnode = new Node(cur->_data);
                //头插
                newnode->_next = _table[i];
                _table[i] = newnode;

                cur = cur->_next;
            }
        }
    }

    //赋值重载
    HashTable& operator=(const 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;
        }
    }




    iterator begin()
    {
        size_t index = 0;
        while (index < _table.size())
        {
            if (_table[index] != nullptr)
            {
                return iterator(_table[index], this);
            }
            index++;
        }
        return end();
    }

    iterator end()
    {
        return iterator(nullptr, this);
    }

    iterator find(const K& key)
    {
        
        if (_table.size() == 0)//元素数量为0,无法查找
        {
            return end();
        }

        KeyOfValue kov;
        HashFunc hf;//key转size_t 仿函数实例化

        //计算哈希地址
        size_t index = hf(key) % _table.size();
        Node* cur = _table[index];
        while (cur)//遍历哈希桶
        {
            if (kov(cur->_data) == key)
            {
                return iterator(cur,this);
            }
            cur = cur->_next;
        }
        return end(); 
    }

    pair<iterator,bool> insert(const Value& data)
    {
        KeyOfValue kov;
        iterator ret = find(kov(data));
        if (ret!=end())
        {
            return make_pair(ret,false);
        }
        HashFunc hf;//key转size_t仿函数实例化
        

        //负载因子为1 增容
        if (_n == _table.size())
        {
            size_t newsize = _n == 0 ? 10 : 2 * _table.size();
            vector<Node*> newtable;
            newtable.resize(newsize);
            for (size_t i = 0; i < _n; ++i)
            {
                if (_table[i])//桶不空,遍历该哈希桶
                {
                    //拿到这个桶的头结点
                    Node* cur = _table[i];
                    while (cur)
                    {
                        Node* next = cur->_next;

                        //重新计算映射位置
                        size_t index = hf(kov(cur->_data)) % newtable.size();
                        //插入至新表
                        cur->_next = newtable[index];
                        newtable[index] = cur;
                        cur = next;
                    }
                }
            }
            _table.swap(newtable);//交换哈希表
        }


        size_t index = hf(kov(data)) % _table.size();
        Node* newnode = new Node(data);

        //头插
        newnode->_next = _table[index];
        _table[index] = newnode;
        ++_n;

        return make_pair(iterator(newnode,this),true);
    }

    bool erase(const K& key)
    {
        KeyOfValue kov;
        HashFunc hf;
        
        //1.计算哈希地址
        size_t index = hf(key) % _table.size();

        //2.在桶中寻找待删除结点
        Node* cur = _table[index];
        Node* prev = nullptr;
        while (cur)
        {
            if (kov(cur->_data) == key)
            {
                if (prev == nullptr)//cur为头结点
                {
                    _table[index] = cur->_next;
                }
                else//cur为中间结点
                {
                    prev->_next = cur->_next;
                }
                delete cur;
                --_n;
                return true;
            }
            prev = cur;
            cur = cur->_next;
        }
        return false;
    }


private:
    vector<Node*> _table;//指针数组
    size_t _n = 0;//实际存放的元素数量
};

封装unordered_map和unordered_set

unordered_set

#pragma once
#include "HashTable.h"

namespace mystd
{
	template <class K,class HashFunc=OpenHash::Hash<K>>
	class unordered_set
	{
		struct SetKOfValue
		{
			//set使用
			const K& operator()(const K& k)
			{
				return k;
			}
		};

	public:
		typedef  typename OpenHash::HashTable<K, K, SetKOfValue, HashFunc>::iterator iterator;

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

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

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

		pair<iterator,bool> Insert(const K& k)
		{
			return _ht.insert(k); 
		}
		
		void erase(const K& key)
		{
			_ht.erase(key);
		}
	private:
		OpenHash::HashTable<K, K, SetKOfValue, OpenHash::Hash<K>> _ht;
	};

}

unordered_map

#pragma once
#include "HashTable.h"

namespace mystd
{
	template <class K,class Value,class HashFunc=OpenHash::Hash<K>>
	class unordered_map
	{
		struct MapKOfValue
		{
			//map使用
			const K operator()(const pair<const K, Value>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename OpenHash::HashTable<K, pair<K, Value>, MapKOfValue, HashFunc>::iterator iterator;

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

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

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

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

		void erase(const K& key)
		{
			_ht.erase(key);
		}

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

	private:
		OpenHash::HashTable<K, pair<K,Value>, MapKOfValue, HashFunc> _ht;
	};
}

— end —

青山不改 绿水长流

你可能感兴趣的:(C++,散列表,c++,哈希算法)