std::hash和unordered_map用法,C++自定义哈希表

目录

    • std::hash
    • unordered_map
    • C++自定义哈希表

std::hash

std::hash是实现了仿函数的类模板,根据传入不同数据类型T,获得其哈希值。
返回值类型:size_t。
对于C/C++内置数据类型,已经实现了其哈希函数,自定义数据类型需要定义其哈希值的求值方式。

std::hash几个原则

不能拋出异常
对于相等的键必须产生相等的哈希值
对于不相等的键产生碰撞的可能性必须最小接近 size_t 最大值的倒数

1、内置类型可直接生成hash值。

    std::hash<int> inthash;
    size_t  hash1 =  inthash(1);
    size_t  hash2 =  inthash(-1);
    printf("hash1=%lu\n",hash1);
    printf("hash2=%lu\n",hash2);

    std::hash<std::string> stringhash;
    size_t  hash3 =  stringhash("helloworld");
    size_t  hash4 =  stringhash("nihao");
    printf("hash3=%lu\n",hash3);
    printf("hash4=%lu\n",hash4);

打印

hash1=1
hash2=18446744073709551615
hash3=10313766814229003468
hash4=5306584351276782105

2、自定义数据类型生成std::hash哈希值。

#include 
class Student{
public:
    int age;
    std::string name;

    //重载==,可做unordered_set和unordered_map的key,而key要保证唯一
    bool operator==(const Student& stu) const{return (age == stu.age) && (name == stu.name); }
};

//自定义数据类型Student生成hash值的方式
namespace std
{
    template<>
    struct hash<Student>: public __hash_base<size_t, Student>
    {
        size_t operator()(const Student& stu) const noexcept
        {
            //可以自定义哈希值的生成方式
            return (std::hash<int>()(stu.age)) ^ (std::hash<std::string>()(stu.name) << 1);
        }
    };
}

    std::hash<Student> Studenthash;
    Student mStudent;
    mStudent.age = 18;
    size_t hash5 = Studenthash(mStudent);
    printf("hash5=%lu\n",hash5);

    std::unordered_map<Student, char> umStudent;

打印

hash5=12285018377944847566

unordered_map

C++中的哈希表是通过unordered_map实现的,它是一种关联容器,可以将键值对存储在其中。它的特点是快速查找,插入和删除,时间复杂度为O(1)。它使用哈希函数将键映射到桶中,每个桶中存储一个链表,用于解决哈希冲突。unordered_map还提供了许多操作,例如迭代器遍历,查找元素,删除元素等.

1、关联性:通过key去检索value,而不是通过绝对地址(和顺序容器不同)
2、无序性:使用hash表存储,内部无序
3、Map : 每个值对应一个键值
4、键唯一性:不存在两个元素的键一样
5、动态内存管理:使用内存管理模型来动态管理所需要的内存空间
6、时间复杂度,接近常数。

map

map内部实现了一个红黑树,根据key有序排列,查找时间复杂度lgn。

unordered_map内部实现:

首先分配一大片内存,形成许多桶。是利用hash函数,对key进行映射到不同区域(桶)进行保存。

插入过程

1、得到key。
2、通过hash函数得到hash值。
3、得到桶号(一般都为hash值对桶数求模)。
4、存放key和value在桶内。

查询过程

1、得到key。
2、通过hash函数得到hash值。
3、得到桶号(一般都为hash值对桶数求模)。
4、比较桶的内部元素是否与key相等,若都不相等,则没有找到。
5、取出相等的记录的value。

哈希冲突(开散列)

1、key通过hash函数计算得到的桶号如果相同,则把这些都放入这个桶中,桶中的元素通过一个大链表连接起来,链表的头结点存储在哈希表中。
2、查找时先找到桶,再遍历桶里的链表。

常用方法代码示例,使用上面自定义类Student做key:

 Student mStudent1;
    Student mStudent2;
    mStudent1.age = 18;
    mStudent1.name = "tom";
    mStudent2.age = 19;
    mStudent2.name = "lucy";

    std::unordered_map<Student, int> unomap;
    unomap.emplace(mStudent1,10086);//插入元素
    unomap.emplace(mStudent1,10000);//插入相同的key,value不会更新
    unomap.emplace(mStudent2,9527);//插入元素

    //遍历
    auto ite = unomap.begin();
    while(ite != unomap.end())
    {
        printf("key=%s,value=%d\n",ite->first.name.c_str(),ite->second);
        ite++;
    }

    int value = unomap[mStudent2];//下标访问,如果不存在该key,返回值为0或不可控
    printf("value=%d\n",value);

    int cnt = unomap.count(mStudent1);//使用count查询,不存在时返回0
    printf("cnt=%d\n",cnt);

    auto it = unomap.find(mStudent1);//find查找
    if(it != unomap.end())
    {
        printf("value=%d\n",it->second);
        it->second = 10010;//修改
        unomap.erase(it);//使用迭代器删除
    }

    auto ret = unomap.erase(mStudent1);//使用key删除,如果没有找到返回0
    printf("ret=%lu\n",ret);

    auto it2 = unomap.find(mStudent2);
    if(it2 != unomap.end())
        printf("value=%d\n",it->second);

打印

key=lucy,value=9527
key=tom,value=10086
value=9527
cnt=1
value=10086
ret=0
value=10010

