unordered_map和unordered_set的使用

前言

前面我们对map和set的使用进行了介绍,以及对其底层(红黑树)也进行了探索和实现,并基于红黑树封装实现了迷你版的map和set!本期我们来介绍另外两个关联式的容器即unordered_mapunordered_set!

本期内容介绍

• unordered_map常用接口的介绍

• unordered_multimap和unordered_map的区别

• unordered_set常用接口的介绍

• unordered_multiset和unordered_set的区别

• 在OJ中的使用

在C++98中,STL提供了以红黑树为底层结构的树形关联式容器即map和set, 他们中的元素是有序的(走中序),他们的查询效率可以达到log(N);但是如果节点但非常多的话效率也不是很理想,因此C++11新引入了两个底层以哈希结构的关联式容器即unordered_map和unordered_set其查找效率可以到到O(1)!下面就来介绍一下这两容器:

• unordered_map常用接口的介绍

OK, 我们还是老样子,先来看看使用文档:

unordered_map和unordered_multimap的头文件unordered_map

为了更好的理解文档,我们有必要先搞清楚其内部的成员类型:

key_type是存储元素pair的第一个模板参数mapped_type(它实际上就是T类型)是存储元素pair的第二个模板参数vlaue_type就是存储的pair<K, V>类型, size_type就是size_t

OK,下面我们来看看文档:

• unordered_map是关联式容器,存储的是一个键值对<K,V>,可以快速的通过K查找!

• unordered_map存储的键值对的K和V可以是不同的!

• unordered_map存储的元素不是有序的,他是基于哈希桶(后面介绍)实现的!

• unordered_map通过K访问元素的效率比map快

• unordered_map实现了[ ]可以直接使用K访问V!

• unordered_map的迭代器是单向的!

• unordered_map的时间复杂度是恒定的O(1)

• 构造、拷贝构造、赋值拷贝、析构函数

unordered_map<int, int> umap1;//空构造
unordered_map<string, int> umap2;//K和V可以不同

//迭代器区间构造
vector<pair<int, int>> v = { {1,1}, {2,2}, {3,3}, {4,4} };
unordered_map<int, int> umap3(v.begin(), v.end());

//拷贝构造
unordered_map<int, int> umap4(umap3);

//列表初始化初始化
unordered_map<double, string> umap5({ {9.9,"aaa"}, {6.6, "bbb"}, {1.1, "cp"} });

//赋值拷贝
unordered_map<double, string> umap6;
umap6 = umap5;//上面的umap5

//列表初始化对象拷贝构造
unordered_map<int, int> umap7;
umap7 = { {1,1}, {2,2}, {3,3}, {4,4} };

析构函数就不介绍了,还是和以前一样:清理资源释放空间!

• 容量

• empty

判断是否为空

• size

获取当前容器中元素的个数

void Test_unordered_map2()
{
	//列表初始化对象拷贝构造
	unordered_map<int, int> umap7 = { {1,1}, {2,2}, {3,3}, {4,4} };

	bool ept = umap7.empty();
	cout << ept << endl;  // 0 -> 不为空

	size_t sz = umap7.size();
	cout << sz << endl; // 4
}

• 迭代器

unordered_map的迭代器是单向的迭代器!

// 验证const迭代器 --> 如果如果范围for可以这里是const对象,就说明const迭代器没问题
void Print(const unordered_map<double, string>& umap)
{
	for (const auto& e : umap)
	{
		cout << e.first << "->" << e.second << endl;
	}
	cout << endl;
}

void Test_unordered_map3()
{
	//列表初始化初始化
	unordered_map<double, string> umap5({ {9.9,"aaa"}, {6.6, "bbb"}, {1.1, "cp"} });

	//迭代器遍历
	unordered_map<double, string>::iterator it = umap5.begin();
	while (it != umap5.end())
	{
		cout << it->first << "->" << it->second << endl;
		++it;
	}
	cout << endl;

	Print(umap5);
}

• 元素访问

[ ] 和 at 都是通过K来访问V

void Test_unordered_map4()
{
	unordered_map<string, int> umap;
	vector<string> v = { "hehe", "haha","cpdd", "hehe", "haha", "cp", "cpdd", "yyds"};

	// [] 正常访问
	for (auto& s : v)
		umap[s]++;

	for (auto& e : umap)
		cout << e.first << "->" << e.second << endl;
	cout << "---------------------" << endl;

	// at 正常访问
	for (auto& s : v)
		umap.at(s)++;

	for (auto& e : umap)
		cout << e.first << "->" << e.second << endl;
}

• [ ] 和 at 的区别:[ ]是暴力检查(断言),而at是"温柔"的检查(抛异常)

• 元素查找

• find

查找元素,找i到了,返回该元素的迭代器,否则返回end

• count

获取key的数量,如果key存在返回1,否则返回0

void Test_unordered_map5()
{
	unordered_map<int, int> umap7 = { {1,1}, {2,2}, {3,3}, {4,4} };
	unordered_map<int, int>::iterator ret = umap7.find(3);
	cout << ret->first << "->" << ret->second << endl;
	size_t cnt = umap7.count(3);
	cout << "count: " << cnt << endl;
}

