C++容器篇,unordered_map和unordered_set容器

C++容器——unordered_set和unordered_map容器

1. unordered系列关联式容器

在C++98中,STL提供了以红黑树为底层结构的关联容器,在查找时的效率可以达到O(log_2(n)),当树的结点非常多的时候,查找的效率也不离校。因此在C++11,STL有提供了四个unordered系列的关联式容器,这些容器的底层是哈希桶,查找的效率可以达到常数级别。

2. unordered_map

2.1 unordered_map的介绍

unordered_map的参考文档

  1. unordered_map是存储<key, value>键值对的关联式容器,并允许通过key快速索引到对应的value值。
  2. 在内部,unordered_map没有进行任何特定的顺序排序,这是因为底层的数据结构所导致的。
  3. unordered_map通过key访问单个元素要比map快,它通常在遍历元素子集的范围迭代方面效率较低。
  4. 它也是重载了(operator [])运算符,通过将key作为下标访问对应的value值。
  5. 它的迭代器至少是前向迭代器。
2.2 unordered_map的使用

unordered_map必须包含头文件#include <unordered_map>,并且属于std命名空间里面。

#include <unordered_map>
#include <string>
#include <iostream>

using namespace std;

int main()
{
    unordered_map<int, string> m;
    m[0] = "hello";
    m[1] = "world";
    m[2] = ".";

    auto it = m.begin();
    while (it != m.end())
    {
        cout << it->first << " : " << it->second << " "; // 0 : hello 1 : world 2 : .
        ++it;
    }
    cout << endl;
}

其实unordered_map与map的区别并不大,主要是底层使用的数据结构的不同,导致效率和顺序性有所差异。

2.2.1 unordered_map的定义
构造函数接口说明
unordered_map();无参构造
  • 第一个是无参构造,构造一个空的unordered_map,传入的模板参数有key,value的类型,比较器的类型(默认是less)。
2.2.2 unordered_map的迭代器

unordered_map不存在反向迭代器,因为存入到容器中的数据顺序是乱序的,因此没有办法从最后一个位置向前遍历。

迭代器的使用使用说明
begin()返回指向开始位置的迭代器
end()返回指向末尾元素的下一个位置的迭代器
cbegin()返回指向开始并且为常量的迭代器
cend()返回指向末尾元素的下一个位置的并且为常量的迭代器
2.2.3 unordered_map的容量
容量说明接口说明
size获取容器中实际的个数
empty判断是否为空
2.2.4 unordered_map的元素访问
函数接口说明
mapped_type& operator[](const key_type& k)获取key对应的value值,如果key存在,则无需插入到unordered_map中,直接返回key对应的value值,如果key不存在,会将对应的key和value值插入到unordered_map中。
2.2.5 unordered_map的修改和查询
增删改查接口说明
pair insert(const value_type& x)在unordered_map中插入键值对<key,value>,如果成功,返回新插入位置的迭代器,true,否则返回,key对应位置的迭代器,false
erase(iterator pos)删除unordered_map中pos位置的元素。
erase(const value_type& x)删除unordered_map中键值对为x的元素
swap(unordered_map& m)交换unordered_map中的元素
find(const key_type& x)在unordered_map中查找key为x的元素,找到返回该元素位置的迭代器,找不到返回end()
count(const key_type& x)返回unordered_map中key值为x的元素的个数
clear清除unordered_map的元素
2.2.6 unordered_map的桶操作
函数声明功能介绍
size_t bucket_count() const返回哈希桶中桶的总个数
size_t bucket_size(size_t n) const返回n号桶中有效元素的总个数
size_t bucket(const K& key)返回元素key所在的桶号

3. unordered_set

参考:

unordered_set的参考文档

4. 在线OJ

重复n次的元素

class Solution 
{
public:
    int repeatedNTimes(vector<int>& A) 
    {
        size_t N = A.size()/2;
        // 用unordered_map统计每个元素出现的次数
        unordered_map<int, int> m;
        for(auto e : A)
        m[e]++;
        // 找出出现次数为N的元素
        for(auto& e : m)
        {
            if(e.second == N)
            	return e.first;
        }
    }
};

两个数组的交集I

class Solution 
{
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) 
    {
        // 用unordered_set对nums1中的元素去重
        unordered_set<int> s1;
        for (auto e : nums1)
        	s1.insert(e);
        // 用unordered_set对nums2中的元素去重
        unordered_set<int> s2;
        for (auto e : nums2)
        	s2.insert(e);
        // 遍历s1,如果s1中某个元素在s2中出现过,即为交集
        vector<int> vRet;
        for (auto e : s1)
        {
        	if (s2.find(e) != s2.end())
        		vRet.push_back(e);
        }
        return vRet;
    }
};

两个数组的交集II

