【C++从0到王者】第三十一站:map与set(上)

简介: 【C++从0到王者】第三十一站:map与set

一、关联式容器

STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。

那什么是关联式容器?它与序列式容器有什么区别?

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更高

二、pair键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息

stl里面对于pair的定义是这样的

template <class T1, class T2>
struct pair
{
  typedef T1 first_type;
  typedef T2 second_type;
  T1 first;
  T2 second;
  pair() : first(T1()), second(T2())
  {}
  pair(const T1& a, const T2& b) : first(a), second(b)
  {}
};

pair有三种构造函数,不难看出,分别是无参的构造,拷贝构造,以及通过两个值来进行构造

除了三种构造函数以外,它还有一种方式,也可以生成pair对象。这个不是一个成员函数,所以可以直接使用。

如上的一些构造函数的使用将在map中进行演示

三、set

1. set的介绍

如下图所示:

我们可以注意到它的模板参数是要比其他容器多一个的,这个容器我们也可以看到是一个仿函数。我们使用优先级队列的时候也用过这个仿函数

集合是按照特定顺序存储唯一元素的容器。

在一个集合中,元素的值也标识它(值本身就是键,类型为 T),并且每个值必须是唯一的。集合中元素的值在容器中不能修改(元素总是 const 类型的),但是可以从容器中插入或删除元素。

在内部,集合中的元素总是按照其内部比较对象(类型为 Compare )指示的特定严格弱排序标准排序。

在通过键访问单个元素时,set 容器通常比 unordered_set 容器慢,但是它们允许基于次序对子集进行直接迭代。

集合通常以二叉搜索树的形式实现。这颗二叉搜索树是红黑树。

set其实就相当于key模型的二叉搜索树

注意:set里面的值是不可以被修改的,它实现这一点的原理就是将迭代器和const迭代器都是const迭代器没有任何区别。

2. set的部分接口以及应用

可以注意到,一共有三个构造函数,第一个是全缺省的默认构造函数,第二个是迭代器区间构造,第三个是拷贝构造。

不过这个拷贝构造的代价比较大,因为它是一个树的拷贝,而且析构也一样有很大的代价。

还有一个接口是insert

这里的value_type就是T

还有很多接口其实看一眼就大概知道啥意思

比如如下代码:可以简单的测试一下代码

void test_set1()
{
  set<int> s;
  s.insert(1);
  s.insert(5);
  s.insert(2);
  s.insert(2);
  s.insert(2);
  s.insert(2);
  s.insert(4);
  s.insert(4);
  s.insert(4);
  s.insert(3);
  set<int>::iterator it = s.begin();
  while (it != s.end())
  {
    cout << *it << " ";
    it++;
  }
  cout << endl;
}
int main()
{
  test_set1();
  return 0;
}

我们可以发现,set可以自动去重和排序

而它的去重原理就是:一个值已经有了,我们就不插入即可

同时我们也可以试一下set的删除

auto pos = s.find(3);
  s.erase(pos);
  s.erase(4);
  for (auto e : s)
  {
    cout << e << " ";
  }
  cout << endl;

如上有演示了两种删除,从表面来看,看上去好像没有什么大的区别。我们可以认为下面的是通过上面的进行实现的。

不过在find中如果没有找到的话,是会直接报错的。

而下面如果没有找到是什么也不会发生的

不过我们可以加一个判断来解决这个问题

这是因为find找不到会返回一个end迭代器导致的

在容器中搜索与val等效的元素,如果找到则返回一个迭代器,否则返回一个迭代器给set::end。

但是我们知道算法库里面也有一个find,这个find似乎也能完成这个操作

其实这两个find是存在一定的差异的,set里面的find是根据二叉搜索树的性质来进行查找的(其实是红黑树,效率更优),时间复杂度为O(logN),而算法库里面的find是一个一个查找的,时间复杂度为O(N)。

3. count

count的作用是,给一个值,然后返回它出现了几次。不过因为set容器里面的值是唯一的,所以一个元素在这里面,返回1,否则返回0

如下的代码可以演示find和count寻找一个数据的使用

void test_set2()
{
  set<int> s;
  s.insert(1);
  s.insert(5);
  s.insert(2);
  s.insert(4);
  s.insert(4);
  s.insert(3);
  set<int>::iterator it = s.begin();
  while (it != s.end())
  {
    cout << *it << " ";
    it++;
  }
  cout << endl;
  if (s.find(5) != s.end())
  {
    cout << "找到了" << endl;
  }
  if (s.count(5))
  {
    cout << "找到了" << endl;
  }
}
int main()
{
  test_set2();
  return 0;
}

4. lower_bound和upper_bound

返回迭代器到下界

返回一个迭代器,该迭代器指向容器中的第一个元素,该元素不被认为位于val之前(即,它要么等价,要么在val之后)。

该函数使用其内部比较对象(key_comp)来确定这一点,并返回一个迭代器,指向key_comp(element,val)将返回false的第一个元素。

