哈希表

来源:互联网 发布:java工程师培训价格 编辑:程序博客网 时间:2024/06/16 05:23

数据结构我们了解好几个了,其中写的最多的就是顺序表和链表,在这个阶段,还模拟实现了库中的vector和list,随后我们又了解了队列和栈这些都是线性的数据结构,所以想要查找一个元素的话,这些的时间复杂度都是O(N)。

除了线性结构还有树形结构,在这里我们从二叉树开始,先后了解了二叉树的实现以及线索化,不过我们发现一个问题,那就是如果二叉树写成单支的情况,那就是线性结构了,所以我们又了解了平衡二叉树、红黑树。我们实现这些代码的时候,最常用的就是递归,但是如果树的深度很大的时候,递归的深度也会很大,所以要开辟很多的栈空间,所以,我们又实现了非递归的形式,最后我们还用了B-树。

树形结构的时间复杂度为log以2为底N

不过,要是对这个时间复杂度还是不满意的话,那么还能继续减小吗?

答案是可以的,用哈希表就可以,将时间复杂度减到O(1)

今天我们就来看一下,哈希表是什么以及怎么实现哈希表

哈希表又称为散列表,是用Key直接访问内存位置的数据结构,它的时间复杂度低的原理是使用空间换取时间。通过开辟多个内存空间来实现高效率的查找。

实现哈希表的关键是哈希函数的选择,选择哈希函数有几个点需要注意:

1、哈希函数的定义域必须包含所有的关键码,如果哈希表允许有m个地址,那么哈希函数的值域必须在0~(m-1)之间

2、哈希函数计算出来的地址最好均匀分布在空间中

3.哈希函数应该比较简单,否则,只计算哈希函数就需要大量的时间,不划算

哈希函数有很多,我们常用的也就两个,一个是直接定址法,一个是除留余数法:

1、直接定址法

取关键字的某个线性函数为散列地址,Hash(Key)=Key 或 Hash(Key)= A*Key+B。

利用数组下标可以很好的将对应的数据存入哈希表对应的位置。例如:在一个字符串中找出第一次只出现一次的字符,字符串为abcdabcdefg,需要找到e,利用下标统计可以很好地解决这个问题,对于这个问题,你必须开辟对应的256个空间。

不过,如果需要查找的数中出现了一个特别大的数(1000000),你必须要开辟1000000个空间,会造成大量空间的浪费。所以,这个方法只适合查找的数据较小,而且连续的情况。

2、除留余数法:
取关键值被某个不大于散列表长m的数p除后的所得的余数为散列地。Hash(Key)= Key % P。

由于“直接定址法”的缺陷,于是下面引入“除留余数法”,该方法提高的空间的利用率,但不同的Key值经过哈希函数Hash(Key)处理以后可能产生相同的值哈希地址,我们称这种情况为哈希冲突。

任意的散列函数都不能避免产生冲突。

那么如何处理哈希冲突呢?

1、线性探测

这里写图片描述

2、二次探测

这里写图片描述

