HashTable哈希表/散列表(线性探测和二次探测)

HashTable的简单介绍

HashTable是根据关键字直接访问在内存存储的数据结构。
HashTable叫哈希表或者散列表。
它通过一个关键值的函数将所需的数据直接映射到表中的位置来访问数据,这个映射函数叫散列函数(哈希函数),存放记录的数组叫散列表(哈希表)。

比如:
给定一字符串“abckhdgubhkacdggk”找出第一次只出现一次的字符。
对于这个问题,用指针解决,用两个指针,一个指针cur指向当前字符,另一个指针next向后遍历,如果找到cur与next相同,cur指向下一个字符,next再进行遍历,直到next遍历完成所有字符,都与cur不同,则此时的cur指向的字符就是第一个只出现一次的字符。时间复杂度O(n^2)。

这种方法可以解决但是时间复杂度比较高,优化一下,在查找时红黑树和堆排序的查找时间复杂度会低一点,建红黑树然后再根据关键字进行查找。时间复杂度O(nlogn)。

能不能再优化一下?
那就是运用到哈希表,所有字符总共256个,我们可以利用字符对应的ASCII值为下标建立数组。遍历一遍字符串,将每个字符对应的下标ASCII处加1,遍历完成后就从头往后进行查找,查找时直接能找到对应的位置。所以这个时间复杂度就为O(n)。

这就是哈希表的直接定址法。

当对一组10以内的数进行排序,也可以用哈希表。比如:1、3、5、2、5、3、5、8,建立一个数组a[10],将对应的数存放在对应的下标处,出现一次加一下,打印时如果下标对应的是0就不打印,对应1就打印1次,对应的下标里存几就打印几。如下:
这里写图片描述
但是并不是所有的数都适合这种排序,如:1000、1002、1000、1005、1000、1008。最大数是1008,开1008大小的数组前1000个就浪费了,我们只需开(1008 - 1000)的大小就好,对应的下标就是(1008-1000)/1000。
这就是哈希表的除留余数法。

除此外,构造哈希表的方法还有:平方取中法、折叠法、随机数法、数学分析法


哈希冲突(哈希碰撞)

不同的关键字通过相同的哈希函数处理后可能产生相同的值哈希地址。我们称这种情况为哈希冲突或哈希碰撞。(任意的散列函数都不能避免产生冲突)
散列表的载荷因子定义:α = 填入表中的元素 / 表的长度

解决哈希冲突的方法:
1. 闭散列方法-开放定址法
2. 开链法/拉链法


线性探测法和二次探测法

闭散列方法-开方地址法有线性探测法和二次探测法

线性探测法:
这里写图片描述
Hash(key) = key % 10(表长);
89放入9的位置;18放入8的位置;49与89冲突,往后加到尾了就再回到头,0的位置为空放入;58与18冲突,往后加有89,再加有49,再加就放入1的位置;9与89冲突一直加到2的位置放入。
Hash(key) + 0,Hash(key)+1,……

线性探测的增删查:

#pragma once

#include 

enum State
{
    EXIST,
    EMPTY,
    DELETE,
};

template <class K,class V>
struct HashNode
{
    pair _kv;
    State _s;//状态
};

template <class K,class V,class HushFunc>
class HashTable
{
    typedef HashNode Node;
public:
    HashTable()
        :_size(0)
    {}

    HashTable(size_t n)
        :_size(0)
    {
        _tables.resize(n);
    }

    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 == EXIST)
                    return &_tables[index];
                else
                    return NULL;
            }
            index += 1;
            if (index == _tables.size())
            {
                index = 0;
            }
        }
        return NULL;
    }

    bool Insert(const pair& kv)//插入
    {
        _Check();//增容

        size_t index = _HashFunc(kv.first);
        while (_tables[index]._s == EXIST)
        {
            if (_tables[index]._kv.first == kv.first)//有key,直接返回
                return false;
            index += 1;
            if (index == _tables.size())//到尾了,就从开头找
                index = 0;
        }
        _tables[index]._kv = kv;
        _tables[index]._s = EXIST;
        ++_size;
        return true;
    }

    bool Remove(const K& key)//删除
    {
        Node* cur = Find(key);//找到了key
        if (cur)
        {
            cur->_s = DELETE;
            --_size;
            return true;
        }
        else
            return false;
    }

protected:
    void _Check()//增容
    {
        if (_tables.size() == 0)
            _tables.resize(10);
        if ((_size * 10) / (_tables.size()) == 7)
        {
            size_t newSize = _tables.size() * 2;
            HashTable newHashTable(newSize);
            for (size_t i = 0; i < _tables.size(); ++i)
            {
                if ((_tables[i]._s) == EXIST)
                {
                    newHashTable.Insert(_tables[i]._kv);
                }
            }
            Swap(newHashTable);
        }
    }

    void Swap(HashTable &ht)//交换
    {
        swap(_size, ht._size);
        _tables.swap(ht._tables);
    }

    size_t _HashFunc(const K& key)//
    {
        return key % _tables.size();
    }
protected:
    vector _tables;
    size_t _size;//哈希表实际存放的个数
};

void TestHashTable()
{
    int a[] = { 89, 18, 49, 58, 9 };
    HashTable<int, int,int> ht1;
    for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); ++i)
    {
        ht1.Insert(make_pair(a[i], i));
    }
    ht1.Insert(make_pair(10, 1));
    ht1.Insert(make_pair(11, 1));
    ht1.Insert(make_pair(12, 1));
    ht1.Insert(make_pair(13, 1));
    ht1.Insert(make_pair(14, 1));

    ht1.Find(10);
    ht1.Remove(10);
    ht1.Find(10);
    ht1.Remove(10);
}

二次探测
这里写图片描述
Hash(key) = key % 10(表长);
89放入9的位置;18放入8的位置;49与89冲突,加1^2,到0的位置;58与18冲突,加1^2,到了9的位置,有数继续加2^2到2的位置;9与89冲突,加1^2,在0的位置,继续加2^2,到3的位置放入。
Hash(key)+ i^2(i= 1、2、3……不算第一放入的数)

线性探测和二次探测必须考虑载荷因子,超过0.7-0.8就增容,增大效率,(以空间换时间)
其中删除是惰性删除,也就是只标记删除记号,实际操作则待表格重新整理时再进行,因为HashTable中的每一个元素不仅代表自己,也关系到其他元素。

你可能感兴趣的:(c++,数据结构,hashtable,散列函数,数据结构,哈希冲突)