容器主要分为:顺序容器包括vector、deque、list、forward_list、array、string;关联容器包括set、map两大类(multiset,multimap,unorder_map,unorder_set,unorder_multimap,unorder_multiset)。
关联容器和顺序容器有着根本的不同:
- 所有顺序容器都提供了快速顺序访问元素的能力;
- 关联容器中的元素是按关键字来保存和访问的。与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。
- 关联容器不支持顺序容器的位置相关的操作。原因是关联容器中元素是根据关键字存储的,这些操作对关联容器没有意义。而且,关联容器也不支持构造函数或插入操作这些接受一个元素值和一个数量值的操作。
vector
vector 是向量类型,可以容纳多个相同类型的数据,如若干个整数,所以称其为容器。使用前需加头文件< vector >
初始化
vector<int> a(10); //定义了10个整型元素的向量(尖括号中为元素类型名,它可以是任何合法的数据类型),但没有给出初值,其值是不确定的。
vector<int> a(10,1); //定义了10个整型元素的向量,且给出每个元素的初值为1
vector<int> a(b); //用b向量来创建a向量,整体复制性赋值
vector<int> a(b.begin(),b.begin+3); //定义了a值为b中第0个到第2个(共3个)元素
int b[7]={1,2,3,4,5,9,8};
vector<int> a(b,b+7); //从数组中获得初值
常用操作
//插入:
a.push_back(5); //注意:在尾部插入,而且不能写成push
a.insert(a.begin()+1,5); //在a的第1个元素(从第0个算起)的位置插入数值5,如a为1,2,3,4,插入元素后为1,5,2,3,4
a.insert(a.begin()+1,3,5); //在a的第1个元素(从第0个算起)的位置插入3个数,其值都为5
a.insert(a.begin()+1,b+3,b+6); //b为数组,在a的第1个元素(从第0个算起)的位置插入b的第3个元素到第5个元素(不包括b+6),如b为1,2,3,4,5,9,8插入元素后为1,4,5,9,8
//删除
a.pop_back();//删除最后一个元素
a.erase(a.begin()+1,a.begin()+3); //删除a中第1个(从第0个算起)到第2个元素,也就是说删除的元素从a.begin()+1算起(包括它)一直到a.begin()+ 3(不包括它)
a.clear(); //清空a中的元素
//赋值
a.assign(b.begin(), b.begin()+3); //b为向量,将b的0~2个元素构成的向量赋给a
a.assign(4,2); //是a只含4个元素,且每个元素为2
//交换
a.swap(b); //b为向量,将a中的元素和b中的元素进行整体性交换
a.reserve(a.begin(),a.end());//反转a
sort(a.begin(),a.end()); //对a中的从a.begin()(包括它)到a.end()(不包括它)的元素进行从小到大排列
copy(a.begin(),a.end(),b.begin()+1); //把a中的从a.begin()(包括它)到a.end()(不包括它)的元素复制到b中,从b.begin()+1的位置(包括它)开始复制,覆盖掉原有元素
find(a.begin(),a.end(),10); //在a中的从a.begin()(包括它)到a.end()(不包括它)的元素中查找10,若存在返回其在向量中的位置
//访问
a.back(); //返回a的最后一个元素
a.front(); //返回a的第一个元素
a[i]; //返回a的第i个元素,当且仅当a[i]存在
//大小操作
a.size(); //返回a中元素的个数;
a.capacity(); //返回a在内存中总共可以容纳的元素个数
a.resize(10); //将a的现有元素个数调至10个,多则删,少则补,其值随机
a.resize(10,2); //将a的现有元素个数调至10个,多则删,少则补,其值为2
注:1.vector类主要用的是向量,访问其中的元素可以用下标;但a[i]只能用于访问已有的数据,无法添加数据!
2.vector中没有push_front和pop_front方法,因为这是堆栈中使用的 堆栈用的是指针用这些操作访问很方便,而且这对于数组vector来说,容易造成内存碎片
stack
Stack(堆栈) 是一个容器类的改编,为程序员提供了堆栈的全部功能,实现了一个先进后出(FILO)的数据结构。
栈(stack)是限制插入和删除只能在一个位置上进行的线性表,该位置在表的末端,叫做栈顶。添加元素只能在尾节点后添加,删除元素只能删除尾节点,查看节点也只能查看尾节点。添加、删除、查看依次为入栈(push)、出栈(pop)、栈顶节点(top)。形象的说,栈是一个先进后出(LIFO)表,先进去的节点要等到后边进去的节点出来才能出来。引自
头文件#include <stack>
常用操作
stack<int> st;
st.empty();
st.pop();//移除栈顶元素
st.push();//压入元素
st.size();//返回栈中元素数目
st.top();//返回栈顶元素
存储结构
栈有两种是实现结构,一种是顺序存储结构,也就是利用数组实现:用一个下标top来标记表示栈顶,top==-1时,栈空,top==0时,表示栈里只有一个元素,通过访问top为下标的数组元素即可。出栈top自减,入栈top自加就OK了。
一种是链式存储结构,可以用单链表实现:通过在表的尾端插入来实现push,通过删除尾节点来实现pop,获取尾节点的元素来表示top。
set
引自
1.按关键字有序保存元素:set(关键字即值,即只保存关键字的容器);multiset(关键字可重复出现的set);
2.无序集合:unordered_set(用哈希函数组织的set);unordered_multiset(哈希组织的set,关键字可以重复出现)。
set就是关键字的简单集合。当只是想知道一个值是否存在时,set是最有用的。
在set中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。set中元素的值不能直接被改变。set内部采用的是一种非常高效的平衡检索二叉树:红黑树,也称为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树。
set具备的两个特点:
- set中的元素都是排序好的
- set中的元素都是唯一的,没有重复的
常用操作
//插入
insert()//在集合中插入元素
//返回值是pair<set<int>::iterator,bool>,bool标志着插入是否成功,而iterator代表插入的位置,若key_value已经在set中,则iterator表示的key_value在set中的位置。
inset(first,second);//将定位器first到second之间的元素插入到set中,返回值是void.
//查找计数
count(); // 返回某个值元素的个数
find()//返回第一个指向被查找到元素的迭代器,没找到则返回end()
size()//集合中元素的数目
swap()//交换两个集合变量
//删除
erase(iterator)//删除定位器iterator指向的值
erase(first,second) //删除定位器first和second之间的值
erase(key_value) //删除键值key_value的值
multiset和set唯一的区别是,允许多个相同的key值存在。
map
引自
map是STL的一个关联容器,它提供一对一的hash。
- 第一个可以称为关键字(key),每个关键字只能在map中出现一次;multimap允许多个相同key值存在
- 第二个可能称为该关键字的值(value);
Map主要用于资料一对一映射(one-to-one)的情況,map內
部的实现自建一颗红黑树,这颗树具有对数据自动排序的功能。默认按照key值从小到大排序。
构造
最常用map<int, string> mapStudent;
插入
// 第一种 用insert函數插入pair,最常用
mapStudent.insert(pair<int, string>(000, "student_zero"));
// 第二种 用insert函数插入value_type数据
mapStudent.insert(map<int, string>::value_type(001, "student_one"));
// 第三种 用"array"方式插入
mapStudent[123] = "student_first";
mapStudent[456] = "student_second";
以上三种用法,虽然都可以实现数据的插入,但是它们是有区别的,当然了第一种和第二种在效果上是完成一样的,用insert函数插入数据,在数据的 插入上涉及到集合的唯一性这个概念,即当map中有这个关键字时,insert操作是不能在插入数据的,但是用数组方式就不同了,它可以覆盖以前该关键字对应的值,用程序说明如下:
mapStudent.insert(map<int, string>::value_type (001, "student_one"));
mapStudent.insert(map<int, string>::value_type (001, "student_two"));
上面这两条语句执行后,map中001这个关键字对应的值是“student_one”,第二条语句并没有生效,那么这就涉及到我们怎么知道insert语句是否插入成功的问题了,可以用pair来获得是否插入成功,程序如下
// 构造定义,返回一个pair对象
pair<iterator,bool> insert (const value_type& val);
pair<map<int, string>::iterator, bool> Insert_Pair;
Insert_Pair = mapStudent.insert(map<int, string>::value_type (001, "student_one"));
if(!Insert_Pair.second)
cout << ""Error insert new element" << endl;
我们通过pair的第二个变量来知道是否插入成功,它的第一个变量返回的是一个map的迭代器,如果插入成功的话Insert_Pair.second应该是true的,否则为false。
查找
// find 返回迭代器指向当前查找元素的位置否则返回map::end()位置
iter = mapStudent.find("123");
if(iter != mapStudent.end())
cout<<"Find, the value is"<<iter->second<<endl;
else
cout<<"Do not Find"<<endl;
删除
//迭代器刪除
iter = mapStudent.find("123");
mapStudent.erase(iter);
//用关键字刪除
int n = mapStudent.erase("123"); //如果刪除了會返回1,否則返回0
//用迭代器范围刪除 : 把整个map清空
mapStudent.erase(mapStudent.begin(), mapStudent.end());
//等同于mapStudent.clear()
deque
deque 是一个优化的双端队列,由若干段连续空间串接而成,一旦有必要在deque的头部或尾端增加新的空间,便配置一段定量连续的空间,串接在deque的头部或尾端。它类似于矢量,但允许在前端和后端高效地访问值(和矢量一样,deque 也可以使用 [ ] 运算符访问元素)。它实际上是一个容器适配器。
图摘自
deque与vector的区别
- vector对于头部的插入删除效率低,成员函数是在尾部push_back() pop();
- 而deque由于是双端数组,相对而言对头部的插入删除速度比vector快;
- 但vector随机访问性和遍历性能比deque快,这和两者内部实现有关。
deque内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真实数据。中控器维护的是每个缓冲区的地址,使得使用deque时像一片连续的内存空间。
迭代器访问操作函数
d.begin();
d.end();
d.rbegin();
d.rend();
d.cbegin();
d.cend();
d.crbegin();
d.crend();
c代表常量const,r代表reverse逆操作。
构造和赋值方法
deque<int>d1;
//构造方法
for(int i=0; i < 10; i++)
{
d1.push_back(i);//1.利用插入构造
}
deque<int>d2(d1.begin(),d1.end());//2.迭代器构造
deque<int>d3(10,100);//3.重复值构造
deque<int>d4(d3);//4.利用已有容器构造
//赋值方法
d2 = d1;//1.//operator= 赋值
d3.assign(d1.begin(), d1.end());//2.assign 赋值
d4.assign(10,100);
常用成员函数
//插入
deque<int> de;
de.push_back();//尾插
de.push_front();//头插
de.insert(iterator,elem);//在该迭代器处插入元素elem
de.insert(iterator,n,elem);//在该迭代器处插入n个元素elem
de.insert(de.begin(),de2.begin(),de2.end());//在起始位置插入de2的某一段数据
//交换
de1.swap(de2);//交换两个容器的数据
//删除
de.pop_back();//尾删
de.pop_front();//头删
deque<int>::iterator it = de.begin();
it++;
de.erase(it);//删除数据,并返回指向下一个数据的迭代器
de.erase(de.begin(), de.end());//按区间方式删除
de.clear(); //清空容器
//容器大小操作
de.empty();//de空时返回真
de.size();//容器大小
d1.resize(num,elem);//重新指定大小为num,当大于原长度时用默认值填充,或者可以指定elem填充
//注意没有提供容量操作capacity()。除了at(),没有任何成员函数会检查索引或迭代器是否有效。
//元素的插入或删除可能导致内存重新分配,所以任何插入或删除动作都会使所有指向deque元素的指针、引用和迭代器失效。
//惟一例外的是在头部或尾部插入元素,操作之后,指针和引用仍然有效,但迭代器将失效。
//访问
de[i];//1.像矢量一样,可以使用[]访问元素
de.at(i);//2.at
de.front();//3.访问头元素
de.back();//4.访问尾元素
//排序
sort(de.begin(),de.end());
注:1.使用sort排序时需要包含algorithm头文件
2.所有支持随机访问迭代器的容器,都可以用sort来排序,例如vector
queue
C++STL库中的队列queue为一种容器适配器,是一种FIFO的数据结构。使用前需要添加相应的头文件< queue >。定义时需要指定元素类型和容器类型,元素类型是必要的;
而容器类型是可选的,可以建立在矢量、链表或者双端队列的基础上。默认情况下,它使用一个双端队列deque作为其基础。
和其他容器有很多相似的地方,常用的成员函数如下
queue<int> que;
que.front();//返回第一个元素
que.back();//返回最后一个元素
que.empty();//如果队列为空,则返回真
que.push();//在末尾加入一个元素
que.pop();//删除第一个元素
que.size();//返回队列中元素的个数
que.empty();//
//等
注:queue本质是限制了一端进另一端出的deque。所以,push默认对入队端;pop默认对出队一端操作,不分front和back。
priority_queue
引自
他和queue不同的就在于我们可以自定义其中数据的优先级, 让优先级高的排在队列前面,优先出队。优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。
定义:
priority_queue<Type, Container, Functional>
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式,当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆
//升序队列,小顶堆
priority_queue <int,vector<int>,greater<int> > q;
//降序队列,大顶堆
priority_queue <int,vector<int>,less<int> >q;
//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。
//其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)
1.基本类型优先队列
#include<iostream>
#include <queue>
using namespace std;
int main()
{
//对于基础类型 默认是大顶堆
priority_queue<int> a;
//等同于priority_queue<int, vector<int>, less<int> > a;
//这里一定要有空格,不然成了右移运算符↓
priority_queue<int, vector<int>, greater<int> > c; //这样就是小顶堆
priority_queue<string> b;
for (int i = 0; i < 5; i++)
{
a.push(i);
c.push(i);
}
while (!a.empty())
{
cout << a.top() << ' ';
a.pop();
}
cout << endl;
while (!c.empty())
{
cout << c.top() << ' ';
c.pop();
}
cout << endl;
b.push("abc");
b.push("abcd");
b.push("cbd");
while (!b.empty())
{
cout << b.top() << ' ';
b.pop();
}
cout << endl;
return 0;
}
输出
4 3 2 1 0
0 1 2 3 4
cbd abcd abc
2.pair优先队列
先比较第一个元素,第一个相等比较第二个
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main()
{
priority_queue<pair<int, int> > a;
pair<int, int> b(1, 2);
pair<int, int> c(1, 3);
pair<int, int> d(2, 5);
a.push(d);
a.push(c);
a.push(b);
while (!a.empty())
{
cout << a.top().first << ' ' << a.top().second << '\n';
a.pop();
}
}
输出
2 5
1 3
1 2
3.自定义类型
#include <iostream>
#include <queue>
using namespace std;
//方法1
struct tmp1 //运算符重载<
{
int x;
tmp1(int a) {x = a;}
bool operator<(const tmp1& a) const
{
return x < a.x; //大顶堆
}
};
//方法2
struct tmp2 //重写仿函数
{
bool operator() (tmp1 a, tmp1 b)
{
return a.x < b.x; //大顶堆
}
};
int main()
{
tmp1 a(1);
tmp1 b(2);
tmp1 c(3);
priority_queue<tmp1> d;
d.push(b);
d.push(c);
d.push(a);
while (!d.empty())
{
cout << d.top().x << '\n';
d.pop();
}
cout << endl;
priority_queue<tmp1, vector<tmp1>, tmp2> f;
f.push(c);
f.push(b);
f.push(a);
while (!f.empty())
{
cout << f.top().x << '\n';
f.pop();
}
}
输出
3
2
1
3
2
1