C++自定义哈希表

实现思路:

1.定义节点,包含key和value和下一节点的指针(单链表);
2.定义固定大小的桶,桶内存放根据key计算得到相同hash值的节点;
3.如果存在哈希冲突,相同hash值的节点放入同一个桶内,用单链表存储.

#ifndef HASHTABLEDEF_H
#define HASHTABLEDEF_H

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

template <typename KEY, typename VALUE,  size_t TABLE_SIZE = 1000>
class HashTableDef
{
public:
    typedef struct HashNode//哈希节点
    {
        HashNode() {}
        HashNode(const KEY &key, const VALUE &val) :
            m_key(key), m_value(val), m_pNext(nullptr) { }
        KEY m_key;
        VALUE m_value;
        struct HashNode *m_pNext;//单链表,指向桶内下一个节点
    } Node, *pNode;

    HashTableDef() : m_nCnt(0),m_nSize(TABLE_SIZE)
    {
        for (size_t nIdx = 0; nIdx < TABLE_SIZE; ++nIdx)
        {
            m_pHashTable[nIdx] = nullptr;
        }
    }
    ~HashTableDef(){ReleaseTable();}

    //删除
    bool Delete(const KEY &key, const VALUE &val)
    {
        size_t nHash = 0;
        pNode pCurNode = nullptr;

        nHash = hash<KEY>{}(key) % TABLE_SIZE;// 计算hash值
        pCurNode = m_pHashTable[nHash];// 获取hash表头结点
        if (!pCurNode)
        {
            return(false);
        }
        while (pCurNode)
        {
            // 如果找到了结点
            if (pCurNode->m_key == key && pCurNode->m_value == val)
            {
                pNode pHeadNode = m_pHashTable[nHash];
                // 头结点的内容覆盖掉被删除的结点
                pCurNode->m_key = pHeadNode->m_key;
                pCurNode->m_value = pHeadNode->m_value;
                // 删除头结点
                m_pHashTable[nHash] = pHeadNode->m_pNext;
                delete pHeadNode;
                pHeadNode = nullptr;
                --m_nCnt;

                return(true);
            }
            pCurNode = pCurNode->m_pNext;
        }

        return(false);
    }

    // 插入
    bool Insert(const KEY &key, const VALUE &Value)
    {
        size_t nIdx = 0;
        pNode pNewNode = nullptr;
        //没有作查重处理,因此可以插入相同的键值对,可根据需求修改
        nIdx = hash<KEY>{}(key) % TABLE_SIZE;// 计算出key的hash值作为索引
        pNewNode = new Node(key, Value);// 分配新结点
        if (!pNewNode)
        {
            return(false);
        }

        if (m_pHashTable[nIdx])
        {
            // 如果桶内存在节点,则进行头插法插入
            pNewNode->m_pNext = m_pHashTable[nIdx]->m_pNext;
            m_pHashTable[nIdx]->m_pNext = pNewNode;
        }
        else
        {
            // 如果不存在则直接赋值给头结点
            m_pHashTable[nIdx] = pNewNode;
        }
        ++m_nCnt;

        return(true);
    }

    // 查找
    bool Find(const KEY &key, vector<VALUE> &rVecValue) const
    {
        size_t nHash = 0;
        pNode pCurNode = nullptr;

        nHash = hash<KEY>{}(key) % TABLE_SIZE;// 计算hash值
        pCurNode = m_pHashTable[nHash];// 获取hash表头结点
        if (!pCurNode)
        {
            return(false);
        }
        while (pCurNode)
        {
            if (pCurNode->m_key == key)
            {
                //key可以相同,出参是相同hash值的集合
                rVecValue.push_back(pCurNode->m_value);
            }
            pCurNode = pCurNode->m_pNext;
        }

        return(true);
    }

    // 遍历
    void PrintTable() const
    {
        for (size_t nIdx = 0; nIdx < TABLE_SIZE; ++nIdx)
        {
            if (m_pHashTable[nIdx])
            {
                pNode pCurNode = m_pHashTable[nIdx];
                cout << "key: " << /*nIdx*/pCurNode->m_key << endl;
                while (pCurNode)
                {
                    cout << "value: " << pCurNode->m_value << endl;
                    pCurNode = pCurNode->m_pNext;
                }
                cout << endl;
            }
        }
    }
private:
    // 释放表
    void ReleaseTable()
    {
        for (size_t nIdx = 0; nIdx < TABLE_SIZE; ++nIdx)
        {
            if (m_pHashTable[nIdx])
            {
                pNode pCurNode = m_pHashTable[nIdx];
                pNode pTmpNode = nullptr;
                while (pCurNode)
                {
                    pTmpNode = pCurNode->m_pNext;// 保存下一个结点
                    delete pCurNode;// 删除当前结点
                    pCurNode = nullptr;
                    pCurNode = pTmpNode;// 当前结点指向下一个结点
                }
                m_pHashTable[nIdx] = nullptr;
            }
        }
        m_nCnt = 0;
    }
private:
    Node *m_pHashTable[TABLE_SIZE];//哈希桶数组
    size_t m_nCnt;//存入节点总数量
    size_t m_nSize;//哈希表最大容量,桶的个数
};

#endif // HASHTABLEDEF_H

你可能感兴趣的:(C/C++,哈希算法,散列表,c++)