哈希表

一:HashTable是散列表/哈希表,是根据关键字(key)而直接访问在内存存储位置的数据结构
通过关键值得函数将所需要的数据映射到表中的位置来访问数据,这个映射函数叫做散列函数,存放记录的数组叫做散列表;
构建哈希表的几种方法:
1:直接定址法–去关键字的某个线性函数为散列地址,hash(key)=key;或者是hash(key) =key*A+B;
2: 除留余数法–取关键值被某个不大于散列表表长m,除后所得的余数为散列地址hash(key) =key%m
3:平方取中;
4:折叠法;
5 : 随机数法;
6:数学分析法;
直接定址法:(高效,但是限定范围)
缺点是实际存储的关键字的集合可能比所给的范围小很多,就会造成内存的浪费;
除留余数法:
不同的key值可能经过哈希函数hash(key)处理后放到表中相同的位置,我们称这种情况为哈希冲突。任何的散列函数都无法避免哈希冲突;
二:解决哈希冲突的方法:闭散列方法–(开放定址法)
1:线性探测:
2:二次探测:
这里写图片描述
缺陷:当继续探测冲突的元素越来越多,查找的效率会越来越慢,
此时需要更大的空间来存放更多的冲突的数,这就需要二次探测;
这里写图片描述
这里面的开更大的空间不是简单的二倍的关系,如果直接开比原来大两倍的空间,还是会出现和线性探测相同的问题,
因此需要素数表做除数,这样就会减少冲突数;
3:素数表(使用素数做除数可以减少哈希冲突)
//使用素数表对齐做哈希表的容量,降低哈希冲突

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
};

4:对于开放定址法影响冲突的是负载因子:
定义:负载因子 a = 填入表中的个数/表的长度;
a是散列表装满程度的标志,a与“填入表中的元素成正比”,所以a越大,表明填入表中的元素越多,产生冲突的元素越多。
对于开放定址法负载因子特别重要,因该严格控制在0.7~0.8左右;
三:开放定址法的实现:

#include<iostream>
#include<vector>
#include<string>
using namespace std;
namespace first
{
    enum State
    {
        EMPTY,
        EXIT,
        DELETE,
    };