• 元素修改

• insert

插入元素

void Test_unordered_map6()
{
	unordered_map<int, int> umap = { {1,1}, {2,2}, {3,3}, {4,4} };
	
	//插入一个pair<K,V>
	umap.insert({ 6,6 });

	//在hint迭代器位置插入一个pair
	umap.insert(--umap.end(), { 0 ,0 });

	//插入一段迭代器区间
	unordered_map<int, int> umap1 = { {11,11}, {22,22}, {33,33}, {44,44} };
	umap.insert(umap1.begin(), umap1.end());

	//插入一个
	umap.insert({ {12,12}, {23,23}, {32,32} });
}
• erase

删除元素

void Test_unordered_map7()
{
	unordered_map<int, int> umap = { {1,1}, {2,2}, {3,3}, {4,4} };
	
	//删除迭代器位置的元素
	umap.erase(umap.begin());

	//删除K对应的元素
	umap.erase(2);

	//删除一段迭代器区间
	umap.erase(++umap.begin(), --umap.end());
}
• clear

清空unordered_map

void Test_unordered_map8()
{
	unordered_map<int, int> umap = { {1,1}, {2,2}, {3,3}, {4,4} };
	cout << umap.size() << endl;

	umap.clear();
	cout << umap.size() << endl;
}

• swap

两个unordered_map交换

unordered_map<int, int> umap1 = { {1,1}, {2,2}, {3,3}, {4,4} };
unordered_map<int, int> umap2 = { {10,10}, {20,20}, {30,30}, {40,40} };

umap1.swap(umap2);

剩下的这些就是得了解底层才可以看明白:

这里不在介绍了,后面介绍了底层就明白了!

• unordered_multimap和unordered_map的区别

他这里和unordered_map唯一的区别就是可以存在重复key的元素, 所以不再支持[]了其他的接口都是一模一样的:这里就不再一一介绍了!

下面来看看,可重复的例子:

void Test_unordered_multimap()
{
	unordered_map<string, int> m1;
	unordered_multimap<string, int> m2;

	m1.insert({ "香蕉", 5 });
	m1.insert({ "苹果", 3 });
	m1.insert({ "香蕉", 1 });
	m1.insert({ "西瓜", 6 });

	for (auto& e : m1)
		cout << e.first << " : " << e.second << endl;

	cout << "----------------------" << endl;

	m2.insert({ "香蕉", 5 });
	m2.insert({ "苹果", 3 });
	m2.insert({ "香蕉", 1 });
	m2.insert({ "西瓜", 6 });

	for (auto& e : m2)
		cout << e.first << " : " << e.second << endl;
}

OK,unordered_map就介绍道这里,下面我们来介绍一下unordered_set!

• unordered_set的介绍

OK,先来看一下内部成员类型:

OK,下面来看看文档:

• unordered_set是存储唯一元素的容器,可以通过V访问元素

• unordered_set中的元素不可修改,但是支持插入和删除的操作

• unordered_set存的元素是无序的,是基于哈希桶实现

• unordered_set通过K访问元素比set快

• unordered_set的迭代器是单向

• unordered_set的时间复杂度恒定为O(1)

• 构造、拷贝构造、复制拷贝和析构

void Test_unordered_set1()
{
	//空构造
	unordered_set<int> us1;

	//迭代器区间构造
	vector<string> v = { "aaa", "bbb", "ccc" };
	unordered_set<string> us2(v.begin(), v.end());

	//拷贝构造
	unordered_set<int> us3(us1);

	//列表初始化对象构造
	unordered_set<int> us4 = { 1,2,3,4,5,6 };
}

//赋值拷贝
unordered_set<int> us5;
us5 = us4;//上面的us4

//列表初始化对象赋值拷贝
unordered_set<int> us6;
us6 = { 1,2,3,4,5,6 };

析构还是和以前一样,释放资源,清理空间,自动调用,不在多介绍了~!

• 容量

• empty

判断是否为空

• size

获取当前容器中的元素个数

void Test_unordered_set2()
{
	//列表初始化构造
	unordered_set<int> us4 = { 1,2,3,4,5,6 };

	bool ept = us4.empty();
	cout << "empty : " << ept << endl;

	size_t sz = us4.size();
	cout << "size : " << sz << endl;
}

• 迭代器

注意:unordered_set的迭代器是单向的迭代器

void Print(const unordered_set<int>& us)
{
	for (const auto& e : us)//验证const 迭代器
	{
		cout << e << endl;
	}
	cout << endl;
}

void Test_unordered_set3()
{
	//列表初始化构造
	unordered_set<int> us4 = { 1,2,3,4,5,6 };

	//普通迭代器
	unordered_set<int>::iterator it = us4.begin();
	while (it != us4.end())
	{
		cout << *it << endl;
		++it;
	}
	cout << endl;

	Print(us4);
}

• 元素查找

• find

查找元素,找到了返回该元素的迭代器,否则返回end

• count

获取key的数量,如果key存在返回1,否则返回0

