vector向量容器
底层数据结构:动态开辟的数组,在windows下的vs2017,2019上用vector是1.5倍扩容的,当在Linux下用gcc,g++去编译是2倍扩容的。
初始化
vector<int> v1;//向量中没有元素
vector<int> v1(10);//向量中10个元素全为0
vector<int> v1(10,1);//向量中10个元素全为1
vector<int> v1(v1);
vector<int> v1={1,2,3};
增加:导致容器扩容
v1.push_back(20);//末尾添加元素,时间复杂度为O(1)
v1.insert(it,20);//在it迭代器指向的位置添加一个元素20,时间复杂度是O(n)
v1.insert(v1.begin(),3,9);//在首部添加3个9
删除
v1.pop_back();//末尾删除元素,O(1)
v1.erase(it);//删除it迭代器指向的元素
v1.erase(v1.begin(),v1.end()-2);//删除区间元素,其他元素前移。左闭右开
v1.clear();//删除所有元素
[ 注意 ] 对容器进行连续或插入删除操作(insert/erase),一定要更新迭代器,否则第一次insert或者erase完成,迭代器就失效了。
查询、遍历
//通过operator[],下标的随机访问
for(int i = 0; i < v1.size(); i++)
{
cout << v1[i] << " ";
}
//通过迭代器进行遍历
for(vector<int>::iterator it = v1.begin(); it != v1.end(); it++)
{
cout << *it << " ";
}
//迭代器反向输出
for(vector<int>::reverse_iterator it = v1.rbegin(); it != v1.rend(); it++)
{
cout << *it << " ";
}
//for...each..,也是通过迭代器实现的
for(int i : v1)
{
cout << i << " ";
}
//索引
for(int i = 0; i < v1.size(); i++)
{
cout << v1.at(i) << " ";
}
常用方法介绍
size():返回容器底层元素的个数
empty():判断容器是否为空
v1.swap(v2):两个容器进行元素交换
reverse(n):给容器底层预留n个元素空间,并不会添加新元素(vec.size()==0 && vec.capacity()==n)
resize(n):不仅给容器底层预留n个空间,此时容器内也会有n个元素(vec.size()==n && vec.capacity()==n)
deque是双端队列容器,底层是动态开辟的二维数组。一维数组从2开始,以2倍的方式进行扩容,每次扩容后原来第二维数组从新的第一维数组下标oldsize/2
开始存放,上下留出相同的空间,方便deque首尾元素添加。
由于是双端队列,所以最开始的时候,first和last都是指向相同的地方
扩容后:
初始化
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(1)
查询搜索:和vector一样,连续的insert和erase一定要考虑迭代器失效问题。
剩余的其他常用方法和vector一样。
双向循环列表,每一个节点都有data,next和pre。增加删除查询和deque一样。
链表的一个好处就是插入删除并不需要像数组一样涉及其他元素的移动,O(1)操作,只需要更改地址域;查询操作就比较慢。
deque和list比vector容器多出来的增加删除函数接口:push_front
和pop_front
vector的特点:动态数组,内存是连续的,2倍的方式进行扩容,0-1-2-4-8…,而扩容所带来的效率不是很高,因为它要在新的内存上拷贝构造原来老内存的对象,再把原来老内存的对象析构、内存释放。
deque的特点:动态开辟的二维数组空间,第二维是固定长度的数组空间,扩容的时候第一维的数组进行2倍扩容,扩容后原来第二维数组从新的第一维数组下标oldsize/2
开始存放,上下留出相同的空间,方便deque首尾元素增加删除。
deque底层内存是否是连续的?
不是。deque每一个第二维是连续的,但是不是所有的第二维都是连续的,因为deque是一个动态开辟的二维数组,deque是分段连续的。可以画图展示为什么。
vector和deque的区别?
vector和list的区别?
vector和list区别即数组和链表的区别。
容器适配器底层没有自己的数据结构,也没有实现自己的迭代器,它是另外一个容器的封装,它的方法全部由底层依赖的容器进行实现,就是一种代理。
依赖deque
适配的,底层还是deque方法,但它把deque方法封装了,给我们提供了push、pop操作。
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<int> s;
s.push(20);//入栈
s.pop();//出栈
s.top();//查看栈顶元素
s.empty();//判断栈空
s.size();//返回栈顶元素个数
依赖deque
常用方法
queue<int> que;
que.push(20);//入队
que.pop();//出队
que.back();//查看队尾元素
que.front();//查看队头元素
que.empty();//判断队空
que.size();//返回元素个数
stack和queue为什么依赖deque,而不依赖vector?
优先级队列,实现的是一个大根堆,通过下标的计算访问每个元素,所以基于vector。
常用方法
priority_queue<int> pque;
pque.push(20);//入队
pque.pop();//出队
pque.top();//查看队顶元素
que.empty();//判断队空
que.size();//返回元素个数
priority_queue为什么依赖vector,而不依赖deque?
由于priority_queue底层是一个大根堆结构,大根堆是一颗完全二叉树,存储方式是数组,根据坐标之间的关系来区分根节点、左孩子和右孩子, 根据坐标索引来访问数据结构中的相应元素,这就需要我们每一个元素内存是绝对连续的,这样计算下标之间的关系才有意义,所以我们需要在一个内存连续的数组上构建一个大根堆或者小根堆,而deque的第二维不是连续的,vector是完全连续的。
底层是链式哈希表,增删查是O(1)
unordered_set:可以用来海量数据去重
#include
#include
using namespace std;
int main() {
unordered_set<int> set1;//不会存储key值重复的元素
// 1. 插入
for (int i = 0; i < 20; i++) {
set1.insert(rand() % 20);
}
// cout << set1.size() << endl; // < 20
// cout << set1.count(5) << endl; // 0或1
// 2. 遍历,迭代器可访问所有容器
for (auto iter = set1.begin(); iter != set1.end(); iter++) {
cout << *iter << " ";
}
cout << endl;
// for_each,底层就是用迭代器
for (int v : set1) {
cout << v << " ";
}
cout << endl;
// 3. 删除 erase(iter), erase(key)
for (auto iter = set1.begin(); iter != set1.end(); ) {
if (*iter == 10) {
// erase后iter失效,删除后从当前位置开始比较,不用++
iter = set1.erase(iter);
}
else {
iter++;
}
}
// 4. 查找find(key),找到返回对应位置iter,否则返回end()
auto iter = set1.find(20);
if (iter != set1.end()) {
set1.erase(iter);
}
return 0;
}
unordered_map:可以用来处理海量数据查重复
map的operator[]的功能:
#include
#include
#include
using namespace std;
int main()
{
unordered_map<int,string> map1;
map1.insert(make_pair(1000,"张三"));
map1.insert({1010,"李四"});
map1.insert({1020,"王五"});
map1.insert({1000,"王凯"});
cout << map1.size() << endl;//3,key值不重复
cout << map1[1000] << endl;//张三
map1.erase(1020);
map1[2000];//key:2000;value:""
map1[2001]="刘硕";//map1.insert({2001,"刘硕"});
map1[1000] = "张三2";
auto it = map1.find(1030);
if(it != map1.end())
{
cout << "key:" << it->first << "value:" << it->second << endl;
}
}
unordered_multimap
#include
#include
#include
using namespace std;
int main()
{
unordered_map<int,string> map1;
map1.insert(make_pair(1000,"张三"));
map1.insert({1010,"李四"});
map1.insert({1020,"王五"});
map1.insert({1000,"王凯"});
cout << map1.size() << endl;//4,key值可以重复
}
底层是红黑树,增删查是O(logn),
用自定义类型使用有序关联容器的时候,set
要给提供operator<
运算符重载函数,map不用提供,使用key就可以排序。
set:有序,不可以重复
用迭代器去遍历是红黑树中序遍历的结果
#include
#include // set multiset 红黑树
using namespace std;
class Student {
public:
Student(int id, string name) :_id(id), _name(name) {};
//用自定义类型使用有序关联容器的时候,要给提供operator<运算符重载函数
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 << stu._id << " " << stu._name << endl;
return out;
}
int main() {
set<Student> set1;
set1.insert(Student(10, "张三"));
set1.insert(Student(30, "李二"));
set1.insert(Student(20, "王五"));
for (const Student& stu : set1) {
cout << stu;
}
return 0;
}
map:有序,不可以重复
#include
#include // map multimap 红黑树
using namespace std;
class Student {
public:
// 对于存放于map的元素,记得给默认构造函数(mp[key]也可能插入元素)
Student(int id=0, string name=string()) :_id(id), _name(name) {};
private:
int _id;
string _name;
friend ostream& operator<<(ostream& out, const Student& stu);
};
ostream& operator<<(ostream& out, const Student& stu) {
out << stu._id << " " << stu._name << endl;
return out;
}
int main() {
map<int, Student> mp;
mp.insert(make_pair(10, Student(10, "张三")));
mp.insert(pair<int, Student>(30, Student(30, "李二")));
mp.insert({ 20, Student(20, "王五") });
for (auto iter = mp.begin(); iter != mp.end(); iter++) {
cout << iter->first << " " << iter->second;
}
return 0;
}