class Solution {
public:
    vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
        unordered_map<int,int> count{};
        vector<int> result;
        for(auto num1 : nums1)
        {
            count[num1]++;
        }
        for(auto num2 : nums2)
        {
            if(count[num2] > 0)
            {
                result.push_back(num2);
                count[num2]--;
            }
        }
        return result;
    }
};

存在重复元素

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_map<int,int> count;
        for(auto num : nums)
        {
            ++count[num];
            if(count[num] >= 2)
                return true;
        }

        return false;
    }
};

两句话中不常见的单词

class Solution {
public:
    vector<string> uncommonFromSentences(string s1, string s2) {
        unordered_map<string, int> count;
        vector<string> ret;
        auto insert = [&](const string& s)
        {
            stringstream ss(s);
            string word;
            while(ss >> word)
            {
                ++count[move(word)];
            }
        };

        insert(s1);
        insert(s2);

        for(const auto& [word,occ] : count)
        {
            if(occ == 1)
                ret.push_back(word);
        }
        return ret;
    }
};

5. 模拟实现

因为这两个容器底层用到了哈希桶的数据结构,参考数据结构之哈希(C++实现)

5.1 哈希表的改造
  1. 模板参数列表的改造

    // K:关键码类型
    // T: 不同容器T的类型不同,如果是unordered_map,T代表一个键值对,如果是
    // unordered_set,T 为 K
    // KeyOfValue: 因为T的类型不同,通过value取key的方式就不同,详细见
    // unordered_map/set的实现
    // Hash: 哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将Key转换为整形数字才能取模
    template<class K, class T, class Hash, class KeyOfT>
    class HashTable;
    
  2. 增加迭代器操作

    	// 前置声明
    	template<class K, class T, class Hash, class KeyOfT>
        class HashTable;
    
    	template<class K, class T, class Hash, class KeyOfT>
        struct __HashIterator
        {
            typedef HashNode<T> Node;
            typedef HashTable<K, T, Hash, KeyOfT> HT;
            typedef __HashIterator<K, T, Hash, KeyOfT> Self;
    
            Node* _node;
            HT* _pht;
    
            __HashIterator(Node* node, HT* pht)
                :_node(node)
                    , _pht(pht)
                {}
    
            T& operator*()
            {
                return _node->_data;
            }
    
            T* operator->()
            {
                return &_node->_data;
            }
    
            Self& operator++()
            {
                if (_node->_next)
                {
                    // 当前桶中迭代
                    _node = _node->_next;
                }
                else
                {
                    // 找下一个桶
                    Hash hash;
                    KeyOfT kot;
                    size_t i = hash(kot(_node->_data)) % _pht->_tables.size();
                    ++i;
                    for (; i < _pht->_tables.size(); ++i)
                    {
                        if (_pht->_tables[i])
                        {
                            _node = _pht->_tables[i];
                            break;
                        }
                    }
    
                    // 说明后面没有有数据的桶了
                    if (i == _pht->_tables.size())
                    {
                        _node = nullptr;
                    }
                }
    
                return *this;
            }
    
            bool operator!=(Self& s) const
            {
                return _node != s._node;
            }
    
            bool operator==(Self& s) const
            {
                return _node == s._node;
            }
        };
    
    
  3. 增加通过key获取value的操作

    	template<class K, class T, class Hash, class KeyOfT>
    	class HashTable
    	{
    		typedef HashNode<T> Node;
    		// 迭代器
    		template<class K, class T, class Hash, class KeyOfT>
    		friend struct __HashIterator;
    	public:
    		typedef __HashIterator<K, T, Hash, KeyOfT> iterator;
    
    		iterator begin()
    		{
    			for (size_t i = 0; i < _tables.size(); ++i)
    			{
    				if (_tables[i])
    				{
    					return iterator(_tables[i], this);
    				}
    			}
    
    			return end();
    		}
    
    		iterator end()
    		{
    			return iterator(nullptr, this);
    		}
    
    		~HashTable()
    		{
    			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] = nullptr;
    			}
    		}
    
    		inline size_t __stl_next_prime(size_t n)
    		{
    			static const size_t __stl_num_primes = 28;
    			static const size_t __stl_prime_list[__stl_num_primes] =
    			{
    				53, 97, 193, 389, 769,
    				1543, 3079, 6151, 12289, 24593,
    				49157, 98317, 196613, 393241, 786433,
    				1572869, 3145739, 6291469, 12582917, 25165843,
    				50331653, 100663319, 201326611, 402653189, 805306457,
    				1610612741, 3221225473, 4294967291
    			};
    
    			for (size_t i = 0; i < __stl_num_primes; ++i)
    			{
    				if (__stl_prime_list[i] > n)
    				{
    					return __stl_prime_list[i];
    				}
    			}
    
    			return -1;
    		}
    
    		std::pair<iterator, bool> Insert(const T& data)
    		{
    			Hash hash;
    			KeyOfT kot;
    
    			// 去重
    			iterator ret = Find(kot(data));
    			auto ret_end = end();
    			if (ret != ret_end)
    			{
    				return std::make_pair(ret, false);
    			}
    
    			// 负载因子到1就扩容
    			if (_size == _tables.size())
    			{
    				//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
    				vector<Node*> newTables;
    				//newTables.resize(newSize, nullptr);
    				newTables.resize(__stl_next_prime(_tables.size()), nullptr);
    				// 旧表中节点移动映射新表
    				for (size_t i = 0; i < _tables.size(); ++i)
    				{
    					Node* cur = _tables[i];
    					while (cur)
    					{
    						Node* next = cur->_next;
    
    						size_t hashi = hash(kot(cur->_data)) % newTables.size();
    						cur->_next = newTables[hashi];
    						newTables[hashi] = cur;
    
    						cur = next;
    					}
    
    					_tables[i] = nullptr;
    				}
    
    				_tables.swap(newTables);
    			}
    
    			size_t hashi = hash(kot(data)) % _tables.size();
    			// 头插
    			Node* newnode = new Node(data);
    			newnode->_next = _tables[hashi];
    			_tables[hashi] = newnode;
    			++_size;
    
    			return std::make_pair(iterator(newnode, this), true);
    		}
    
    		iterator Find(const K& key)
    		{
    			if (_tables.size() == 0)
    			{
    				return end();
    			}
    
    			Hash hash;
    			KeyOfT kot;
    			size_t hashi = hash(key) % _tables.size();
    			Node* cur = _tables[hashi];
    			while (cur)
    			{
    				if (kot(cur->_data) == key)
    				{
    					return iterator(cur, this);
    				}
    
    				cur = cur->_next;
    			}
    
    			return end();
    		}
    
    		bool Erase(const K& key)
    		{
    			if (_tables.size() == 0)
    			{
    				return false;
    			}
    
    			Hash hash;
    			KeyOfT kot;
    			size_t hashi = hash(key) % _tables.size();
    			Node* prev = nullptr;
    			Node* cur = _tables[hashi];
    			while (cur)
    			{
    				if (kot(cur->_data) == key)
    				{
    					// 1、头删
    					// 2、中间删
    					if (prev == nullptr)
    					{
    						_tables[hashi] = cur->_next;
    					}
    					else
    					{
    						prev->_next = cur->_next;
    					}
    
    					delete cur;
    					--_size;
    
    					return true;
    				}
    
    				prev = cur;
    				cur = cur->_next;
    			}
    
    			return false;
    		}
    
    		// ....
    
    	private:
    		std::vector<Node*> _tables;
    		size_t _size = 0; // 存储有效数据个数
        };
    
