目录
目录
简介(如果需要频繁插入和删除数据,遍历的不会特别多的时候用)
基本概念:
STL(Standard Template Library)广义上讲分为三类,algorithm算法,container容器,iterator迭代器,容器和算法通过迭代器可以进行无缝地连接,容器里面存数据,算法进行排序或查找,迭代器是用来访问容器里的数据的
STL的一个重要特点就是数据结构和算法的分离,即数据的存储和数据的访问进行了分离,如栈的栈顶指针为栈数据的常用访问,而栈的数据存储是通过栈的数据结构
STL的特点
可重用性(stl中几乎所有代码都是通过模板类,模板函数的方式实现)
高性能(map的键值,map采用红黑树的变体来实现,查找某个元素只需log2^n的时间复杂度,即十万条数据差不多只用比较十四次)
高移植性(项目A用STL写的模块可以直接移植到项目B)
跨平台(VS可以在XCode上直接编译)
STL的容器
序列式容器
每个元素都有固定的位置,取决于插入时机和地点,和元素值无关,如栈
vector向量
deque双队列
list链表
stack栈
queue队列
数据结构 | 描述 | 实现头文件 |
向量(vector) | 连续存储的元素 | <vector> |
双队列(deque) | 连续存储的指向不同元素的指针所组成的数组 | <deque> |
列表(list) | 由节点组成的双向链表,每个结点包含着一个元素 | <list> |
栈(stack) | 后进先出的值的排列 | <stack> |
优先队列(priority_queue) | 元素的次序是由作用域所存储的值对上的某种谓词决定的一种队列 | <queue> |
关联式容器
元素位置取决于特定的排序准则,和插入顺序无关
set
multiset
map
multimap
数据结构 | 描述 | 实现头文件 |
集合(set) | 由节点组成的红黑树(查找效率比较高),每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序,即不会出现两个相同的元素 | <set> |
多重集合(multiset) | 允许存在两个次序相等的元素的集合 | <set> |
映射(map) | 由{键,值}对组成的集合,以某种作用于键对上的谓词排列 | <map> |
多重映射(multimap) | 允许键对有相等的次序的映射 | <map> |
序列式容器
Vector容器(动态扩容的线性表)
简介(简单理解为动态数组,即可以动态扩容)
vector是将元素置于一个动态数组中加以管理的容器
vecotr可以随机存取元素(支持索引值直接存取,用[]操作符或at()方法)
vector尾部添加或移除元素非常快速,但在中部或头部插入元素或移除元素比较费时
vector的元素地址是连续的,动态扩容后可能地址会发生变化,即可能会在一块新的地址重新放新的连续的元素
vector对象的默认构造
vector采用模板类实现,vector对象的默认构造形式
#include <vector>
vector<T> vecT;
vector<int> vecInt; //一个存放int的vector容器
vector<float> vecFloat; //一个存放float的vector容器
//... //尖括号内还可以设置指针类型或自定义类型
class CA{}
vector<CA*> vecpCA; //用于存放CA对象的指针的vector容器
vector<CA> vecCA; //用于存放CA对象的vector容器,由于容器元素的存放是按值复制的方式进行的
//所以此时CA必须提供CA的拷贝构造函数,以保证CA对象间拷贝正常
vector对象的带参构造
vector(beg,end); //构造函数将[beg,end]区间中的元素拷贝给本身,注意该区间是左闭右开的区间,即beg为指向第一个元素的指针,end为指向最后一个元素的下一个元素的指针
vector(n,elem); //构造函数将n个elem拷贝给本身
vector(const vector &vec); //拷贝构造函数
#include <vector>
#include <iostream>
using namespace std;
int main()
{
int iArray[]{ 1,2,3,4,5 };
vector<int> vecIntA(iArray, iArray + 5); //里面为1,2,3,4,5
//vector<int> vecIntB(vecIntA.begin(), vecIntA.end()); //里面为1,2,3,4,5
vector<int> vecIntB(vecIntA.begin() + 2, vecIntA.begin() + 5); //里面为3,4,5
vector<int> vecIntC(3, 9); //里面为9,9,9
vector<int> vecIntD(vecIntC); //拷贝构造函数调用,里面为9,9,9
for (int i = 0; i < vecIntD.size(); i++)
{
cout << vecIntD[i] << endl;
}
}
vector方法
vector<int> v1;
赋值
v1.assign(beg,end); //构造函数将[beg,end)区间中的数据拷贝给本身,注意该区间是左闭右开的区间,即beg为指向第一个元素的指针,end为指向最后一个元素的下一个元素的指针(注意,使用assign方法会将之前的赋值给清空,比如原来为10个5,assign为3个1,则容器内为3个1)
v1.assign(n,elem); //将n个elem拷贝赋值给本身(注意,使用assign方法会将之前的赋值给清空,比如原来为10个5,assign为3个1,则容器内为3个1)
vector& operator=(const vector &vec); //重载等号操作符,例如vecIntD=vecIntC;或v1[2]=10;
v1.swap(vec); //互换两个容器中的元素
大小
v1.size() //大小,如果没有元素则为0,容器为空即为0
v1.empty() //是否为空,为空返回true
v1.resize(num) //重新制定容器的长度为num,若变长,则以默认值填充新位置,如果变短则末尾超出容器长度的元素将被删除
v1.resize(num,elem) //变长则以elem值填充,变短删除
访问
//如果下标越界可能会导致程序异常终止
v1.at(idx); //返回索引idx所指的数据,如果idx越界,抛出out_of_range异常
v1[idx]; //返回索引idx所指的数据,越界时,运行直接报错
插入与删除
动态数组(长度为动态的,可以动态插入和删除)
v1.push_back(elem); //从末尾添加一个元素
v1.pop_back(); //从末尾删除一个元素
v1.insert(pos,elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置,pos类型为迭代器类型,需要构建vector迭代器对象
v1.insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值
v1.insert(pos,beg,end); //在pos位置插入[beg,end)区间的数据,无返回值
v1.clear(); //移除容器的所有数据
v1.erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置
v1.erase(pos); //在pos位置删除一个元素,返回下一个数据的位置
//v1.remove(elem); //删除容器中所有与elem匹配的元素,里面没有该方法
//注意:vector中没有删除某个元素的函数即v1里没有remove,写在这只是提一下
vector的迭代器
迭代器简介
迭代器提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围(避免了越界的情况产生,即用下标法和at)
为什么需要迭代器?
STL提供每种容器的实现原理各不相同,如果没有迭代器我们需要记住每一种容器中对象的访问方法,迭代器虽然在每个容器中的实现方式不一样,但对用户来说操作方法是一致的,也就是说通过迭代器统一了对所有容器的访问方式,例如:无论哪个容器,访问当前元素的下一个元素我们可以通过迭代器自增进行访问
迭代器为end()后++迭代器为begin()位置
vector中的迭代器为随机访问迭代器:访问可以一次移动多个位置
vector<int>::iterator it;
int iArray[]{ 99,2,3,4,5 };
vector<int> vecIntA(iArray, iArray + 5); //里面为99,2,3,4,5
it=vecIntA.begin();
for(it=vecIntA.begin(); it!=vecIntA.end(); it++)
{
cout<<*it<<endl;
}
vector迭代器失效的情况
插入元素后失效
因为vector容器是动态扩容的连续存储结构,所以空间的地址可能变化了,因此直接用扩容前的迭代器的地址插入元素后的地址可能会失效(记得在扩容后重新赋值迭代器,例如it=v1.insert(it,8);)
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
vector<int>::iterator it=v.begin()+3; //为4
it=v.insert(it,8); //insert会返回一个新位置的迭代器,需要重新给原来的it迭代器进行赋值更新地址,否则会报错
cout<<*it<<endl;
删除元素后失效
失效原因与上面相同
vector<int>::iterator it;
int iArray[]{ 1,2,3,3,3,4,5 };
vector<int> vecIntA(iArray, iArray + 7); //里面为 1,2,3,3,3,4,5
//或
//vector<int> vecIntA{ 1,2,3,3,3,4,5 };
for (it = vecIntA.begin(); it != vecIntA.end();)
{
if (*it == 3)
{
it = vecIntA.erase(it); //erase返回一个删除的元素指向的下一个元素位置的迭代器
cout << "aaa" << endl; //输出三次
}
else
{
it++;
}
}
for (it = vecIntA.begin(); it != vecIntA.end(); it++)
{
cout << *it << endl; //输出1,2,4,5
}
deque容器(双向队列)
简介(简单理解为动态数组,即可以动态扩容)
deque是double-ended queue的缩写
deque是双端数组,而vector为单端(vector两端都可以操作,但操作其时长度为朝着一端增长,因此其为单端,双端的意思为在最前面增加不用移动元素位置,也可以朝前增长)
deque在接口上和vector十分相似,在许多操作的地方可以直接替换
deque可以随机存取元素(支持索引值直接存取,用[]操作符或at()方法)
deque头部和尾部添加或移除元素都非常快速,但在中部插入或移除元素比较费时
#include<deque>
deque容器的地址不像vector一样是连续的,它动态扩容后地址不一定是连续的,之所以用下标访问还是不会出错因为它源码重载了[]符号
deque方法
deque与vector在操作上几乎一致,多了两个函数
deque<int> deq{1,2,3};
deq.push_front(elem); //在容器头部插入一个数据
deq.pop_front(); //删除容器第一个数据
list容器(双向链表)
简介(如果需要频繁插入和删除数据,遍历的不会特别多的时候用)
查找时间复杂度为O(n),插入和删除也是O(n)
list是一个双向链表容器,可以高效地进行插入删除元素
list不可以随机存取元素,所以不支持at.(pos)函数与[]操作符
It++(ok) //可以++和--
It+5(err) //不能把迭代器加上某个常量
#include<list>
list对象的默认构造
list采用模板类实现,对象的默认构造形式:list<T> lst如
list<int> lstInt; //声明一个存放int的list容器
list<float> lstFloat; //声明一个存放float的list容器
list<string> lstString; //声明一个存放string的list容器
list对象的带参数构造
list(n,elem); //构造函数将n个elem拷贝给本身
list(beg,end); //构造函数将[beg,end)区间中的元素拷贝给本身
list(const list &lst); //拷贝构造函数例如:list<int>lst1(lst);
list<int>lst{ 10,100,1000,10000 };
list<int> lst0(3, 10); //10,10,10
//链表不能直接用迭代器+n,只能一个一个++或者--,且要注意是左闭右开的区间
list<int>::iterator beg = lst.begin();
beg++;
list<int>::iterator end = lst.end();
end--; //此时*end为10000,并不是1000
cout << *end << endl;
list<int> lst1(beg,end); //100,1000
list<int> lst2(lst); //调用拷贝构造函数
list方法
list的数据存取
注意:front()和back()返回的是元素,begin()和end()返回的是迭代器理解成指针值,需要解引用才能赋值,也就是说lst.front()和*(lst.begin())的结果为相等的
lst.front(); //返回第一个元素
lst.back(); //返回最后一个元素
赋值
lst.assign(beg,end); //构造函数将[beg,end)区间中的数据拷贝给本身,注意该区间是左闭右开的区间,即beg为指向第一个元素的指针,end为指向最后一个元素的下一个元素的指针(注意,使用assign方法会将之前的赋值给清空,比如原来为10个5,assign为3个1,则容器内为3个1)
lst.assign(n,elem); //将n个elem拷贝赋值给本身(注意,使用assign方法会将之前的赋值给清空,比如原来为10个5,assign为3个1,则容器内为3个1)
vector& operator=(const list &lst); //重载等号操作符,例如lstIntD=lstIntC;注意list容器没有下标拿到数据
lst.swap(lst0); //互换两个容器中的元素
大小
lst.size() //大小,如果没有元素则为0,容器为空即为0
lst.empty() //是否为空,为空返回true
lst.resize(num) //重新制定容器的长度为num,若变长,则以默认值填充新位置,如果变短则末尾超出容器长度的元素将被删除
lst.resize(num,elem) //变长则以elem值填充,变短删除
list头尾的添加移除操作
list<int> lst;
lst.push_back(elem); //在容器尾部加入一个元素
lst.pop_back(); //删除容器中最后一个元素
lst.push_front(elem); //在容器开头插入一个元素
lst.pop_front(); //从容器开头移除第一个元素
注意:insert是从迭代器的前面开始加,好比现在有12356,迭代器insert的位置应当为5,而不是为3
lst.insert(pos,elem); //在pos位置插入一个elem元素的拷贝,返回新数据的位置,pos类型为迭代器类型,需要构建list迭代器对象
lst.insert(pos,n,elem); //在pos位置插入n个elem数据,无返回值
lst.insert(pos,beg,end); //在pos位置插入[beg,end)区间的数据,无返回值
lst.clear(); //移除容器的所有数据
lst.erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置
lst.erase(pos); //在pos位置删除一个元素,返回下一个数据的位置
lst.remove(elem); //删除容器中所有与elem匹配的元素
list与迭代器
list容器的迭代器不能+n,因为是链表只能一个一个地访问
list容器的迭代器为双向迭代器:双向迭代器从两个方向读写容器
//反向迭代器的遍历
for (list<int>::reverse_iterator rIt = lst.rbegin(); rIt != lst.rend(); rIt++)
{
cout << *rIt << endl;
}
list迭代器在手动插入和删除时有没有失效的情况?
插入的情况没有,手动删除的时候有,具体手动操作的写法参考上面的迭代器失效的情况,建议使用find和remove,list与vector不同,vector为连续地址因此在插入和删除时因动态扩容导致系统开辟了一片新的比较大的地址所以vector的迭代器会存在失效情况,需要在insert和erase后重新赋值,而list为链表,地址并不是连续的,因此其不能用lst[n]来访问,只能通过迭代器++或--的方式访问,其插入时不会失效,是因为不管是emplace还是insert不会导致节点的删除以及位置的变化,而在list删除的时候它用的erase出现了节点的删除,因此它不知道迭代器指向的下一个的位置在哪里,erase的返回值为其下一个元素的位置,需要把它重新赋值给迭代器来指向其下一个元素
反转list容器的方法
lst.reverse(); //123直接反转顺序成321,当然也可以直接用reverse_iterator的rbegin()和rend()
stack容器(栈)
stack简介
stack是堆栈容器,是一种先进后出的容器
只能操作栈顶的指针,stack容器没有迭代器,因为不允许遍历,如果想访问元素只能通过出栈后使用top()来实现,如果栈内为空则pop()和top()会报错
#include <stack>
stack对象的默认构造
stack采用模板类实现,stack对象的默认构造形式stack<T> s;
stack对象的拷贝构造与赋值
stack(const stack &stk); //拷贝构造函数
stack& operator=(const stack &stk); //重载等号操作符
stack<int>sta;
sta.push(9);
stack<int> sta1(sta); //调用拷贝构造函数
stack<int> sta2;
sta2=sta; //等号重载赋值
stack<int> sta3=sta; //调用拷贝构造函数,只有这条语句执行会调用构造函数,此处为调用拷贝构造函数而不是用等号重载赋值
stack逐一访问每个栈顶元素
stack<int> sta;
sta.push(1);
sta.push(2);
sta.push(3);
while(!sta.empty())
{
cout<<sta.top()<<endl; //3 2 1
sta.pop();
}
stack方法
stack<int> sta;
sta.push(elem); //往栈顶添加元素
sta.pop(); //从栈顶移除第一个元素,pop的函数并没有返回值
sta.top(); //访问栈顶的元素值
sta.empty(); //栈是否为空,为空返回true
sta.size(); //返回栈中元素的存储个数
应用例子:
stack<int> sta;
sta.push(1);
sta.push(2);
sta.push(3);
sta.emplace(4);
stack<int> sta1;
sta1.push(5);
sta1.push(6);
sta.swap(sta1);
cout <<"sta.size() is " << sta.size() << endl;
cout << "sta1.size() is " << sta1.size() << endl;
while (!sta1.empty())
{
cout << sta1.top() << endl; //3 2 1
sta1.pop();
}
queue容器
queue容器简介
queue为队列容器,是一种先进先出的容器
#include<queue>
queue对象的默认构造和拷贝构造和赋值
模板类,queue<T> q;
都与上面的一模一样
queue方法
queue<int> q;
q.push(elem); //进队列
q.pop(); //出队列
q.front(); //返回队列的第一个元素
q.back(); //返回队列的最末尾的元素
q.empty();
q.size();
priority_queue
使用时包含头文件 #include < queue > 和queue不同,它可以定义其中数据的优先级,功能相当于构建了一个大顶堆(由队头到队尾为降序排列)或小顶堆(默认为这个)(由队头到队尾为升序排列)。
定义:priority_queue<Type, Container, Functional>
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式,当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆
普通数据类型例子:
priority_queue<int> q; //等于priority_queue<int,vector<int>,less<int>> q;
q.emplace(6);
q.emplace(1);
q.emplace(3);
q.emplace(2);
q.emplace(4);
q.emplace(5);
while (!q.empty())
{
cout << q.top() << endl; //队头元素开始 顺序为654321
q.pop();
}
priority_queue<int, vector<int>, greater<int>> q; //如果greater为less的话则为654321
q.emplace(6);
q.emplace(1);
q.emplace(3);
q.emplace(2);
q.emplace(4);
q.emplace(5);
while (!q.empty())
{
cout << q.top() << endl; //队头元素开始 顺序为123456
q.pop();
}
与queue不同的是,不能使用front()访问队头元素,而应该使用top()
struct node {
int id;
float x;
};
bool operator >=(const node& a, const node& b) {
if (a.id!=b.id)
{
return a.id >= b.id;
}
else
{
return a.x >= b.x;
}
}
int main()
{
priority_queue<node, vector<node>,greater_equal<node>> q;
vector<node> vecNode;
vecNode.push_back({ 2,-0.2f });
vecNode.push_back({ 1,-0.1f });
vecNode.push_back({ 4,-0.4f });
vecNode.push_back({ 3,-0.3f });
vecNode.push_back({ 3,-0.5f });
vecNode.push_back({ -1,-0.5f });
for (int i = 0; i < vecNode.size(); i++)
{
cout << vecNode[i].id << " " << vecNode[i].x << endl;
//2 -0.2
//1 -0.1
//4 -0.4
//3 -0.3
//3 -0.5
//-1 -0.5
q.push(vecNode[i]);
}
cout << "aaa" << endl;
while (!q.empty())
{
cout << q.top().id << " " << q.top().x << endl;
//-1 -0.5
//1 -0.1
//2 -0.2
//3 -0.5
//3 -0.3
//4 -0.4
q.pop();
}
Set和Multiset容器(关联式容器)
序列式容器:元素位置和插入元素的时机有关系,什么时候插入元素决定了元素在什么位置,位置和本身值的大小没有关系
关联式容器:元素位置和元素值有关,和插入元素的时机没有关系
set和multiset简介
set容器主要是为了实现去重的功能,默认为升序排序(从小到大)
set是一个集合容器,其中所包含的元素是唯一的,集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置
set采用红黑树变体的数据结构实现,红黑树属于平衡二叉树,在插入操作和删除操作上(Olog2 n)比vector(O(n))快
set不可以直接存取元素(不可以使用at.(pos)和[]操作符)
multiset和set区别:set支持唯一键值,每个元素值只能出现一次,而multiset中同一值可以出现多次
不可以直接修改set或multiset容器中的元素值,因为该类容器是自动排序的,如果希望修改一个元素值,必须先删除原有的元素,再插入新的元素
#include <set>
默认构造,拷贝构造和赋值和上面一样
template < class T, // 键 key 和值 value 的类型
class Compare = less<T>, // 指定 set 容器内部的排序规则
class Alloc = allocator<T> // 指定分配器对象的类型
> class set;
set方法
set<int> s;
s.insert(elem); //在容器中插入元素
s.begin(); //返回容器中第一个数据的迭代器
s.end(); //返回容器中最后一个数据的迭代器
s.size(); //容器中元素的数目
s.empty(); //是否为空
s.clear(); //清除所有元素
s.erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器(一般用来删除最小值和最大值,注意:删除最大值时正向迭代器等于end()后需要--,反向迭代器rbegin()没有erase(pos)函数)
s.erase(beg,end); //删除区间[beg,end)的所有元素
s.erase(elem); //删除容器中值为elem的元素,没有该元素返回0,成功返回1
s.find(elem); //查找elem元素,返回指向该元素的迭代器,如果不在其容器内迭代器返回end()
s.count(elem); //返回容器中值为elem的元素个数,对于set来说不是0就是1,对于Multiset来说,值可能大于1
s.lower_bound(elem); //返回第一个排在elem元素下面或值相等的元素的迭代器,elem可以为不在容器中的元素(注意:greater(9 5 3 1)里查找4的话返回的是3,less(1 3 5 9)查找4的话返回是5),重写了比较方法则看比较方法里的,如果elem比所有的都小则会报错
s.upper_bound(elem); //返回第一个排在elem元素下面的元素的迭代器,如果elem比所有的都小则会报错
s.equal_range(elem); //返回容器中与elem相等的上下限的两个迭代器,上限是闭区间,下限是开区间,如[beg,end), 简单理解为返回s.lower_bound(elem)和s.upper_bound(elem),如果没有还排在elem元素往下则返回end(),如果elem比所有的都小则会报错
set<int, greater<int>> s;
s.insert(1);
s.insert(3);
s.insert(5);
s.insert(7);
s.insert(9);
for (auto i =s.begin(); i != s.end(); i++)
{
cout << *i << endl;
}
pair<set<int>::iterator, set<int>::iterator> q = s.equal_range(10);
cout <<"aaa" << *(q.first) << " " << *(q.second) << endl;
//equal_range(4)时3 3,equal_range(5)时5 3,equal_range(10)时9 9
以下为自定义数据类型
struct Student
{
public:
//Student(int a, string b) { number = a; name = b; }
int number;
string name;
};
class StudentFun
{
public:
bool operator()(const Student &s1,const Student &s2) const
{
if (s1.number==s2.number)
{
return s1.name < s2.name;
}
return s1.number < s2.number;
}
};
int main()
{
//set<int> s; //等价于set<int,less<int>> s;
set<Student, StudentFun> s;
s.insert({ 3,"z" });
s.insert({ 1,"h" });
s.insert({ 4,"a" });
s.insert({ 1,"n" });
s.insert({ 5,"g" });
s.insert({ 9,"h" });
s.emplace(Student{ 3,"c" });
for (set<Student>::iterator it = s.begin(); it != s.end(); it++)
{
cout << (*it).number<<" "<<(*it).name << endl; //1h 1n 3c 3z 4a 5g 9h
}
struct Student
{
public:
//Student(int a, string b) { number = a; name = b; }
int number;
string name;
bool operator<(const Student& s1) const
{
if (s1.number==number)
{
return name < s1.name;
}
return number < s1.number;
}
};
int main()
{
set<Student> s;
s.insert({ 3,"z" });
s.insert({ 1,"h" });
s.insert({ 4,"a" });
s.insert({ 1,"n" });
s.insert({ 5,"g" });
s.insert({ 9,"h" });
s.emplace(Student{ 3,"c" });
for (set<Student>::iterator it = s.begin(); it != s.end(); it++)
{
cout << (*it).number<<" "<<(*it).name << endl; //1h 1n 3c 3z 4a 5g 9h
}
map容器
map简介
map是标准的关联式容器,一个map是一个键值对序列,即(key,value)对。
它提供基于key的快速检索能力。
map中key值是唯一的。集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。
map的具体实现采用红黑树变体的平衡二叉树的数据结构。在插入操作和删除操作上比vector快。
map可以直接存取key所对应的value,支持[]操作符,如map[key]=value(将key键所对应的值修改为value)
multimap与map的区别:map支持唯一键值,每个键只能出现一次;而multimap中相同键可以出现多次。multimap不支持[]操作符
map/multimap容器对象的默认构造
模板类实现,对象的默认构造形式
map<T1,T2> mapTT;
map<int,int> mapA;
#include <iostream>
#include <set>
#include <map>
using namespace std;
struct Student
{
public:
Student(int a, const char* chars) { number = a; name = chars; };
int number;
string name;
bool operator<(const Student& s1) const //可以在这里面定义需要的方法
{
if (s1.number == number)
{
return name < s1.name;
}
return number < s1.number;
}
};
int main()
{
map<int,string> m;
Student s(3, "z");
m.insert(map<int, string>::value_type(s.number, s.name));
m.insert(pair<int, string>(1, "h"));
m.insert(pair<int, string>(4, "a"));
m.insert(pair<int, string>(1, "n"));
m.insert(pair<int, string>(5, "g"));
m.insert(pair<int, string>(9, "h"));
m[3] = "a"; //下标方式如果存在则会覆盖,不存在则会插入
string a = m[3]; //该操作为将m[3]的值赋给a;a="a";
string stuName = m[10]; //因为m[10]不存在,所以该操作会自动在m中插入一个实例,键为10,值为初始值(空值)
for (map<int, string>::iterator it = m.begin(); it !=m.end(); ++it)
{
cout << (*it).first<< " " << (*it).second << endl; //1h 3a 4a 5g 9h 10
}
map<int, string>::iterator iElemFound = m.find(3);
cout << (*iElemFound).first << " " << (*iElemFound).second << endl;
}
map方法
map容器的插入
map.insert(...); //往容器插入元素,返回pair
在map中插入元素的三种方式:
通过pair的方式插入对象:mapStu.insert(pair<int,string>(3,"小张") ); //其不会覆盖,想要覆盖则需要先删除后插入
通过value_type的方式插入对象:mapStu.insert( map<int,string>::value_type(1,"小李") );
通过数组的方式插入值:mapStu[3] = “小刘"; //3为键值,若3存在则修改值,若不存在则插入,即该种方式会覆盖
注意:string stuName=mapStu[1]; //只有当mapStu存在2时该操作为取键值为1的元素值,否则会自动插入一个实例,键为1,值为初始值(空值)
map容器对象获取键对应的值
使用[]
使用find()函数,成功返回对应的迭代器,失败返回end()的返回值
使用at()函数,键值不存在会抛出"out_of_range"异常
map容器的删除
map.clear(); //删除所有元素
map.erase(pos); //删除pos迭代器所指的元素,返回下一个元素的迭代器。
map.erase(beg,end); //删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
map.erase(keyElem); //删除容器中key为keyElem的对组。
map容器的查找
map.find(key); 查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();
map.count(keyElem); //返回容器中key为keyElem的对组个数。
map.lower_bound(elem); //返回第一个>=elem元素的迭代器。
map.upper_bound(elem); // 返回第一个>elem元素的迭代器。
map.equal_range(elem); //返回容器中与elem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)
总结
排序算法
Sort()
sort() 函数是基于快速排序实现的
sort() 函数受到底层实现方式的限制,它仅适用于普通数组和部分类型的容器。换句话说,只有普通数组和具备以下条件的容器,才能使用 sort() 函数:
sort() 只对 vector、deque 这 2 个容器提供支持
如果对容器中指定区域的元素做默认升序排序,则元素类型必须支持<小于运算符;同样,如果选用标准库提供的其它排序规则,元素类型也必须支持该规则底层实现所用的比较运算符
sort() 函数在实现排序时,需要交换容器中元素的存储位置。这种情况下,如果容器中存储的是自定义的类对象,则该类的内部必须拷贝构造函数和赋值运算符的重载。
sort() 排序是不稳定的
vector<int> v{1,3,5,7,9,2,4,6,8,10};
sort(v.begin(), v.end());
for (auto i = v.begin(); i != v.end(); i++)
{
cout << *i << endl; //1 2 3 4 5 6 7 8 9 10
}
stable_sort()排序算法
stable_sort() 函数是基于归并排序实现的
stable_sort() 是稳定的排序算法
stable_sort()函数与sort()函数的使用方法相同
partial_sort()排序函数
partial sort 可直译为“部分排序”,该函数可以从指定区域中提取出部分数据,并对它们进行排序
vector<int> v{1,3,5,7,9,2,4,6,8,10};
//sort(v.begin(), v.end());
//stable_sort(v.begin(), v.end());
partial_sort(v.begin(), v.begin()+v.size()-3, v.end());
for (auto i = v.begin(); i != v.end(); i++)
{
cout << *i << endl; //1 2 3 4 5 6 7 9 8 10
}
类似remove
partial_sort() 函数只适用于 array、vector、deque 这 3 个容器
当选用默认的升序排序规则时,容器中存储的元素类型必须支持小于运算符;同样,如果选用标准库提供的其它排序规则,元素类型也必须支持该规则底层实现所用的比较运算符;
partial_sort() 函数在实现过程中,需要交换某些元素的存储位置。因此,如果容器中存储的是自定义的类对象,则该类的内部必须提供移动构造函数和移动赋值运算符
merge()
功能:将两个已经排好序的序列合并为一个有序的序列
注意:使用的时候result,如果用的vector,必须先使用resize扩容
vector<int> v{1,3,5,7,9,2,4,6,8,10};
vector<int> v1{11,13,15,17,19,12,14,16,18,20};
vector<int> v2;
sort(v.begin(), v.end());
sort(v1.begin(), v1.end());
v2.resize(v.size()+v1.size());
vector<int>::iterator it = v2.begin();
merge(v.begin(), v.end(), v1.begin(), v1.end(), it);
for (auto i = v2.begin(); i != v2.end(); i++)
{
cout << *i << endl; //1到20
}
reverse()
函数参数:reverse(first,last)
功能:反转容器
string和vector和deque只能使用模板库算法里的反转函数
list可以使用算法里的和list类的reverse
stack和queue没有迭代器,自然不能使用算法里的reverse,其类也没有提供反转的成员函数
set和map的元素是按照键值排序的,不能修改键值,不可反转
查找算法
adjacent_find()
功能:在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的迭代器。
vector<int> v{1,3,5,7,9,2,4,6,8,10};
vector<int> v1{7,9,11,13,15,8,10,12,14,16};
vector<int> v2;
sort(v.begin(), v.end());
sort(v1.begin(), v1.end());
v2.resize(v.size()+v1.size());
vector<int>::iterator it = v2.begin();
merge(v.begin(), v.end(), v1.begin(), v1.end(), it);
for (auto i = v2.begin(); i != v2.end(); i++)
{
cout << *i << endl;
}
cout << "aaa" << *adjacent_find(v2.begin(), v2.end()); //7
binary_search()
功能:二分查找法,在有序序列中查找value,找到则返回true
cout << "aaa" << binary_search(v2.begin(), v2.end(),10); //1
count()
功能:利用==操作符,把标志范围内的元素与输入值比较,返回相等的个数
cout << "aaa" << count(v2.begin(), v2.end(), 7) << endl; //2
find()
find() 函数用于在指定范围内查找和目标元素值相等的第一个元素。
返回值:该函数会返回一个输入迭代器,当 find() 函数查找成功时,其指向的是在 [first, last) 区域内查找到的第一个目标元素;如果查找失败,则该迭代器的指向和 last 相同。
find() 函数的底层实现,其实就是用==运算符将 val 和 [first, last) 区域内的元素逐个进行比对。这也就意味着,[first, last) 区域内的元素必须支持==运算符。
find(first,last,toval) //查找[first,last)范围内,与toval等价的第一个元素,返回值为该元素的迭代器。如果没有这个元素,将返回last,注意:由于last如果为end()则值为空,所以如果没找到就直接报错
list<int> lst{1,2,3,4,6,7}
list<int>::iterator iElementFound;
iElementFound = find(lst.begin(), lst.end(), 6);
find_if()
功能:和 find() 函数相同,find_if() 函数也用于在指定区域内执行查找操作。不同的是,前者需要明确指定要查找的元素的值,而后者则允许自定义查找规则
struct Test
{
int a=7;
string b;
bool operator()(const int& t)
{
return t-1==a;
}
};
int main()
{
vector<int> v{ 1,2,3,4,5,6,7,7,8,8,9,9 };
cout << *find(v.begin(), v.end(), 7) << endl; //7
cout << *adjacent_find(v.begin(), v.end()) << endl; //7
cout << binary_search(v.begin(), v.end(), 6) << endl; //1
cout << count(v.begin(), v.end(), 7) << endl; //2
cout << *find_if(v.begin(), v.end(), Test()) << endl; //8
}
重载的是()符号!!!
search()
功能:search()函数用于在序列 A 中查找序列 B 第一次出现的位置。
vector<int> v{ 1,2,3,4,5,6,7,7,8,8,9,9 };
vector<int> v1{ 7,8,8,9 };
cout << *search(v.begin(), v.end(), v1.begin(), v1.end()) << endl; //7
remove()
stl中的remove()不会删除元素,总个数并没有改变,只是进行了位置的移动与元素的替换,其将全部不包含待删除元素的值按顺序移动到第零个...第一个...第二个...第x个(0<=x<=n,即可以完全没有该元素,也可以全部都是该元素),然后全部遍历完毕后,迭代器在x的下一个元素的位置,且从此位置开始到末尾都为原先的元素没有改变,若要真正移除,则需要搭配使用erase()使用
注意:如果其全都是该待删除元素,那么erase(remove(后该容器为空,size()为0,若全部都不是,那么将不会删除任一元素
例子:
list<int> l{ 4,1,2,3,4,4,5,7,4,9 }; //4123445749
list<int>::iterator iRemove;
iRemove = remove(l.begin(), l.end(), 4);
cout <<"iRemove is " << *iRemove << endl; //iRemove is 5
for (auto i = l.begin(); i != l.end(); i++)
{
cout << *i << endl; //412344 5749变到123579 5749
}
l.erase(iRemove, l.end());
cout << "After erase" << endl;
for (auto i0 = l.begin(); i0 != l.end(); i0++)
{
cout << *i0 << endl; //123579
}
使用erase(remove(不会存在迭代器失效的问题而且也方便写,上面只是为了展示,实际代码:
list<int> l{ 4,1,2,3,4,4,5,7,4,9 }; //4123445749
l.erase(remove(l.begin(), l.end(), 4), l.end()); //移除,不会存在迭代器失效的问题
cout << "After erase" << endl;
for (auto i0 = l.begin(); i0 != l.end(); i0++)
{
cout << *i0 << endl; //123579
}
哈希表
#include <iostream>
using namespace std;
#ifndef DefaultInt
#define DefaultInt -65535
#endif // !DefaultInt
class HashTable
{//通过开放定址法来解决冲突,除留余数法为散列函数的哈希表
private:
int* TableElem;
int count;
public:
HashTable() { count = 12; TableElem = new int[count]; for (int i = 0; i < count; i++) { TableElem[i] = DefaultInt; } };
HashTable(int size) { count = size; TableElem = new int[count]; for (int i = 0; i < count; i++) { TableElem[i] = DefaultInt; } };
~HashTable() { if (TableElem != nullptr) { delete[] TableElem; TableElem = nullptr; }count = 0; };
int Hash(int a) { return a % count; }; //散列函数
void InsertHash(int elem) { int a = Hash(elem); while (TableElem[a] != DefaultInt) { a = Hash(elem + 1); }; TableElem[a] = elem; };//插入数据
int SearchHash(int key); //哈希表查找
};
inline int HashTable::SearchHash(int key)
{
int a = Hash(key);
int b = a;
while (TableElem[a]!=key)
{
a = Hash(key + 1); b++;
if (TableElem[a] == DefaultInt || a == Hash(key))
{
cout << "not find" << endl;
return DefaultInt;
}
}
return b;
}
#include <iostream>
#include "HashTable.cpp"
#include <unordered_map>
using namespace std;
int main()
{
HashTable a(12);
unordered_multimap<int, string> b; //其会动态扩容,解决冲突的方法为链地址法,但相较于multimap不再保证其中的顺序
a.InsertHash(12); a.InsertHash(13); a.InsertHash(25);
b.insert(make_pair(3, "a")); b.insert(make_pair(2, "b")); b.insert(make_pair(4, "c"));
b.insert(make_pair(3, "d")); //键值相同,在unordered_map中此操作为无效操作,在这里为有效,其为键值3后面链表的元素
b.insert(make_pair(3, "d")); //有效
for (auto it = b.begin(); it != b.end(); ++it)
{
cout << (*it).first << " " << (*it).second<<" next ";
}
cout << endl;
pair<unordered_multimap<int, string>::iterator, unordered_multimap<int, string>::iterator> ElemFind = b.equal_range(3);//可以直接写为auto
for (auto it = ElemFind.first; it != ElemFind.second; ++it) {
cout << it->first << " -> " << it->second << endl; //输出指向键值为3的所有元素
}
auto ElemFind1 = b.find(3);
if (ElemFind1!=b.end())
{
while ((*ElemFind1).first==3)
{
cout << (*ElemFind1).first << "!" << (*ElemFind1).second << " ";
++ElemFind1;
}
cout << endl;
}
cout<<a.SearchHash(26)<<endl;
cout<<a.SearchHash(25)<<endl;
return 0;
}