关于哈希表

学习完了有关二叉树的搜索结构,今天我们来接触另外一种结构,叫做哈希表。

1.什么是哈希表


哈希表:是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。它的所保存的数据和位置会有一种关系,这种关系我们叫做哈希函数。

2.构建哈希表的方法


常用的构建哈希表的方法有5种。

这5种方法分别是:
构建哈希表的方法有:
1)直接定址法: 该方法是取关键字的某个线性函数值为哈希地址
2)除留余数法: 它是用数据元素关键字除以某个常数所得的余数作为哈希地址。
3)折叠法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。
4)随机数法::选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。
5)*数学分析法:找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。

一般常用的方法是直接定址法和数学分析法。下面我通过数学分析法来说明。

在这不得不说的是一个叫做哈希冲突的问题:因为你散列函数所计算的位置很可能有多个值,所以这多个值就会造成冲突,我们也就叫做哈希冲突,为了解决哈希冲突,研究了这几种方法。
这里写图片描述

3.开放定址法


也叫做闭散列法,我们在这里提供两种思路,一种是线性探测,一种是二次探测。

线性探测:线性探测说的就是,当你插入的一个数和所在位置上的数冲突了,这个时候,你就向后找,找到为空的位置进行存放这个数。
这里写图片描述

正是因为开放地址法的缺点,所以我们在对哈希表进行删除的时候,所要采取的删除方式采用懒惰删除,就是对需要删除的元素,我们不直接删除,而是给它一个标记,表示它已经被删除,我们不对它访问就好了。
这里写图片描述
另外,哈希的容量根据研究如果采用素数来说,冲突概率更小,所以我们提供一个素数表来取容量。
代码实现:

namespace open
{
    enum Status
    {
        EMPTY,
        DELETE,
        EXIST
    };

    template<typename K, typename V>
    struct HashNode
    {

        HashNode(const K &key = K(), const V&value = V())//给出默认实参
        : _key(key)
        , _value(value)
        , _status(EMPTY)
        {
        }

        K _key;
        V _value;
        Status _status;
    };
    template<typename K>
    struct _Hashfunc
    {
        size_t operator ()(const K &key)
        {
            return key;
        }
    };
    template<>
    struct _Hashfunc<string>
    {
        static size_t BKDRHash(const char*str)
        {
            unsigned int seed = 131;// 31 131 1313 13131 131313
            unsigned int hash = 0;
            while (*str)
            {
                hash = hash*seed + (*str++);
            }
            return(hash & 0x7FFFFFFF);
        }
        size_t operator ()(const string &key)
        {
            return BKDRHash(key.c_str());
        }
    };

    template<typename K, typename V, typename HashFunc = _Hashfunc<K>>
    class Hashtable
    {
        typedef HashNode<K, V> Node;
        typedef Hashtable<K, V, HashFunc> HashTable;

    public:
        Hashtable()
            :_size(0)
        {
            _ht.resize(GetNewSize());
        }
        ~Hashtable()
        {
            ;
        }
        bool Insert(const K& key, const V& value)
        {
            //进行是否增容的检查。
            _CheckCapacity();
            size_t index = _HashFunc(key);
            while (_ht[index]._status == EXIST)
            {
                if (_ht[index]._key == key)
                    return false;
                ++index;
                if (index == _size)
                    index = 0;
            }
            _size++;
            _ht[index]._status = EXIST;
            _ht[index]._key = key;
            _ht[index]._value = value;
            return true;
        }
        bool Delete(const K& key)
        {
            size_t index = _HashFunc(key);
            size_t pos = index;
            while (_ht[index]._status != EMPTY)
            {
                if (_ht[index]._key == key)
                {
                    _ht[index]._status = DELETE;
                    _size--;
                    return true;
                }
                ++index;
                if (pos == index)
                {
                    break;
                }

            }
            return false;
        }
        Node* Find(const K& key)
        {
            size_t index = _HashFunc(key);
            size_t pos = index;
            while (_ht[index]._status != EMPTY)
            {
                if (_ht[index]._key == key&&_ht[index]._status == EXIST)
                {
                    return &_ht[index];
                }
                index++;
                if (pos == index)
                {
                    break;
                }
            }
            return NULL;
        }
    protected:
        void _CheckCapacity()
        {
            //判断负载因子是否小于0.8
            if (_ht.size() == 0 || _size * 10 / _ht.size() >= 8)
            {
                size_t newindex = 0;

                size_t newsize = GetNewSize();
                HashTable tmp;
                tmp._ht.resize(newsize);
                for (size_t i = 0; i < _size; i++)
                {
                    if (_ht[i]._status == EXIST)
                    {
                        tmp.Insert(_ht[i]._key, _ht[i]._value);
                    }
                }
                this->Swap(tmp);
            }
            else
                return;
        }
        void Swap(Hashtable & t)
        {
            _ht.swap(t._ht);
            swap(_size, t._size);
        }
        size_t _HashFunc(const K& key)
        {
            //利用仿函数进行处理其他像string和结构体带来的问题。
            HashFunc hf;
            return hf(key) % _ht.size();
        }
        size_t GetNewSize()
        {
            const int _PrimeSize = 28;
    //采用素数表
            static const unsigned long _PrimeList[_PrimeSize] =
            {
                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
            };
            for (size_t i = 0; i < _PrimeSize; ++i)
            {
                if (_ht.size() < _PrimeList[i])
                {
                    return _PrimeList[i];
                }
            }
            return 0;
        }
    protected:
        std::vector<Node> _ht;
        size_t _size;

    };


}

4.拉链法


为了让哈希表更加高效,人们又想出了拉链法。也叫做哈希桶。