#include<iostream>using namespace std;#include<vector>enum State{    EMPTY, EXIST, DELETE};template<class K, class V>struct KVNode{    pair<K, V>  _kv;    State _s;//保存某个位置的状态信息    KVNode()        : _s(EMPTY)    {}};template<typename K>struct _GetK{    size_t operator()(const K& key)    {        return key;    }};//获取字符串keystruct _GetStrK{    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& str)    {        return BKDRHash(str.c_str());    }};template<class K, class V, typename GetK=_GetK<K>>class HashTable{public:    HashTable(size_t size = 12)        : _size(0)    {        _table.resize(GetPrime(size));    }    //// 线性探测    //bool Insert(const K& key, const V& value)    //{    //  _CheckTable();//判断哈希表是不是满的    //  int index = _HashFunc(key);    //  while (EXIST == _table[index]._s)    //  {    //      if (key != _table[index]._kv.first)//要插入的值,在原来的表中已存在    //      {    //          return false;    //      }    //      ++index;    //      if (index == _table.size())//遍历到末尾,需要将索引置开头    //      {    //          index = 0;    //      }    //  }    //  //找到合适的位置,插入    //  _table[index]._kv.first = key;    //  _table[index]._kv.second = value;    //  _table[index]._s = EXIST;    //  ++_size;    //return true;    //}    //二次探测    bool Insert(const K& key, const V& value)    {        _CheckTable();//判断哈希表是不是满的        int i = 0;        int index = _HashFunc(key);        //如果存在哈希冲突,即哈希地址上不为空,但是元素不在那个位置        while (_table[index]._s == EXIST)        {            if (_table[index]._kv.first == key)                return false;            //二次探测            index = HashFunc2(index, ++i);            if (index > _table.size())                index -= _table.size();        }        //找到合适的位置,插入        _table[index]._kv.first = key;        _table[index]._kv.second = value;        _table[index]._s = EXIST;        ++_size;        return true;    }    pair<KVNode<K, V>*, bool> Find(const K& key,const V& value)    {        size_t index = _HashFunc(key);        while (EMPTY != _table[index]._s)        {            if (key == _table[index]._kv.first)            {                //如果那个元素的状态是存在,而不是已删除的,返回true                if (EXIST == _table[index]._s)                    return make_pair(&_table[index],true);                else                    return make_pair((KVNode<K, V>* )0, false);            }            else                index++;            //如果遍历到末尾,那么重头开始            if (index == _table.size())            {                index = 0;            }        }        return make_pair((KVNode<K, V>*)0, false);    }    bool Remove(const K& key)    {        //如果哈希表为空,返回false        if (_size == 0)        {            return false;        }        //哈希函数找到带删除的元素的地址        size_t index = _HashFunc(key);        int begin = index;//标记其实位置        while (_table[index]._s != EMPTY)        {            //如果这个元素找到了,并且状态是存在的,那么删除            if (key == _table[index]._kv.first && _table[index]._s==EXIST)            {                _table[index]._s = DELETE;                --_size;                return true;            }            index++;            //如果到末尾            if (index == _table.size())            {                index = 0;            }            //走了一圈还没有找到,返回false            if (begin == index)                return false;        }    }private:    // 线性探测处理函数    size_t _HashFunc(const K& key)    {        GetK getK;        return getK(key)% (_table.size());    }    // 二次探测处理函数    size_t HashFunc2(size_t hashAddr, size_t i)    {        return hashAddr + (2 * i - 1);//通过二次探测的算法优化后的结果    }    void _CheckTable()    {        if (_size * 10 / (_table.size()) > 7)        {            int newSize = GetPrime(_table.size());//获取一个新的size            HashTable<K, V,GetK> hash;            hash._table.resize(newSize);//创建一张新的哈希表            //将原来的表中元素按照新的哈希函数计算的地址,放入新表中            for (size_t i = 0; i < _size; ++i)            {                if (_table[i]._s == EXIST)                {                    hash.Insert(_table[i]._kv.first, _table[i]._kv.second);                }                this->Swap(hash);            }        }        else            return;    }    int GetPrime(int data)    {        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 (int i = 0; i < _primesize; i++)        {            if (_PrimeList[i]>data)//找出比data大的素数,                return _PrimeList[i];        }        return _PrimeList[_primesize - 1];//如果没有找到,那么返回最大的那个素数    }    void Swap(HashTable<K, V,GetK>& ht)    {        //_table.swap(ht._table);        _table.swap(ht._table);        swap(_size, ht._size);    }private:    std::vector<KVNode<K, V>> _table;//hashtable have _kv and status    size_t _size;  // 有效元素的个数};void TestHashTable(){    HashTable<int, int> ht;    int array1[] = { 89, 18, 8, 58, 2, 43, 74, 9, 20 };    for (int i = 0; i < sizeof(array1) / sizeof(array1[0]); ++i)    {        ht.Insert(array1[i], 0);    }    ht.Remove(8);    ht.Remove(20);    HashTable<string, int, _GetStrK> ht2;    char* array2[] = { "hello", "world", "what", "find", "sort", "sort" };    for (int i = 0; i < sizeof(array2) / sizeof(array2[0]); ++i)    {        //找带插入的元素,看是否在哈希表中        KVNode<string, int>* node = (ht2.Find(array2[i], 0)).first;        if (node)//如果结已存在,那么将            node->_kv.second++;        else            ht2.Insert(array2[i], 0);    }}int main(){    TestHashTable();    return 0;}