unordered_map 和 unordered_set 的实现

目录

  • [1 成员变量](#1 成员变量)
  • [2 模板参数](#2 模板参数)
  • [3 迭代器](#3 迭代器)
    • [3.1 operator++](#3.1 operator++)
    • [3.2 迭代器的代码](#3.2 迭代器的代码)
  • [4 unordered_set](#4 unordered_set)
    • [4.1 KeyofV](#4.1 KeyofV)
    • [4.2 begin](#4.2 begin)
    • [4.3 end](#4.3 end)
    • [4.4 Insert](#4.4 Insert)
    • [4.5 Erase](#4.5 Erase)
    • [4.6 Find](#4.6 Find)
    • [4.7 unordered_set 的代码](#4.7 unordered_set 的代码)
  • [5 unordered_map](#5 unordered_map)
    • [5.1 KeyofV](#5.1 KeyofV)
    • [5.2 Insert](#5.2 Insert)
    • [5.3 [] 重载](#5.3 [] 重载)
    • [5.4 unordered_map 的代码](#5.4 unordered_map 的代码)

1 成员变量

unordered_map/unordered_set 是封装哈希表来实现的,所以成员变量只有一个哈希表

unordered_set 中,保存的值是不允许修改的,所以第二个模板参数需要加上 const

unordered_map 中,保存的键值对中,Key 是不允许修改的,所以需要给 Key 的类型加上 const

cpp 复制代码
//unordered_set 
HashTable<K, const K, KeyofV<K>> _t;
//unordered_map
HashTable<K, pair<const K, V>, KeyofV<K, V>> _t;

2 模板参数

哈希表的模板参数需要修改,由于 unordered_map 存储的是 pair 键值对 Key/Value,而 unordered_set 存储的就是Key,由于在进行值的比较时需要使用 Key,所以还需要增加一个 KeyofT 来取出 Key

cpp 复制代码
template<class K, class V, class KeyofV, class Hash = HashFunc<K>>

迭代器的模板参数中,K 表示于比较的值的类型, V 表示存储的值的类型,Ptr 和 Ref 分别是指针类型和引用类型,用来实现普通迭代器和 const 迭代器,KeyofV 用于提取 Key ,Hash 用于将不符合要求的 Key 转化为整形(字符串,负数等)

cpp 复制代码
template<class K, class V, class Ptr, class Ref, class KeyofV, class Hash = HashFunc<K>>

unordered_set 只需要存储一个值即可,所以模板参数只有一个 K,代表用于比较的值的类型和要存储的值的类型

cpp 复制代码
template<class K>

unordered_map 要存储一个键值对 pair<K, V>,pair 中 Key 的类型为 K,Value 的类型为 V,所以模板参数有 K,V 两个

cpp 复制代码
template<class K, class V>

3 迭代器

3.1 operator++

迭代器++ 后要移动到下一个结点,由于当前是哈希桶,所以实现方式有点类似于遍历链表,实现思路是在迭代器进行移动前先判断迭代器移动后会不会将当前的哈希桶遍历完(会不会指向空),若不会则让迭代器继续指向下一个结点即可,若会就要计算出当前哈希桶的编号 hashi,然后不断地增加编号,找到下一个不为空的哈希桶,如果所有的桶都遍历完了,编号 hashi 表示的就是无效桶,此时需要让迭代器指向空,代表到达哈希表尾

cpp 复制代码
Self operator++()
{
    if (_node->_next) //哈希桶中还有节点
    {
        _node = _node->_next;
    }
    else //哈希桶中没结点了
    {
        KeyofV kov;
        Hash hash;
        size_t hashi = hash(kov(_node->_kv)) % _pht->_table.size(); //计算当前桶的编号

        hashi++;
        while (hashi < _pht->_table.size())
        {
            if (_pht->_table[hashi]) //不是空桶,指向这个桶的第一个结点
            {
                _node = _table[hashi];
                break;
            }

            hashi++; //是空桶移动到下一个桶
        }

        if (hashi == _pht->_table.size()) //所有的桶都遍历完了
            _node = nullptr;
    }

    return *this;
}

3.2 迭代器的代码

cpp 复制代码
template<class K, class V, class Ptr, class Ref, class KeyofV, class Hash = HashFunc<K>>
class Iterator
{
    typedef HashNode<V> Node;
    typedef HashTable<K, V, KeyofV, Hash> HashTable;
    typedef Iterator<K, V, Ptr, Ref, KeyofV, Hash> Self;
public:
    Iterator(Node* node, HashTable* pht)
        :_node(node)
        ,_pht(pht)
    {}

    Ref operator*()
    {
        return _node->_kv;
    }

    Ptr operator->()
    {
        return &_node->_kv;
    }

    bool operator!=(const Self& it)
    {
        return it._node != _node;
    }

    Self operator++()
    {
        if (_node->_next) //哈希桶中还有节点
        {
            _node = _node->_next;
        }
        else //哈希桶中没结点了
        {
            KeyofV kov;
            Hash hash;
            size_t hashi = hash(kov(_node->_kv)) % _pht->_table.size(); //计算当前桶的编号

            hashi++;
            while (hashi < _pht->_table.size())
            {
                if (_pht->_table[hashi]) //不是空桶,指向这个桶的第一个结点
                {
                    _node = _pht->_table[hashi];
                    break;
                }

                hashi++; //是空桶移动到下一个桶
            }

            if (hashi == _pht->_table.size()) //所有的桶都遍历完了
                _node = nullptr;
        }

        return *this;
    }
private:
    Node* _node; //指向结点的指针
    HashTable* _pht; //指向哈希表的指针
};

4 unordered_set

4.1 KeyofV

由于 unordered_set 中保存的值就是 Key,仿函数 KeyofV 直接返回该值即可

cpp 复制代码
template<class K>
struct KeyofV
{
    const K& operator()(const K& key)
    {
        return key;
    }
};

4.2 begin

begin 的作用是返回第一个结点的迭代器,所以要遍历一下哈希表,找到第一个非空的哈希桶,返回这个哈希桶中的第一个结点的地址来构造出迭代器

cpp 复制代码
iterator begin()
{
    return _t.begin();
}
//哈希表的begin
iterator begin()
{
    if (_n == 0)
        return { nullptr, this };

    for (int i = 0;i < _table.size();++i)
    {
        if (_table[i])
            return { _table[i], this };
    }

    return { nullptr, this };
}

4.3 end

end 的作用是返回哈希表的表尾迭代器,所以返回空即可

cpp 复制代码
iterator end()
{
    return _t.end();
}
//哈希表的end
iterator end()
{
    return nullptr;
}

4.4 Insert

Insert 的功能是插入结点,返回一个 pair,里面分别是指向结点的迭代器和 bool 类型的变量,true 表示插入成功,false 表示插入失败(已经有值)

cpp 复制代码
pair<iterator, bool> Insert(const K& key)
{
    return _t.Insert(key);
}

//哈希表的Insert
pair<iterator, bool> Insert(const V& kv)
{
    KeyofV kov;
    auto it = Find(kov(kv));

    if (it != end())
        return { it, false };

    Hash hash;

    //负载因子为1进行扩容
    if (_n == _table.size())
    {
        //开新表
        vector<Node*> newTable;
        newTable.resize(__stl_next_prime(_table.size() + 1), nullptr);

        //将原表的数据移动到新表内
        for (int i = 0;i < _table.size();++i)
        {
            Node* cur = _table[i];
            while (cur)
            {
                Node* next = cur->_next;

                size_t hashi = hash(kov(cur->_kv)) % newTable.size();
                cur->_next = newTable[hashi];
                newTable[hashi] = cur;

                cur = next;
            }
            _table[i] = nullptr;
        }
    }

    size_t hashi = hash(kov(kv)) % _table.size();
    Node* newNode = new Node(kv);
    newNode->_next = _table[hashi];
    _table[hashi] = newNode;
    ++_n;

    return { { newNode, this }, true };
}

4.5 Erase

Erase 的功能是删除指定的结点,返回 bool 类型的值,true 代表删除成功,false 表示删除失败

cpp 复制代码
bool Erase(const K& key)
{
    return _t.Erase(key);
}

//哈希表的Erase
bool Erase(const K& key)
{
    KeyofV kov;
    Hash hash;
    size_t hashi = hash(key) % _table.size();
    Node* cur = _table[hashi];
    Node* prev = nullptr;

    while (cur)
    {
        if (kov(cur->_kv) == key && prev == nullptr)
        {
            _table[hashi]->_next = cur->_next;
            --_n;
            return true;
        }
        else if (kov(cur->_kv) == key && prev)
        {
            prev->_next = cur->_next;
            --_n;
            return true;
        }

        prev = cur;
        cur = cur->_next;
    }

    return false;
}

4.6 Find

Find 要做的是根据传入的值来查找结点,找得到则返回该节点的迭代器,找不到返回哈希表的表尾迭代器

cpp 复制代码
iterator Find(const K& key)
{
    return _t.Find(key);
}

//哈希的Find
iterator Find(const K& key)
{
    KeyofV kov;
    Hash hash;
    size_t hashi = hash(key) % _table.size();
    Node* cur = _table[hashi];

    while (cur)
    {
        if (kov(cur->_kv) == key)
            return { cur, this };
        cur = cur->_next;
    }

    return { end(), this };
}

4.7 unordered_set 的代码

cpp 复制代码
template<class K>
class unordered_set
{
    template<class K>
    struct KeyofV
    {
        const K& operator()(const K& key)
        {
            return key;
        }
    };
public:
    typedef typename HashTable<K, const K, KeyofV<K>>::iterator iterator;
    typedef typename HashTable<K, const K, KeyofV<K>>::const_iterator const_iterator;

    iterator begin()
    {
        return _t.begin();
    }

    iterator end()
    {
        return _t.end();
    }

    const_iterator begin() const
    {
        return _t.begin();
    }

    const_iterator end() const
    {
        return _t.end();
    }

    pair<iterator, bool> Insert(const K& key)
    {
        return _t.Insert(key);
    }

    iterator Find(const K& key)
    {
        return _t.Find(key);
    }

    bool Erase(const K& key)
    {
        return _t.Erase(key);
    }
private:
    HashTable<K, const K, KeyofV<K>> _t;
};

5 unordered_map

begin,end,Erase,Find 与 unordered_set 类似,不在这里说明

5.1 KeyofV

由于 unordered_map 中保存的是 pair,进行比较时要用其中的 Key,所以需要使用仿函数 KeyofV 取出它的 Key

cpp 复制代码
template<class K, class V>
struct KeyofV
{
    const K& operator()(const pair<K, V>& kv)
    {
        return kv.first;
    }
};

5.2 Insert

Insert 使用时,除了插入的是一个 pair,返回值和功能都与 unordered_set 是一样的

cpp 复制代码
pair<iterator, bool> Insert(const pair<K, V>& kv)
{
    return _t.Insert(kv);
}

5.3 [] 重载

\] 重载兼具了查找,修改和插入的作用,所以要复用哈希表的 Insert 函数,根据外部传入的 key,\[\] 重载内将 Key 和 V 类型的默认值构成 pair ,调用哈希表的 Insert 来进行插入,在插入时会进行查找,找得到时,返回的 pair 内保存的是指向该节点的迭代器和 false,找不到时会插入新结点,返回的 pair 内保存的是指向新插入结点的迭代器和 true,再通过该迭代器返回结点中值的引用即可,这样外部就可以做到修改值 ```cpp V& operator[](const K& key) { pair ret = _t.Insert({ key, V() }); return ret.first->second; } ``` ### 5.4 unordered_map 的代码 ```cpp template class unordered_map { template struct KeyofV { const K& operator()(const pair& kv) { return kv.first; } }; public: typedef typename HashTable, KeyofV>::iterator iterator; typedef typename HashTable, KeyofV>::const_iterator const_iterator; iterator begin() { return _t.begin(); } const_iterator begin() const { return _t.begin(); } iterator end() { return _t.end(); } const_iterator end() const { return _t.end(); } iterator Find(const K& key) { return _t.Find(key); } bool Erase(const K& key) { return _t.Erase(key); } pair Insert(const pair& kv) { return _t.Insert(kv); } V& operator[](const K& key) { pair ret = _t.Insert({ key, V() }); return ret.first->second; } private: HashTable, KeyofV> _t; }; ```

相关推荐
九久。2 小时前
手动实现std:iterator/std:string/std::vector/std::list/std::map/std:set
c++·stl
小羊羊Python2 小时前
Sound Maze - 基于 SFML+C++14 的音效迷宫开源游戏 | MIT 协议
c++·游戏·开源
txinyu的博客2 小时前
HTTP服务实现用户级窗口限流
开发语言·c++·分布式·网络协议·http
代码村新手2 小时前
C++-类和对象(上)
开发语言·c++
txinyu的博客2 小时前
map和unordered_map的性能对比
开发语言·数据结构·c++·算法·哈希算法·散列表
im_AMBER3 小时前
Leetcode 101 对链表进行插入排序
数据结构·笔记·学习·算法·leetcode·排序算法
予枫的编程笔记3 小时前
【Java集合】深入浅出 Java HashMap:从链表到红黑树的“进化”之路
java·开发语言·数据结构·人工智能·链表·哈希算法
mjhcsp3 小时前
C++ 后缀数组(SA):原理、实现与应用全解析
java·开发语言·c++·后缀数组sa
hui函数3 小时前
如何解决 pip install 编译报错 ‘cl.exe’ not found(缺少 VS C++ 工具集)问题
开发语言·c++·pip