    template<class K, class V>
    struct  HashNode
    {
        pair<K, V>_kv;
        State _s;//表示存的状态
    };
    //仿函数
    template<class 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 &s)
        {
            return BKDRHash(s.c_str());
        }
    };
    template<class K, class V, class HashFunc = __HashFunc<K>>
    class HashTable
    {
        typedef HashNode<K, V> Node;
    public:
        HashTable()
            :_size(0)
        {}


        HashTable(size_t n)
            :_size(0)
        {
            _tables.resize(n);
        }
        //插入
        pair<Node*, bool> Insert(const pair<K, V> &kv)
        {
            _Check();
            size_t index = _HashFunc(kv.first);//除留余数法算位置

            while (_tables[index]._s == EXIT)//存在进行线性探测
            {
                if (_tables[index]._kv.first == kv.first)
                {
                    return make_pair(&_tables[index], false);//表明该位置存在
                }
                index += 1;

                if (index == _tables.size())
                {
                    index = 0;
                }
            }
            //插入成功
            _tables[index]._kv = kv;
            _tables[index]._s = EXIT;//将状态改为存在
            ++_size;

            return make_pair(&_tables[index], true);//插入成功返回
        }

        //查找
        Node*Find(const K&key)
        {
            size_t index = _HashFunc(key);
            while (_tables[index]._s != EMPTY)
            {
                if (_tables[index]._kv.first == key)
                {
                    if (_tables[index]._s == EXIT)
                    {
                        return &_tables[index];
                    }
                    else
                        return NULL;
                }
                index += 1;
                if (_index == _tables.size())
                {
                    index = 0;
                }
            }
            return NULL;
        }
        //删除
        bool Remove(const K&key)
        {
            Node*node = Find(key);
            if (node)
            {
                node->_s = DELETE;
                --_size;
                return true;
            }
            else
                return false;
        }

        void Swap(HashTable<K, V, HashFunc>&hf)
        {
            swap(_size, hf._size);
            _tables.swap(hf._tables);
        }
        //实现operator[]实现字典;
        V&operator[](const K&key)
        {
            pair<Node*, bool>ret = Insert(make_pair(key, V()));
            return ((ret.first)->_kv).second;
        }
    protected:
        //除留余数法算位置
        size_t _HashFunc(const K&key)
        {
            HashFunc hf;
            return hf(key) % _tables.size();
        }
        //素数表
        size_t GetNextPeime(size_t num)
        {
            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 (_PrimeList[i] > num)
                {
                    return _PrimeList[i];
                }
            }

            return _PrimeList[_PrimeSize - 1];
        }
        //检查是否需要增容
        void _Check()
        {
            if (_tables.size() == 0)
            {
                _tables.resize(GetNextPeime(0));
            }
            //考虑负载因子
            if (_size * 10 / _tables.size() == 7)
            {
                size_t NewSize = GetNextPeime(_tables.size());//新表的大小
                HashTable<K, V, HashFunc>NewHf(NewSize);
                //将旧表的元素插入到新表中
                for (size_t i = 0; i < _tables.size(); ++i)
                {
                    if (_tables[i]._s == EXIT)
                    {
                        NewHf.Insert(_tables[i]._kv);
                    }
                }
                Swap(NewHf);
            }
        }
    protected:
        vector<Node>_tables;
        size_t _size;//实际表中元素的个数

    };
    void HashTables()
    {
        int a[] = { 9, 18, 60, 29, 58 };

        HashTable<int, int> ht;
        for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
        {
            ht.Insert(make_pair(a[i], 1));
        }
        ht.Insert(make_pair(5, 1));
        ht.Insert(make_pair(6, 1));
        ht.Insert(make_pair(8, 1));
        ht.Insert(make_pair(30, 1));
        ht.Insert(make_pair(42, 1));

        HashTable<string, string> dict;
        dict["insert"] = "插入";
        dict["erase"] = "删除";
        dict["find"] = "查找";
    }
}

四:开链法的实现:
我们知道闭散列方法当负载因子大于0.8时,哈希表的查找效率就会变得很低效,如何解决?
哈希桶就是盛放不同key链表的容器(即是哈希表),在这里我们可以把每个key的位置看作是一个桶,桶里放了一个链表(vector可以实现动态增容)
这里写图片描述

开链法(哈希桶)
namespace second
{
    //哈希桶
    template<class K,class V>
    struct HashNode
    {
        pair<K, V> _kv;
        HashNode<K, V>*_next;

        HashNode(const pair<K, V>&kv)
            :_kv(kv)
            ,_next(NULL)
        {}
    };
     template<class K,class V,class HashFunc>
     class HashTable;
     template<class K, class V, class HashFunc = first::__HashFunc<K>>
    class HashTable
    {
        typedef HashNode<K, V>Node;
    public:
        HashTable()
            :_size(0)
        {}

        void Resize(size_t n)
        {
            _Check(n);
        }
        //构造函数
        HashTable(const HashTable<K, V, HashFunc>&ht)
            :_size(0)
        {
            _tables.resize(ht._tables.size());//开和原来一样大的空间
            for (size_t i = 0; i <ht._tables.size(); ++i)//拷贝节点的数据
            {
                Node*cur = ht._tables[i];
                while (cur)
                {
                    Insert(cur->_kv);
                    cur = cur->_next;
                }
            }
        }
        //拷贝构造
        HashTable<K, V, HashFunc>& operator =(HashTable<K, V, HashFunc> ht)
        {
            swap(_size, ht._size);
            _tables.swap(ht._tables);
            return *this;
        }
        ~HashTable()
        {
            _Clear();
        }

