01 vector(向量容器)
底层数据结构:是动态开辟的数组,每次以原来空间大小的2倍进行扩容。
vector扩充的过程:重新申请⼀块连续空间,将原有的数据拷⻉到新空间中,再释放原有空间,完成⼀次扩充。所以每次扩充是重新开辟空间,扩充后原有的迭代器就会失效。
实例化
vector<int> vec;
操作 | 示例操作 | 描述 | 时间复杂度 |
---|---|---|---|
增加: | vec.push_back(20); | 末尾添加元素,导致容器扩容 | O(1) |
vec.insert(it,20); | it迭代器指向的位置添加一个元素20,导致容器扩容 | O(n) | |
删除: | vec.pop_back(); | 末尾删除元素 | O(1) |
vec.erase(it); | 删除it迭代器指向的元素 | O(n) |
查询操作:
vector提供了operator[]
(中括号运算符重载函数),用来随机访问。vector本身也是一个数组,有着随机访问的特点,可以用下标的随机访问,例如vec[5] 。此外还可以使用以下方法:
iterator
迭代器进行遍历;find
,for_each
(foreach是通过iterator来实现的)等方法。
注意:
对容器进行连续插入或者删除操作(insert/erase),一定要更新迭代器,否则第一次insert或者erase完成,迭代器就失效了。
频繁调⽤ push_back() 影响:
push_back() 向 vector 的尾部添加元素,很有可能引起整个对象存储空间重新分配更⼤的内存后,再将原数据拷⻉到新空间中,再释放原有内存,这个过程是耗时耗⼒的,频繁对 vector 调用push_back()会导致性能的下降。
在 C++11 之后, vector 容器中添加了新的⽅法: emplace_back()
。它与 push_back()
⼀样的是,都是在容器末尾添加⼀个新的元素进去,不同的是 emplace_back() 在效率上相⽐
较于 push_back() 有了⼀定的提升。
emplace_back() 函数在原理上⽐ push_back() 有了⼀定的改进,包括在内存优化⽅⾯和
运⾏效率⽅⾯。内存优化主要体现在使⽤了就地构造(直接在容器内构造对象,不⽤拷⻉⼀个
复制品再使⽤)+强制类型转换的⽅法来实现,在运⾏效率⽅⾯,由于省去了拷⻉构造过程,
因此也有⼀定的提升。
常用方法介绍:
size():
返回底层容器有效元素的个数。
empty():
判断容器是否为空。
reserve(20):
vector预留空间的;只给容器底层开辟指定大小的内存空间,并不会添加新的元素。
resize(20):
容器扩容用的;不仅给容器底层开辟指定大小的内存空间,还会添加新的元素。
swap():
两个容器进行元素交换。
int main() {
vector<int> vec; // vector<string> vec;
//vec.reserve(20); // 叫做给vector容器预留空间
for (int i = 0; i < 20; ++i) {
vec.push_back(rand() % 100 + 1);
}
//vector的operator[]运算符重载函数
int size = vec.size();
for (int i = 0; i < size; ++i) {
cout << vec[i] << " ";
}
cout << endl;
// 把vec容器中的所有偶数全部删除
auto it2 = vec.begin();
while (it2 != vec.end()) {
if (*it2 % 2 == 0) {
it2 = vec.erase(it2); // 更新迭代器
}
else {
++it2;
}
}
// 通过迭代器遍历vector容器
auto it1 = vec.begin();
for (; it1 != vec.end(); ++it1) {
cout << *it1 << " ";
}
cout << endl;
// 给vector容器中所有的奇数前面都添加一个小于奇数1的偶数
for (it1 = vec.begin(); it1 != vec.end(); ++it1) {
if (*it1 % 2 != 0) {
it1 = vec.insert(it1, *it1 - 1);
++it1;
}
}
for (it1 = vec.begin(); it1 != vec.end(); ++it1) {
cout << *it1 << " ";
}
cout << endl;
return 0;
}
02 deque(双端队列)和list(链表)
deque(双端队列容器)
底层数据结构:是动态开辟的二维数组,一维数组从2开始,以2倍的方式进行扩容,每次扩容后,原来第二维的数组,从新的第一维数组的下标oldsize/2开始存放,上下都预留相同的空行,方便支持deque的首尾元素添加。
实例化
deque<int> deq;
操作 | 示例操作 | 描述 | 时间复杂度 |
---|---|---|---|
增加: | deq.push_back(20); | 从尾部添加元素 | O(1) |
deq.push_front(20); | 从首部添加元素 | O(1) | |
deq.insert(it,20); | it指向的位置添加元素 | O(n) | |
删除: | deq.pop_back(); | 从尾部删除元素 | O(1) |
deq.pop_front(); | 从首部删除元素 | O(1) | |
deq.erase(it); | 从it指向的位置删除元素 | O(n) |
首位元素的添加:deq.insert(deq.begin(),20)
O(n)
查询搜素:
iterator(连续的insert和erase一定要考虑迭代器失效的问题)。
list(链表容器)
底层数据结构:双向的循环列表 pre data next
实例化
list<int> mylist;
操作 | 示例操作 | 描述 | 时间复杂度 |
---|---|---|---|
增加: | mylist.push_back(20); | 从尾部添加元素 | O(1) |
mylist.push_front(20); | 从首部添加元素 | O(1) | |
mylist.insert(it,20); | it指向的位置添加元素 | O(1) | |
删除: | mylist.pop_back(); | 从尾部删除元素 | O(1) |
mylist.pop_front(); | 从首部删除元素 | O(1) | |
mylist.erase(it); | 从it指向的位置删除元素 | O(1) |
首位元素的添加:mylist.insert(mylist.begin(),20)
,O(n)
- 链表中进行insert的时候,先要进行一个query查询操作,对于链表来说查询操作较慢。
查询搜素:
iterator(连续的insert和erase一定要考虑迭代器失效的问题)。
deque和list,比vector容器多出来的增加删除函数接口:
push_front和pop_front。
03 vecotr,list和deque对比
vector特点:动态数组,内存是连续的,2倍的方式进行扩容;
deque特点:动态开辟的二维数组空间,第二维是固定长度的数组空间,扩容的时候(第一维的数组进行2倍扩容)
面经问题:deque底层内存是否是连续的?不是
容器的纵向考察:容器掌握的深度
容器的横向考察:各个相似容器之间的对比
vector和deque之间的区别
- 底层数据结构不同。
- 前中后插入删除元素的时间复杂度:中间和末尾都是O(1) ,前端 deque是 O(1) ,vector是 O(n)。
- 对于内存的使用效率:vector需要的内存空间必须是连续的;deque可以分块进行数据存储,不需要内存空间必须是一片连续的。
- 在中间进行insert或者erase,vector的效率比deque好,是由于deque的第二维内存空间不是连续的,在deque进行元素移动要比vector慢。
vector和list之间的区别
底层数据结构:数组 双向循环链表
数组:增加删除O(n) 查询O(n) 随机访问O(1)
链表:(考虑搜素的时间)增加删除O(1) 查询O(n) 随机访问O(1)
04 容器适配器
标准容器 - 容器适配器 => 有一种设计模式叫做适配器模式
怎么理解适配器?
- 适配器底层没有自己的数据结构,它是另外一个容器的封装,它的方法全部由底层依赖的容器进行实现的;
- 没有实现自己的迭代器。
// 描述适配器
template<typename T,typename Container=deque<T>>
class Stack {
public:
void push(const T& val) { con.push_back(val); }
void pop() { con.pop_back(); }
T top()const { return con.back(); }
private:
Container con;
};
stack:
stack是⼀种先进后出的数据结构,只有⼀个出⼝,stack 允许从最顶端新增元素,移除最顶端元
素,取得最顶端元素。
push入栈,pop出栈,top查看栈顶元素,empty判断栈空,size返回元素个数
queue:
queue是⼀种先进先出的数据结构,有两个出⼝,允许从最底端加⼊元素,取得最顶端元素,从最底端新增元素,从最顶端移除元素。
push入队,pop出队,front查看队头元素,back查看队尾元素,empty判断队空,size返回元素个数
stack和queue都依赖deque, 为什么不依赖vector呢?
- vector的内存使用效率太低了!没有deque好。
- 对于queue需要支持尾部插入,头部删除,O(1); 如果queue依赖vector,其出队效率很低。
- vector需要大片的连续内存,而deque只需要分段的内存,当存储大量数据时,deque对于内存利用率更好。
priority_queue:
底层是⼀个 vector,使⽤ heap(大根堆) 形成的算法,插⼊,获取 heap 中元素的算法,维护这个vector,以达到允许⽤户以任何次序将任何元素插⼊容器内,但取出时⼀定是从优先权最⾼
(数值最⾼)的元素开始取的⽬的。
push入队,pop出队,top查看队顶元素,empty判断队空,size返回元素个数
priority_queue底层数据结构默认是大根堆。
priority_queue依赖vector, 为什么依赖vector呢?
底层默认把数据组成一个大根堆结构 ,在一个内存连续的数组上构建一个大根堆或者小根堆。
因为堆中的每一个节点跟它左右子节点的关系是通过下标计算的,通过下标的值访问相应元素的值。
int main() {
stack<int> s1;
for (int i = 0; i < 20; ++i) {
s1.push(rand() % 100 + 1);
}
cout << s1.size() << endl;
while (!s1.empty()) {
cout << s1.top() << " ";
s1.pop();
}
cout << endl;
cout << "------------------------------------------" << endl;
queue<int> que;
for (int i = 0; i < 20; ++i) {
que.push(rand() % 100 + 1);
}
cout << que.size() << endl;
while (!que.empty()) {
cout << que.front() << " ";
que.pop();
}
cout << endl;
cout << "------------------------------------------" << endl;
priority_queue<int> pque;
for (int i = 0; i < 20; ++i) {
pque.push(rand() % 100 + 1);
}
cout << pque.size() << endl;
while (!pque.empty()) {
cout << pque.top() << " ";
pque.pop();
}
return 0;
}
05 无序适配器
无序关联容器 => 链式哈希表,增删查O(1)
set:集合 key
map:映射表 [key,value]
unordered_set 单重集合
unordered_multiset 多重集合
unordered_map 单重映射表
unordered_multimap 多重映射表
#include <iostream>
// 使用有序关联容器包含的头文件
#include <set> // set multiset
#include <map> // map multimap
// 使用无序关联容器包含的头文件
#include <unordered_set>
#include <unordered_map>
#include <string>
using namespace std;
/*
关联容器:
1.各个容器底层的数据结构 O(1) O(log2n)
2.常用增删查方法
增加:insert(val)
遍历:iterator自己搜索,调用find成员方法
删除:erase(key) erase(it)
*/
int main()
{
// 处理海量数据查重复;去重复的时候
/*
map中存的都是一个一个的pair对象
[key,value]
struct pair
{
first; => key
second; => value
}
map的operator[]
1.查询
2.如果key不存在,它会插入一对数据[key,string()]
V& operator[](const K &key)
{
insert({key,V()});
}
*/
unordered_map<int, string> map1;
// 可选的插入方式
map1.insert(make_pair(1000, "张三"));
map1.insert({ 1010,"李四" }); // map表增加元素
map1.insert({ 1020,"王五" });
map1.insert({ 1030,"王凯" });
auto it1 = map1.find(1030);
if (it1 != map1.end())
{
// it1 -> pair对象
cout << "key:" << it1->first << " value:" << it1->second << endl;
}
/*
map1.erase(1020); // {1020,"王五"}删除了
// map1[2000]; key:2000 vaule:""
map1[2000] = "刘硕"; //相当于map1.insert({2000,"刘硕"});
map1[1000] = "张三2";
cout << map1.size() << endl;
// map operator[](key) => value 查询
cout << map1[1000] << endl;
*/
#if 0
unordered_set<int> set1; // 不会存储key值重复的元素
// unordered_multiset<int> set1; 多重集合允许重复的元素
for (int i = 0; i < 50; ++i)
{
set1.insert(rand() % 20 + 1); // 由哈希表的函数决定插入函数位置
// vector/deque/list(线性表要指向位置,需要迭代器) insert(it(迭代器),val(值))
}
// cout << set1.size() << endl;
// cout << set1.count(15) << endl;
auto it1 = set1.begin();
for (; it1 != set1.end(); ++it1)
{
cout << *it1 << " ";
}
cout << endl;
set1.erase(20); // 按key值删除元素
for (it1 = set1.begin(); it1 != set1.end(); )
{
if (*it1 == 30)
{
it1 = set1.erase(it1); // 调用erase,it1迭代器就失效了
}
else
{
++it1;
}
}
it1 = set1.find(20);
if (it1 != set1.end())
{
set1.erase(it1);
}
for (int v : set1)
{
cout << v << " ";
}
cout << endl;
#endif
return 0;
}
06 有序关联容器
#include <iostream>
// 使用有序关联容器包含的头文件 红黑树
#include <set> // set multiset
#include <map> // map multimap
#include <string>
using namespace std;
class Student
{
public:
Student(int id=0,string name="")
:_id(id),_name(name){}
/*
map表用k排序,因k是整数自己会排序,所以无序
bool operator<(const Student& stu)const
{
return _id < stu._id;
}
*/
private:
int _id;
string _name;
friend ostream& operator<<(ostream& out, const Student& stu);
};
ostream& operator<<(ostream& out, const Student& stu)
{
out << ":id:" << stu._id << " name:" << stu._name << endl;
return out;
}
int main()
{
map<int, Student> stuMap;
stuMap.insert({ 1000,Student(1000,"张雯") });
stuMap.insert({ 1020,Student(1020,"李广") });
stuMap.insert({ 1030,Student(1030,"高阳") });
//stnMap.erase(it) stuMap.erase(1020) stuMap[2000] [2000,V()]
// cout << stuMap[1020] << endl; stuMap.find(key);
auto it = stuMap.begin();
for (; it != stuMap.end(); ++it)
{
cout << "key:" << it->first << " value:" << it->second << endl;
}
cout << endl;
return 0;
}
int main()
{
set<Student> set1;
set1.insert(Student(1000, "张雯"));
set1.insert(Student(1020, "李广"));
for (auto it1 = set1.begin(); it1 != set1.end(); ++it1)
{
cout << *it1 << endl;
}
/*
set<int> set1;
for (int i = 0; i < 20; ++i)
{
set1.insert(rand() % 20 + 1);
}
for (int v : set1)
{
cout << v << " ";
}
cout << endl;
*/
return 0;
}
07 迭代器
iterator(迭代器)模式⼜称 Cursor(游标)模式,⽤于提供⼀种⽅法顺序访问⼀个聚合对象中各个元素, ⽽⼜不需暴露该对象的内部表示。或者这样说:iterator模式是运⽤于聚合对象的⼀种模式,通过运⽤该模式,使得我们可以在不知道对象内部表示的情况下,按照⼀定顺序(由iterator提供的⽅法)访问聚合对象中的各个元素。
iterator:普通的正向迭代器
const_iterator:常量的正向迭代器 只能读,而不能写了
reverse_iterator:普通的反向迭代器
const_reverse_iterator:常量的反向迭代器
int main()
{
vector<int> vec;
for (int i = 0; i < 20; ++i)
{
vec.push_back(rand() % 100 + 1);
}
//auto it1 = vec.begin(); // it1的类型是vector<int>::iterator
vector<int>::const_iterator it1 = vec.begin();
/*
const_iterator <= iterator能赋值的原因;
class const_iterator
{
public:
const T& operator*(){return *_ptr;}
class iterator:public const_iterator
{
T& operator*(){return *_ptr;}
}
派生类的关系
*/
for (; it1 != vec.end(); ++it1)
{
cout << *it1 << " ";
}
cout << endl;
// rbegin():返回的是最后一个元素的反向迭代器表示
// rend():返回的是首元素前驱位置的迭代器的表示
// vector<int>::reverse_iterator
vector<int>::const_reverse_iterator rit = vec.rbegin();
for (; rit != vec.rend(); ++rit)
{
cout << *rit << " ";
}
cout << endl;
for (int v : vec)
{
cout << v << " ";
}
cout << endl;
return 0;
}
08 函数对象
函数对象 => C语言里面的函数指针
把有operator()小括号运算符重载函数的对象,称作函数对象或者仿函数
// 使用C的函数指针解决方案
template<typename T>
bool mygreater(T a, T b)
{
return a > b;
}
template<typename T>
bool myless(T a, T b)
{
return a < b;
}
- 通过函数对象调用operator(),可以省略函数的调用开销,比通过函数指针调用函数(不能够inline内联调用)效率高
- 因为函数对象是用类生成的,所以可以添加相关的成员变量,用来记录函数对象
使用时更多的信息
//C++函数对象的版本实现
template<typename T>
class mygreater
{
public:
bool operator()(T a, T b) // 二元函数对象
{
return a > b;
}
};
template<typename T>
class myless
{
public:
bool operator()(T a, T b) // 二元函数对象
{
return a < b;
}
};
// compare是C++的库函数模板
template<typename T,typename Compare>
bool compare(T a, T b,Compare comp)
{
// 通过函数指针调用函数,是没有办法内联的,效率很低,因为有函数调用开销
return comp(a, b); // operator(a,b)
}
int main()
{
cout << compare(10, 20, mygreater<int>) << endl;
cout << compare(10, 20, myless<int>) << endl;
return 0;
}
#endif
int main()
{
// 举例1
priority_queue<int> que1; // vector
for (int i = 0; i < 10; ++i)
{
que1.push(rand() % 100);
}
while (!que1.empty())
{
cout << que1.top() << " ";
que1.pop();
}
cout << endl;
using MinHeap = priority_queue<int,vector<int>, greater<int>>;
MinHeap que2; // vector
for (int i = 0; i < 10; ++i)
{
que2.push(rand() % 100);
}
while (!que2.empty())
{
cout << que2.top() << " ";
que2.pop();
}
cout << endl;
// 举例2
set<int, greater<int>> set1;
for (int i = 0; i < 10; ++i)
{
set1.insert(rand() % 100);
}
for (int v : set1)
{
cout << v << " ";
}
cout << endl;
return 0;
}
09 泛型算法和绑定器
泛型算法 = template + 迭代器 + 函数对象
特点一:泛型算法的参数接受的都是迭代器
特点二:泛型算法的参数还可以接受函数对象(C函数指针)
sort,find,find_if,binary_search,for_each
绑定器: + 二元函数对象 =》 一元函数对象
bind1st:把二元函数对象的operator()(a,b)的第一个形参绑定起来
bind2nd:把二元函数对象的operator()(a,b)的第二个形参绑定起来
#include <iostream>
#include <vector>
#include <algorithm> // 包含了C++ STL里面的泛型算法
#include <functional> // 包含了函数对象和绑定器
using namespace std;
int main()
{
int arr[] = { 12,4,78,9,21,43,56,52,42,31 };
vector<int> vec(arr, arr + sizeof(arr) / sizeof(arr[0]));
for (int v : vec)
{
cout << v << " ";
}
cout << endl;
// 默认小到大排序
sort(vec.begin(), vec.end());
for (int v : vec)
{
cout << v << " ";
}
cout << endl;
// 有序容器进行二分查找
if (binary_search(vec.begin(), vec.end(), 21))
{
cout << "binary_search 21存在" << endl;
}
auto it1 = find(vec.begin(), vec.end(), 21);
{
cout << "find21存在" << endl;
}
// 传入函数对象greater,改变容器元素排序时的比较方式
sort(vec.begin(), vec.end(), greater<int>());
for (int v : vec)
{
cout << v << " ";
}
cout << endl;
// 78 56 52 43 42 31 21 12 9 4
// 把48按序插入到vector容器当中,找第一个小于48的数字
// find_if需要的是一个一元函数对象
// greater a > b less a < b
auto it2 = find_if(vec.begin(), vec.end(),
//bind1st(greater<int>(),48));
//bind2nd(less<int>(), 48);
[](int val)->bool {return val < 48; });
vec.insert(it2, 48);
for (int v : vec)
{
cout << v << " ";
}
cout << endl;
// for_each可以遍历容器的所有元素,可以自行添加合适的函数对象对容器
// 的元素进行过滤
for_each(vec.begin(), vec.end(),
[](int val)->void
{
if (val % 2 == 0)
{
cout << val <<" ";
}
});
cout << endl;
return 0;
}