哈希表的简单实现

文章介绍了C++中使用模板实现的哈希表,包括插入、查找和删除操作,以及处理哈希冲突的线性探测、二次探测和开散列法。重点讲解了线性探测的实现机制和其优缺点。
摘要由CSDN通过智能技术生成
#pragma once
#include <iostream>
#include <vector>

using namespace std;

namespace myhash
{
    enum State
    {
        EMPTY,
        EXIST,
        DELETE
    };

    template <class K, class V>
    struct HashData
    {
        pair<K, V> _kv;
        State _state;
    };

    template <class K, class V>
    class HashTable
    {
    public:
        HashTable() : _size(0) {}

        bool Insert(const pair<K, V>& kv)
        {
            if (_table.size() == 0 || 10 * _size / _table.size() >= 7)
            {
                Rehash();
            }

            size_t index = kv.first % _table.size();
            while (_table[index]._state == EXIST)
            {
                index++;
                index %= _table.size();
            }
            _table[index]._kv = kv;
            _table[index]._state = EXIST;
            ++_size;

            return true;
        }

        HashData<K, V>* Find(const K& key)
        {
            if (_table.size() == 0)
            {
                return nullptr;
            }
            size_t index = key % _table.size();
            while (_table[index]._state != EMPTY)
            {
                if (_table[index]._state == EXIST && _table[index]._kv.first == key)
                {
                    return &_table[index];
                }
                index++;
                index %= _table.size();
            }
            return nullptr;
        }

        bool Erase(const K& key)
        {
            HashData<K, V>* ret = Find(key);
            if (ret)
            {
                ret->_state = DELETE;
                --_size;
                return true;
            }
            else
            {
                return false;
            }
        }

    private:
        vector<HashData<K, V>> _table;
        size_t _size;

        void Rehash()
        {
            size_t newsize = _table.empty() ? 10 : _table.size() * 2;
            HashTable<K, V> newHT;
            newHT._table.resize(newsize);
            for (auto e : _table)
            {
                if (e._state == EXIST)
                {
                    newHT.Insert(e._kv);
                }
            }
            _table.swap(newHT._table);
        }
    };

} // namespace myhash

namespace HashBucket
{
    template <class K,class V>
    class HashNode
    {
        pair<K, V> _kv;
        HashNode<K,V>* next;
        Hashnode()
            :_kv(kv)
            ,next(nullptr)
        {}
    };

    template <class K,class V>
    class HashTable
    {
        typedef HashNode<K, V> node;
    public:
        //由于编译器会自动生成析构函数对内置类型vector进行析构,但是挂在下面的节点没有释放,因此需要自己手动写一个析构函数
        ~HashTable()
        {
            for (int i = 0; i < _table.size(); i++)
            {
                node* cur = _table[i]
                    while (cur)
                    {
                        node* next = cur->next;
                        free(cur);
                        cur = next;
                    }
                _table[i] = nullptr;
            }
        }
        bool Insert(const pair<K, V>& kv)
        {
            //去重,如果有就不插入
            if (find(kv.first))
            {
                return false;
            }
            //如果负载因子超过1就进行扩容
            if (_size == _table.size())
            {
                //这里开辟一个新哈希表,然后把原节点重新映射到新表中,最后交换删除
                size_t newsize = _size == 0 ? 10 : 2 * _size;
                vector<node*> newtable;
                newtable.resize(newsize, nullptr);
                //重新映射
                for (int i = 0; i < _table.size(); i++)
                {
                    node* cur = _table[i];
                    while (cur)
                    {
                        size_t newindex = cur->_kv.first % newsize;
                        node* next = cur->next;
                        cur->next = newtable[newindex];
                        newtable[newindex] = cur;
                        cur = next;
                    }
                    _table[i] = nullptr;
                }
                //交换删除
                _table.swap(newtable);
            }
            int index = kv.first % _table.size();
            //头插
            node* newnode = new node(kv);
            newnode->next = _table[index];
            _table[index] = newnode;
            ++_size;
            return true;
        }

        node* Find(const K& key)
        {
            if (_table.size() == 0)
            {
                return nullptr;
            }
            int index = key % _table.size();
            node* cur = _table[index];
            while (cur)
            {
                if (cur->_kv.first == key)
                {
                    return cur;
                }
                cur = cur->next;
            }
            return nullptr;
        }
    private:
        vector<node*> _table;
        size_t _size = 0;
    };
}

哈希的定义:

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素

时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即

O(log_2 N),搜索的效率取决于搜索过程中元素的比较次数。

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。

如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立

一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

插入元素

根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放

搜索元素

对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置

取元素比较,若关键码相等,则搜索成功

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称

为哈希表(Hash Table)(或者称散列表)

当发生哈希冲突时,这里按照最简单的闭散列法中的线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

线性探测的哈希实现:

当我们使用线性探测法来探测该位置元素是否为空,因此我们就需要额外定义一个表示位来标识该位置是否为空。

这里会存在一个问题,就是有可能这个哈希表已经满了,这个时候while就会死循环,所以我们需要扩容。为了衡量什么时候应该扩容,这里引用了一个参数负载因子:

线性探测优点:实现非常简单,

线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同

关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降

低。

2. 二次探测

线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位

置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法

为:$H_i$ = (H_0+ i^2) % m。其中:i = 1,2,3…, H_0是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表 的大小。

但是从本质上来说,这两种方法都是通过寻找下一个空的位置进行占位,没用从根本上解决问题,于是就有了下一种方法:开散列法。

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地

址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链

接起来,各链表的头结点存储在哈希表中。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值