        //插入
        pair<Node*, bool> Insert(const pair<K, V>&kv)
        {
            _Check();
            size_t index = _HashFunc(kv.first,_tables.size());
            Node*cur = _tables[index];

            while (cur)
            {
                if (cur->_kv.first == kv.first)
                    return make_pair(cur, false);//插入值相等
                cur = cur->_next;
            }
            //表示表为空,插入新节点
            Node*tmp = new Node(kv);
            tmp->_next = _tables[index];
            _tables[index] = tmp;

            ++_size;
        }
        //查找
        Node*Find(const K&key)
        {
            size_t index = _HashFunc(key, _tables.size());//位置
            Node*cur = _tables[index];
            while (cur)
            {
                if (cur->_kv.first == key)
                {
                    return cur;
                }

                cur = cur->_next;
            }
            return NULL;//没找到
        }
        //删除
        bool Remove(const K&key)
        {
            size_t index = _HashFunc(key, _tables.size());//位置
            Node*prev = NULL;
            Node*cur = _tables[index];
            while (cur)
            {
                if (cur->_kv.first == key)//扎到删除的元素
                {
                    if (prev == NULL)
                    {
                        _tables[index] = cur->_next;//保存下一个节点
                    }
                    else
                        prev->_next = cur->_next;

                    delete cur;
                    --_size;
                }
                prev = cur;
                cur = cur->_next;
            }
            return false;//删除的节点不存在
        }

    protected:
        size_t _HashFunc(const K&key, size_t size)
        {
                 //HashFunc hf;
                 return HashFunc()(key) % size;
        }

        void _Check(size_t  n =0)
        {
            if (_size == _tables.size() || n > _tables.size())
            {
                if (_tables.size() > n)
                {
                    n = _tables.size();
                }

                vector<Node*> NewTables;
                NewTables.resize(GetNextPeime(n));//创建新表

                for (size_t i =0; i < _tables.size(); ++i)
                {
                    Node*cur = _tables[i];//摘节点赋给新表上
                    while (cur)
                    {
                        Node*next = cur->_next;
                        size_t index = _HashFunc(cur->_kv.first, NewTables.size());
                        cur->_next = NewTables[index];
                        NewTables[index] = cur;

                        cur = next;//遍历下一个节点
                    }
                    _tables[i] = NULL;//将节点与原链表分离
                }
                _tables.swap(NewTables);
            }
        }

        size_t GetNextPeime(size_t num)
        {
            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 (_PrimeList[i] > num)
                {
                    return _PrimeList[i];
                }
            }

            return _PrimeList[_PrimeSize - 1];
        }

        void _Clear()
        {
            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] = NULL;
            }
            _size = 0;
        }
    protected:
        vector<Node*>_tables;
        size_t _size;//表中实际元素的个数
    };

    void TestHashTable()
    {
        int a1[] = { 51, 105, 52, 3, 55, 2, 106, 53, 0 };
        HashTable<int, int> ht1;


        for (size_t i = 0; i < sizeof(a1) / sizeof(a1[0]); ++i)
        {
            ht1.Insert(make_pair(a1[i], i));
        }
    }
}

这里面我们使用命名空间namespace增加代码的复用,开链法的特点是哈希表下面挂的是节点,而每个节点的头结点的地址都存在这个表中方便遍历,以及实现插入和删除节点的操作。
区别:
1:闭散列采用的是除留余数法算位置,然后在位置上实行插入、查找、删除,而且要判断每个位置的状态;开链法是对vector上的节点操作。
2:增容,闭散列采用每次开原来的两倍大小的空间,然后通过处留余数法重新计算位置,再将原表的元素插入到新表,释放旧表(影响因素:负载因子不差过0.8)
开链法,开空间,然后摘节点把原表的节点,给新表相同的位置。把原表和节点的链解开,释放原表。负载因子可以超过0.8(影响因素:挂的节点很多时,查找效率变慢)
五:小结:
1:直接定址法:用于确定范围不是很大(字符串不行)时间复杂度是O(1);
2:处留余数法:hash(key) =key%len(表长)
不同的值通过哈希函数映射到相同的位置 主要用于处理冲突(闭散列方法,开放定址法)
3:线性探测:找位置(可能互相冲突影响效率)
4:二次探测:解决线性探测的冲突(负载因子不超过0.8)
5:开链法:解决二次探测的冲突以及空间利用率不高,(缺点哈希表下挂的节点不能太多否则影响查找效率,解决方法:节点可以挂红黑树这样查找效率会得到很大的优化)

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值