如果用默认比较类型(less)实例化set类,则该函数返回一个指向不小于val的第一个元素的迭代器。

类似的成员函数upper_bound具有与lower_bound相同的行为,只是set包含一个与val等效的元素:在这种情况下,lower_bound返回一个指向该元素的迭代器,而upper_bound返回一个指向下一个元素的迭代器。

返回迭代器到上界

返回一个迭代器,该迭代器指向容器中被认为在val之后的第一个元素。

该函数使用其内部比较对象(key_comp)来确定这一点,并返回一个迭代器,指向key_comp(val,element)将返回true的第一个元素。

如果用默认比较类型(less)实例化set类,则该函数返回指向第一个大于val的元素的迭代器。

类似的成员函数lower_bound具有与upper_bound相同的行为,只是set包含一个与val等效的元素:在这种情况下,lower_bound返回一个指向该元素的迭代器,而upper_bound返回一个指向下一个元素的迭代器。

我们可以使用这段代码来进行验证

void test_set3()
{
  std::set<int> myset;
  std::set<int>::iterator itlow, itup;
  for (int i = 1; i < 10; i++) myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
  itlow = myset.lower_bound(30);                   
  itup = myset.upper_bound(60);                        
  myset.erase(itlow, itup);                     // 10 20 70 80 90
  for (auto e : myset)
  {
    cout << e << " ";
  }
  cout << endl;
}
int main()
{
  test_set3();
  return 0;
}

lower_bound和upper_bound一个设置为>=,一个设置为>。这样刚好可以将我们输入值所处的区间进行控制,刚好满足左闭右开。无论是构造也好,删除也好,插入也好都是刚好十分方便的。

相关文章
|
21天前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
55 18
你对Collection中Set、List、Map理解?
|
14天前
|
存储 缓存 安全
只会“有序无序”?面试官嫌弃的List、Set、Map回答!
小米,一位热衷于技术分享的程序员,通过与朋友小林的对话,详细解析了Java面试中常见的List、Set、Map三者之间的区别,不仅涵盖了它们的基本特性,还深入探讨了各自的实现原理及应用场景,帮助面试者更好地准备相关问题。
51 20
|
1月前
|
存储 C++ 容器
【C++】map、set基本用法
本文介绍了C++ STL中的`map`和`set`两种关联容器。`map`用于存储键值对,每个键唯一;而`set`存储唯一元素,不包含值。两者均基于红黑树实现,支持高效的查找、插入和删除操作。文中详细列举了它们的构造方法、迭代器、容量检查、元素修改等常用接口,并简要对比了`map`与`set`的主要差异。此外,还介绍了允许重复元素的`multiset`和`multimap`。
30 3
【C++】map、set基本用法
|
1月前
|
存储 算法 C++
【C++】unordered_map(set)
C++中的`unordered`容器(如`std::unordered_set`、`std::unordered_map`)基于哈希表实现,提供高效的查找、插入和删除操作。哈希表通过哈希函数将元素映射到特定的“桶”中,每个桶可存储一个或多个元素,以处理哈希冲突。主要组成部分包括哈希表、哈希函数、冲突处理机制、负载因子和再散列,以及迭代器。哈希函数用于计算元素的哈希值,冲突通过开链法解决,负载因子控制哈希表的扩展。迭代器支持遍历容器中的元素。`unordered_map`和`unordered_set`的插入、查找和删除操作在理想情况下时间复杂度为O(1),但在冲突较多时可能退化为O(n)。
22 5
|
1月前
|
存储 C++ 容器
【C++】map的模拟实现
C++中的`map`是STL中的一种关联容器,存储键值对且键唯一。`map`基于红黑树实现,自动按键排序,支持动态调整、复杂数据类型、丰富的成员函数及双向迭代器。插入、查找等操作保证了对数时间复杂度,适用于需要快速查找和有序存储的场景。
23 3
|
1月前
|
存储 C++ 容器
【C++】set模拟实现
C++中的`set`是STL提供的一种关联容器,用于存储唯一元素并自动按特定顺序(默认升序)排序。其内部通过红黑树实现,保证了高效的插入、删除和查找操作,时间复杂度均为O(log n)。`set`支持迭代器遍历,提供了良好的数据访问接口。
34 3
|
2月前
|
存储 JavaScript 前端开发
Set、Map、WeakSet 和 WeakMap 的区别
在 JavaScript 中,Set 和 Map 用于存储唯一值和键值对,支持多种操作方法,如添加、删除和检查元素。WeakSet 和 WeakMap 则存储弱引用的对象,有助于防止内存泄漏,适合特定场景使用。
|
2月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
44 1
|
3月前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
42 5
|
3月前
|
存储 前端开发 API
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用
该文章详细介绍了ES6中Set和Map数据结构的特性和使用方法,并探讨了它们在前端开发中的具体应用,包括如何利用这些数据结构来解决常见的编程问题。
ES6的Set和Map你都知道吗?一文了解集合和字典在前端中的应用