void Test_unordered_set4()
{
	//列表初始化构造
	unordered_set<int> us4 = { 1,2,3,4,5,6 };

	unordered_set<int>::iterator ret = us4.find(1);
	cout << *ret << endl;

	size_t cnt = us4.count(1);
	cout << cnt << endl;
}

• 修改

• insert

OK,上面unordered_map只是给出了例子没有验证,这里顺便验证一下:

void Print(const unordered_set<int>& us)
{
	for (const auto& e : us)
	{
		cout << e << " ";
	}
	cout << endl;
}

void Test_unordered_set5()
{
	//列表初始化构造
	unordered_set<int> us4 = { 1,2,3,4,5,6 };
	Print(us4);
	cout << "-----------------------" << endl;
	//插入一个val -> 插入 0
	us4.insert(0);
	Print(us4);
	cout << "-----------------------" << endl;

	//在迭代器位置插入一个val,再--end位置插入66
	us4.insert(--us4.end(), 66);
	Print(us4);
	cout << "-----------------------" << endl;

	//插入一段迭代器区间
	vector<int> nums = { 11,22,33 };
	us4.insert(nums.begin(), nums.end());
	Print(us4);
	cout << "-----------------------" << endl;

	//插入一个列表初始化对象
	us4.insert({ 99,88,77 });
	Print(us4);
}

这里你可能想为什么我在--end插入的是66,结果在第二个位置呢?首先一开始我们就说过unordered_map个unordered_set是无序的,具体为什么下一期介绍了底层的哈希表就知道了!

• erase

void Test_unordered_set6()
{
	//列表初始化构造
	unordered_set<int> us4 = { 1,2,3,4,5,6, 7, 8, 9,10,11 };
	Print(us4);
	cout << "-----------------------" << endl;

	//删除某个迭代器位置的元素
	us4.erase(++us4.begin());
	Print(us4);
	cout << "-----------------------" << endl;

	//删除某个K对应的元素
	us4.erase(4);
	Print(us4);
	cout << "-----------------------" << endl;

	//删除一段迭代器区间
	us4.erase(++us4.begin(), --us4.end());
	Print(us4);
}

• clear

清空当前容器

• swap

交换两个容器的内容

void Test_unordered_set7()
{
	//列表初始化构造
	unordered_set<int> us1 = { 1,2,3,4,5,6, 7, 8, 9,10,11 };
	unordered_set<int> us2 = {10,20,30 };

	Print(us1);
	cout << "-----------------------" << endl;
	us1.clear();
	cout << endl;

	cout << "--------- 交换前:---------" << endl;
	Print(us1);
	Print(us2);
	us1.swap(us2);

	cout << "--------- 交换后:---------" << endl;
	Print(us1);
	Print(us2);
}

OK,unorsered_set的常用接口就这些,剩下的就是底层的了,得了解底层才可以理解!这里不再介绍了,等下一期介绍了底层就明白了!

• unordered_multiset和unordered_set的区别

unordered_multiset和unordered_set的唯一区别就是允许重复元的存在

OK,这里不在一一介绍每个接口的使用了,和unordered_set的一样!这里还是演示一下可以存在重复元素的例子:

void print(const unordered_multiset<int>& us)
{
	for (const auto& e : us)
	{
		cout << e << " ";
	}
	cout << endl;
}
void Test_unordered_set8()
{
	//列表初始化构造
	unordered_multiset<int> us1 = { 1,2,3,4,5,6,7,4,2,1 };
	print(us1);
}

• 正OJ中的使用

重复n次的元素

思路:先将数组中的元素放到unordered_map中,然后再次遍历数组,让每个元素去查找unordered_map该元素的次数是n(数组长度的一半)就返回!最后没找到就返回-1

class Solution 
{
public:
    int repeatedNTimes(vector<int>& nums) 
    {
        int n = nums.size() / 2;
        unordered_map<int, int> m;
        
        for(auto& e : nums)
            m[e]++;

        for(auto& e : nums)
            if(n == m[e])
                return e;

        return -1;//取值范围是>=0的,所以走到这里是没有找到
    }
};

两个数组的交集

思路:将一个数组先放到unordered_set中,然后遍历另一个数组,如果元素存在将该元素添加到返回数组,然后将哈希表中的元素删除,最后返回结果数组即可

class Solution 
{
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) 
    {
        //返回数组
        vector<int> ret;

        //将一个数组放到unordered_set中
        unordered_set<int> s(nums1.begin(), nums1.end());

        //遍历另一个数组,如果该元素存在就是交集元素
        for(auto& e : nums2)
        {
            if(s.count(e))
            {
                ret.push_back(e);
                s.erase(e);//将交集元素,添加到返回数组中后就再哈希表中删除
            }
        }

        //返回
        return ret;
    }
};

这就是哈希表再OJ中的用法!下面这几道题,可以自己尝试一下:

两个数组的交集2

存在重复元素

两句中的不常见单词

OK,本期内容就到这里,好兄弟,我们下期再见~!

结束语:我们风雨兼程绝不空手而归!

  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值