今天就学这5种容器:deque、stack、queue、list、set/multiset,学了这5种之后也差不多,明天就能学完了。但是今天这一篇,只要内容还是看了传智播客c++的视频,写的笔记,有些截图都是从视频截取的,如果想去认真学习容器的,可以看看这个一个视频,个人感觉还不错。
9.1 deque
9.1.1 deque介绍
deque 也是顺序容器的一种,同时也是一个可变长数组。要使用 deque,需要包含头文件 deque。所有适用于 vector 的操作都适用于 deque。
deque 和 vector 有很多类似的地方。在 deque 中,随机存取任何元素都能在常数时间内完成(但慢于vector)。它相比于 vector 的优点是,vector 在头部删除或添加元素的速度很慢,在尾部添加元素的性能较好,而 deque 在头尾增删元素都具有较好的性能(大多数情况下都能在常数时间内完成)。
deque是双口容器,可以在头部插入或删除,也可以在尾部插入或删除。deque没有容量的概念,因为它是动态的以分段的连续空间组合而成,随时可以增加一段新的空间并链接起来。
上面这幅图,画的就是deque的操作,记图比记文字快。
这个图是deque内存管理的,deque是自己管理内存的申请和释放,不需要像vector那样要我们手动释放,deque内存管理是这样,它自己有一个中继器,通俗来说,就是一个指针数组,里面存储着一系列的地址,这里面的地址,就是指向一块内存区域,这一块内存区域才是存储数据用的,如果一条用完了,那就再申请一条,然后把这个内存地址保存到中继器中,由中继器统一管理。
特性总结:
- 双端插入和删除元素效率较高
- 指定位置插入也会导致数据元素移动,降低效率
- 可随机存储,效率高
9.1.2 deque构造函数
构造方法和vertor差不多,不写了,下面的函数如果跟vertor差不多就不写例子了。
9.1.3 deque赋值操作
这个swap交换,只是一个简单的交换,不需要跟vertor一样,需要减少空间。
9.1.4 deque大小操作
这个就没有提供容量的接口,还有申请内存的大小,因为是deque自己管理了。
9.1.5 deque双端插入和删除
vertor容器只能单端插入,不同于我们deque容器,是两端都可以插入和删除。
9.1.6 deque数据存取
这个就跟vertor容器一模一样了。
9.1.7 deque插入操作
插入操作,不能截图,不过跟vertor是差不多的
insert方法
9.1.8 deque删除操作
deque容器就讲到这里,不过传智播客还讲了一个很好的例子,我这里偷懒一下了,以后有空再回来写写。
9.2 stack
9.2.1 stack介绍
stack是一种先进后出的数据结构,它只有一个出口,就是我们平时说的栈,只能在栈顶添加元素,移除元素,获取顶端元素,只有栈顶元素才能被使用,所以说栈不具有遍历行为,没有迭代器。
这个就是栈的数据结构图。
特性总结:
栈不能遍历,不支持随机访问,只能通过top从栈顶获取或删除元素。
9.2.2 stack构造函数
9.2.3 stack赋值操作
9.2.4 stack数据存取操作
9.2.5 stack大小操作
栈容器的操作比较简单,就只有入栈出栈操作,但是栈的用处还是有的,这里就不举例了,有空可以去刷leetcode了。
9.3 queue
9.3.1 queue介绍
队列跟栈一样比较简单,没太多好讲的,队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First in First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。
9.3.2 queue构造函数
9.3.3 queue存取、插入和删除操作
跟栈的区别就是队列可以两端操作,栈只有一端。
9.3.4 queue赋值操作
赋值语句都是重载=操作符。
9.3.5 queue大小操作
队列也比较简单,可以去leetcode刷刷题,用c在leetcode刷题太累了,啥都要自己写,现在可以转移到c++刷题了。
9.4 list
9.4.1 list介绍
ist 是顺序容器的一种。list 是一个双向链表。使用 list 需要包含头文件 list。双向链表的每个元素中都有一个指针指向后一个元素,也有一个指针指向前一个元素,如图所示。
链表的内存是不连续的,需要一个结点的时候,才申请一个结点的内存,所以链表需要一个额外的空间保存,前一个的指针还有后一个的指针。
9.4.2 list构造函数
9.4.3 list数据元素插入和删除操作
这里新加了一个remove办法,remove就是删除容器中所有与elem匹配的元素。
list容器不支持随机访问,只能往后++,所以插入的时候,不能直接用迭代器+2,只能++到指定位置。
写个例子:
#include <list>
void show(list<int>& v) {
for(list<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
int main(int argc, char **argv)
{
list<int> list;
list.push_back(10);
list.push_back(20);
list.push_back(30);
list.push_back(40);
list.push_back(50);
show(list);
list.insert(list.begin(), 1);
show(list);
//list.insert(list.begin() + 1, 2); //失败。不能随机访问
list.insert(list.begin()++, 2); //只支持++操作
show(list);
return 0;
}
9.4.4 list大小操作
链表也没有容量的概念。
9.4.5 list赋值操作
9.4.6 list数据存取
这个是双向链表,支持获取头尾元素。
9.4.7 list反转排列排序
排序有点重要,例子:
bool compare1(int v1, int v2) {
return v1 > v2; //回调函数处理 从大到小
}
int main(int argc, char **argv)
{
list<int> list;
list.push_back(10);
list.push_back(20);
list.push_back(30);
list.push_back(40);
list.push_back(50);
show(list);
list.insert(list.begin(), 1);
show(list);
//list.insert(list.begin() + 1, 2); //失败。不能随机访问
list.insert(list.begin()++, 2); //只支持++操作
show(list);
list.sort(); //排序,默认从小到大
show(list);
//可以修改成从大到小,需要传入回调函数
list.sort(compare1);
show(list);
return 0;
}
算法中也有一个sort方法,那个sort方法是支持随机访问的,也就是算法内部会根据是随机访问的,优化一下排序算法,但是list不支持随机访问,所以list自己提供了一个成员方法。
9.5 set/multiset
9.5.1 set/multiset介绍
set 是关联容器的一种,是排序好的集合(元素已经进行了排序)。set 和 multiset 类似,它和 multiset 的差别在于 set 中不能有重复的元素。multiset 的成员函数 set 中也都有。
set容器其实就是一颗搜索二叉树,我也不明白为什么叫set,而不是tree,有关树的概念可以看我另一篇博客https://blog.csdn.net/C1033177205/article/details/103414388,比较详细介绍了搜索二叉树。
set是以红黑树为底层的一种机制,(要想了解红黑树可以看了另一篇博客https://blog.csdn.net/C1033177205/article/details/103753436,这里详细讲述了红黑树),红黑树查找效率比较高。
我们不能直接修改 set 容器中元素的值。因为元素被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。因此,如果要修改 set 容器中某个元素的值,正确的做法是先删除该元素,再插入新元素。
9.5.2 set/multiset构造函数
9.5.3 set/multiset赋值操作
9.5.4 set/multiset大小操作
9.5.5 set/multiset插入删除操作
9.5.6 set/multiset查找操作
二叉搜索树就是查找比较重要,练习练习:
int main(int argc, char **argv)
{
set<int> s1;
s1.insert(10);
s1.insert(4);
s1.insert(2);
s1.insert(90);
s1.insert(33);
show(s1);
set<int>::iterator it1 = s1.find(4);
if(it1 == s1.end()) { //返回的迭代器要跟end()判断
//如果等于,就相当于没找到
cout << "没找到" << endl;
} else {
cout << "it1 " << *it1 << endl;
}
set<int>::iterator it2 = s1.find(12);
if(it2 == s1.end()) { //返回的迭代器要跟end()判断
//如果等于,就相当于没找到
cout << "没找到" << endl;
} else {
cout << "it2 " << *it2 << endl;
}
//返回第一个key>=keyElem元素的迭代器
set<int>::iterator it3 = s1.lower_bound(10);
if(it3 == s1.end()) { //返回的迭代器要跟end()判断
//如果等于,就相当于没找到
cout << "没找到" << endl;
} else {
cout << "it3 " << *it3 << endl;
}
//返回第一个key>keyElem元素的迭代器
set<int>::iterator it4 = s1.upper_bound(10);
if(it4 == s1.end()) { //返回的迭代器要跟end()判断
//如果等于,就相当于没找到
cout << "没找到" << endl;
} else {
cout << "it4 " << *it4 << endl;
}
//equal_range 返回的是 lower_bound 和 upper_bound 的值
pair<set<int>::iterator, set<int>::iterator> it5 = s1.equal_range(10);
if(it5.first == s1.end()) { //返回的迭代器要跟end()判断
//如果等于,就相当于没找到
cout << "没找到" << endl;
} else {
cout << "it5.first " << *(it5.first) << endl;
}
if(it5.second == s1.end()) { //返回的迭代器要跟end()判断
//如果等于,就相当于没找到
cout << "没找到" << endl;
} else {
cout << "it5.second " << *(it5.second) << endl;
}
return 0;
}
运行的结果:
9.5.7 pair对组
对组(pair)将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别用pair的两个公有函数first和second访问。
类模板:template<class T1, class T2> strcut pair;
创建对组的方法也比较简单,这里就不举例了。
9.5.8 改变默认排序
multiset 类模板的定义如下:
template <class Key, class Pred = less<Key>, class B = allocator<Key> > class multiset {
...
};
该模板有三个类型参数:Key、Pred 和 B。类型参数可以有默认值,默认值就是某种类型。例如,Pred 类型参数的默认值就是 less 类型,B 的默认值就是 allocator 类型。第三个类型参数极少用到,一般都用默认值,因此这里不做介绍。
第一个类型参数说明 multiset 容器中的每个元素都是 Key 类型的。第二个类型参数 Pred 用于指明容器中元素的排序规则,在被实例化后,Pred 可以是函数对象类,也可以是函数指针类型。
multiset 内部在排序时定义了一个变量Pred op,根据表达式op(x, y)来比较两个元素 x、y 的大小。该表达式的值为 true,则说明 x 比 y 小。Pred 的默认值是 less,less 是 STL 中的函数对象类模板,其定义如下:
template <class_Tp>
struct less
{
bool operator() (const _Tp &__x, const _Tp &__y) const
{ return __x < __y; }
};
参考文章:http://c.biancheng.net/view/386.html
所以如果我们要修改默认参数的话,就要传入这个参数,例:
#include <set>
class compare1{
public:
bool operator()(int v1, int v2) {
return v1 > v2; //这里就是回调这个函数,进行排序
}
};
void show(set<int, compare1>& v) {
for(set<int, compare1>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
int main(int argc, char **argv)
{
set<int, compare1> s1;
s1.insert(10);
s1.insert(4);
s1.insert(2);
s1.insert(90);
s1.insert(33);
show(s1);
return 0;
}
本来这里就要完成了,我再写一个用set容器存储类:
#include <set>
class Person
{
public:
int age;
int id;
Person(int age, int id): age(age), id(id){}
//set容器排序,需要用到<号,也可以像上面那个用仿函数
bool operator<(const Person& anthor) const {
return this->age > anthor.age;
}
};
//用这种也可以
class compare1{
public:
bool operator()(const Person& p1, const Person& p2) {
return p1.age < p2.age;
}
};
void show(set<int, compare1>& v) {
for(set<int, compare1>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
int main(int argc, char **argv)
{
set<Person, compare1> s1;
Person p1(10, 1), p2(3, 2),p3(46, 3),p4(5, 4);
s1.insert(p1);
s1.insert(p2);
s1.insert(p3);
s1.insert(p4);
for(set<Person, compare1>::iterator it = s1.begin(); it != s1.end(); it++) {
cout << " 年龄 " << (*it).age << " id " << (*it).id << endl;
}
//find方法,因为这是一个类对象,find查找是谁呢?
//其实find查找的是,你用了那个变量排序,就用那个变量匹配,不会管其他变量的。
Person p5(10, 44);
set<Person, compare1>::iterator it1 = s1.find(p5);
if(it1 == s1.end()) {
cout << "没有找到" << endl;
} else {
cout << "找到 年龄 " << (*it1).age << " id " << (*it1).id << endl;
}
return 0;
}
执行的结果:
从结果看仿函数其到作用了,还有找到的也是只是查找你排序时候的变量,其他的变量就不理会了。