简单的说拉链法就是,当出现冲突的时候,我们在这个冲突的位置下面挂一个链表,这样,就可以轻松加愉快的解决冲突问题。

这里写图片描述

拉链法就可以很好的解决了开放地址法出现的一些问题。

这里写图片描述

当然,为了提高哈希表的效率,在拉链法中我们依然要考虑负载因子,这样我们就能更加高效的查找,这里的负载因子,最好是每一个哈希下只有一个节点。

当哈希表拉链法中拉链的节点增多的时候,这个时候查找的效率就很低,为了解决效率问题,这个时候我们可以提供一种思路,把链表改成一棵红黑树就好了,就从本来的O(N)到了O(logN)。这样就更加高效了。

代码实现:

namespace link
{
    template<typename K,typename V>
    struct KVNode
    {
        KVNode(const K& key=K(),const V& value=V())
        : _key(key)
        , _value(value)
        , _next(NULL)
        {}

        K _key;
        V _value;
        KVNode<K, V> *_next;
    };

    template<typename K>
    struct __Hashfunc
    {
        size_t operator() (const K &key)
        {
            return key;
        }
    };
    template<>
    struct __Hashfunc<string>
    {
        static size_t BKDRHash(const char*str)
        {
            unsigned int seed = 131;// 31 131 1313 13131 131313
            unsigned int hash = 0;
            while (*str)
            {
                hash = hash*seed + (*str++);
            }
            return(hash & 0x7FFFFFFF);
        }
        size_t operator ()(const string &key)
        {
            return BKDRHash(key.c_str());
        }

    };

    template<typename K,typename V,typename HashFunc=__Hashfunc<K> >
    class HashTable
    {
        typedef KVNode<K, V> Node;
        typedef HashTable<K, V, HashFunc> Hashtable;


    public:
        HashTable()
            :_size(0)
        {
            _ht.resize(GetNewSize());
        }
        ~HashTable()
        {   
            Node* cur = NULL;
            Node* del = NULL;
            for (size_t i = 0; i < _ht.size(); i++)
            {
                cur = _ht[i]; 
                if (_ht[i] == NULL)
                    continue;
                while (cur)
                {
                    del = cur;
                    cur = cur->_next;
                    delete del;
                    del = NULL;
                }
            }

        }
        HashTable(const HashTable& ht)
        {
            _Copy();
        }
        bool Insert(const K& key,const V& value)
        {
            CheckCapacity();
            size_t index = Funcpos(key,_ht.size());

            Node* newnode = new Node(key,value);
            if (_ht[index])
            {
                Node* cur = _ht[index];
                while (cur)
                {
                    if (cur->_key == key)
                        return false;
                    cur = cur->_next;
                }
            }
            _size++;
            newnode->_next = _ht[index];
            _ht[index] = newnode;

            return true;
        }
        bool Delete(const K& key)
        {
            size_t index = Funcpos(key,_ht.size());
            Node* cur = _ht[index];
            Node* prev = NULL;
            Node* del = NULL;
            while (cur)
            {
                if (cur->_key == key)
                {
                    if (prev == NULL)
                    {
                        del = cur;
                        delete cur;
                        _ht[index] = NULL;
                    }
                    else
                    {
                        prev->_next = cur->_next;
                        delete cur;
                    }
                    _size--;
                    return true;
                }
                prev = cur;
                cur = cur->_next;
            }

            return false;
        }
        Node* Find(const K& key)
        {
            size_t index = Funcpos(key,_ht.size());
            Node *cur = _ht[index];
            if (_ht.empty())
            {
                return NULL;
            }

            while (cur)
            {
                if (cur->_key == key)
                {
                    return cur;
                }
                cur = cur->_next;
            }
            return NULL;
        }
    protected:
        void CheckCapacity()
        {
            if (_ht.size() == 0 || _size == _ht.size())
            {
                size_t newsize = GetNewSize();
                Hashtable tmp;
                tmp._ht.resize(newsize);
                Node* cur = NULL;
                Node* NewInsert = NULL;
                for (size_t i = 0; i < _ht.size(); i++)
                {
                    //采用移动法进行交换
                    //就是把原来的vector上面的节点进行移动到新的vector上面。
                    cur = _ht[i];
                    if (cur == NULL)
                        continue;
                    while (cur)
                    {
                        size_t newpos = Funcpos(cur->_key, tmp._ht.size());
                        NewInsert = cur;
                        cur = cur->_next;
                        NewInsert->_next = tmp._ht[newpos];
                        tmp._ht[newpos] = NewInsert;

                    }

                    _ht[i] = NULL;

                }
                NewSwap(tmp);
            }
        }
        void Getnew()
        {
            Hash

        }
        void NewSwap( Hashtable &h)
        {
            _ht.swap(h._ht);
            //std::swap(_size, h._size);
        }

        size_t GetNewSize()
        {
            const int _PrimeSize = 28;

            static const unsigned long _PrimeList[_PrimeSize] =
            {
                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
            };
            for (size_t i = 0; i < _PrimeSize; ++i)
            {
                if (_ht.size() < _PrimeList[i])
                {
                    return _PrimeList[i];
                }
            }
            return 0;
        }
        size_t Funcpos(const K& key,const size_t &size)
        {
            HashFunc h;
            return h(key) % size;
        }
    protected:
        std::vector<Node*> _ht;
        size_t _size;

    };



}

5.哈希表的总结


对于哈希表,查找起来是相当方便,大致的效率为O(1),但是哈希表最大的问题就是太浪费空间。因为负载因子的原因,部分空间都是不用的,所以哈希也就是一种牺牲空间来提高时间的方式。另外哈希表不能够进行排序,作为一个使用key来决定储存位置的,它的key是关键,key当然不能重复。

代码已上传github:https://github.com/wsy081414

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值