5.2 unordered_map模拟实现
namespace ming
{
	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const std::pair<K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename HashBucket::HashTable<K, std::pair<K, V>, Hash, MapKeyOfT>::iterator iterator;

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

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

		pair<iterator, bool> Insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.Insert(std::make_pair(key, V()));
			return ret.first->second;
		}

	private:
		HashBucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT> _ht;
	};
}
5.3 unordered_set模拟实现
#pragma once
#include "HashTable.h"

namespace ming
{
    template <class K, class Hash = HashFunc<K>>
    class unordered_set
    {
        struct SetKeyOfT
        {
            const K& operator()(const K& key)
            {
                return key;
            }
        };

    public:
        typedef typename HashBucket::HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;

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

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

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

    private:
        HashBucket::HashTable<K, K, Hash, SetKeyOfT> _ht;
    };
}
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
unordered_set和unordered_mapC++11中新增加的两个关联式容器,它们的区别主要体现在以下几个方面: 1. 底层实现:unordered_set和unordered_map的底层都是哈希表,而setmap的底层是红黑树。哈希表是一种根据键值直接进行访问的数据结构,而红黑树是一种自平衡的二叉搜索树。 2. 排序:unordered_set是不可排序的,而set是有序的。unordered_map是无序的,而map是有序的。这是因为哈希表是根据键值的哈希值进行存储和访问的,没有固定的顺序。 3. 迭代器:unordered_set和unordered_map使用的是单向迭代器,而setmap使用的是双向迭代器。单向迭代器只能从前往后遍历容器中的元素,而双向迭代器可以从前往后和从后往前遍历。 4. 效率:由于底层实现的不同,unordered_set和unordered_map的插入、查找和删除操作的时间复杂度都是O(1),而setmap的时间复杂度是O(logN)。因此,unordered_set和unordered_map相对于setmap来说,在大部分情况下具有更高的效率。 综上所述,unordered_set和unordered_mapsetmap在底层实现、排序、迭代器和效率上存在一些区别。选择使用哪个容器取决于具体的需求和性能要求。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [C++ 哈希表及unordered_set + unordered_map容器](https://blog.csdn.net/qq_60750110/article/details/126746419)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [算法(42)-数组等值切割-前缀累加和-哈希表Map-set版-C++](https://download.csdn.net/download/weixin_38710566/14039060)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值