容器分为序列容器和关联容器。
序列容器,是因为元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置,元素就会位于什么位置。
关联容器,元素默认是由小到大排序好的,即便是插入元素,元素也会插入到适当位置。所以关联容器在查找时具有非常好的性能。
1. 序列容器
序列容器主要包含以下几种:
1. array<T,N>
2. vector
3. deque
4. list
5. forward_list
1.1 array<T,N>(数组容器)
该容器可以存储 N 个 T 类型的元素,是 C++ 本身提供的一种容器。此类容器一旦建立,其长度就是固定不变的,这意味着不能增加或删除元素,只能改变某个元素的值。
#include<iostream>
#include<array>
using namespace std;
int main() {
array<int, 10> myArr = {1,2,3,4,5};//固定大小为10
cout << "元素序列:";
for (auto i = 0; i < 10; ++i) {
cout << myArr[i] << " ";
}
cout << endl;
cout << "myArr.at(3) = " << myArr.at(3) << endl;//任意访问
cout << "myArr[3] = " << myArr[3] << endl;//任意访问
cout << "myArr.front() = " << myArr.front() << endl;//获取第一个元素
cout << "myArr.back() = " << myArr.back() << endl;//获取最后一个元素
cout << "myArr.data() = " << myArr.data() << endl;//获取第一个元素的指针
cout << "*myArr.data() = " << *myArr.data() << endl;//获取第一个元素的指针指向的元素
return 0;
}
1.2 vector(向量容器)
用来存放 T 类型的元素,是一个长度可变的序列容器,即在存储空间不足时,会自动申请更多的内存。使用此容器,在尾部增加或删除元素的效率最高(时间复杂度为 O(1) 常数阶),在其它位置插入或删除元素效率较差,因为它需要移动元素(时间复杂度为 O(n) 线性阶,其中 n 为容器中元素的个数)。
int main(int argc, const char * argv[]) {
//定义一个vector容器
vector<int> v1;
//插入元素(尾部插入)
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
//迭代器遍历打印
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
cout << *it << " ";
}
cout << endl;
//修改头部元素的值(front()返回是引用,可以当左值)
v1.front() = 44;
//输出头部元素
cout<< "头部元素:" << v1.front() << endl;
//修改尾部的值(back()返回是引用,可以当左值)
v1.back() = 99;
//输出尾部元素
cout << "尾部元素" << v1.back() <<endl;
//删除元素(从尾部删除)
v1.pop_back();
//迭代器遍历打印
for (vector<int>::iterator it = v1.begin(); it != v1.end(); it++) {
cout << *it << " ";
}
cout << endl;
v1.push_back(2);
v1.push_back(2);
vector<int>::iterator it = v1.begin();
while (it != v1.end()) {
if (*it == 2) {
it = v1.erase(it); //删除后,迭代器指针会执行下一个位置并返回。
}else{
it++;
}
}
return 0;
}
1.3 deque< T >(双端队列容器)
和 vector 非常相似,区别在于使用该容器不仅尾部插入和删除元素高效,在头部插入或删除元素也同样高效,时间复杂度都是 O(1) 常数阶,但是在容器中某一位置处插入或删除元素,时间复杂度为 O(n) 线性阶;
int main(int argc, const char * argv[]) {
//定义deque对象
deque<int> d1;
//尾部插入元素
d1.push_back(10);
d1.push_back(20);
d1.push_back(30);
//头部插入元素
d1.push_front(1);
d1.push_front(2);
d1.push_front(3);
//尾部删除元素
d1.pop_back();
//头部删除元素
d1.pop_front();
//修改头部和尾部的值
d1.front() = 111;
d1.back() = 222;
//查找元素为1的下标
//通过distance求取下标
deque<int>::iterator it = d1.begin();
while (it != d1.end()) {
if (*it == 1) {
cout << "下标:" << distance(d1.begin(), it) << endl;
}
it++;
}
//遍历
for (deque<int>::iterator it = d1.begin(); it != d1.end(); it++) {
cout << *it << " ";
}
cout << endl;
return 0;
}
1.4 list< T >(链表容器)
是一个长度可变的、由 T 类型元素组成的序列,它以双向链表的形式组织元素,在这个序列的任何地方都可以高效地增加或删除元素(时间复杂度都为常数阶 O(1)),但访问容器中任意元素的速度要比前三种容器慢,这是因为 list 必须从第一个元素或最后一个元素开始访问,需要沿着链表移动,直到到达想要的元素。
int main(int argc, const char * argv[]) {
//创建list对象
list<int> l;
//尾部添加元素
for (int i = 0; i < 10; i++) {
l.push_back(i);
}
//头部添加元素
l.push_front(111);
//遍历
for (list<int>::iterator it = l.begin(); it != l.end(); it++) {
cout << *it << " ";
}
cout << endl;
//list不能随机访问
list<int>::iterator it = l.begin();
it++;
it++;
cout << *it <<endl;
// it = it + 1; //编译报错,不能随机访问
// it = it + 5; //编译报错,不能随机访问
//删除头部元素
l.erase(l.begin());
//删除某个区间
it = l.begin();
it++;
it++;
it++;
l.erase(l.begin(), it);
//移除值为100的所有元素
l.remove(100);
return 0;
}
1.5 forward_list< T >(正向链表容器)
和 list 容器非常类似,只不过它以单链表的形式组织元素,它内部的元素只能从第一个元素开始访问,是一类比链表容器快、更节省内存的容器。
// forward_list constructors
#include <iostream>
#include <forward_list>
int main ()
{
// constructors used in the same order as described above:
std::forward_list<int> first; // default: empty
std::forward_list<int> second (3,77); // fill: 3 seventy-sevens
std::forward_list<int> third (second.begin(), second.end()); // range initialization
std::forward_list<int> fourth (third); // copy constructor
std::forward_list<int> sixth = {3, 52, 25, 90}; // initializer_list constructor
std::forward_list<int> mylist = { 34, 77, 16, 2 };
std::cout << "mylist contains:";
for ( auto it = mylist.begin(); it != mylist.end(); ++it )
std::cout << ' ' << *it;
std::forward_list<int> mylist = {10, 20, 30, 40, 50};
// 10 20 30 40 50
auto it = mylist.begin(); // ^
it = mylist.erase_after(it); // 10 30 40 50
// ^
it = mylist.erase_after(it,mylist.end()); // 10 30
// ^
std::cout << "mylist contains:";
for (int& x: mylist) std::cout << ' ' << x;
std::cout << '\n';
return 0;
}
2. 关联容器
关联容器主要包含以下几种:
set:排好序的集合,不允许有相同元素。
multiset:排好序的集合,允许有相同元素。
map:每个元素都分为关键字和值两部分,容器中的元素是按关键字排序的。不允许有多个元素的关键字相同。
multimap:和 map 类似,差别在于元素的关键字可以相同。
使用关联容器的目的也就在于快速查找。当一个元素被插入关联容器时,该元素会和已有的元素进行比较,最终被插入一个合适的位置。
关联容器底层实现为“红黑树”(RB-Tree),红黑树是平衡二叉树的一种,其有以下特点:
(1)所有左子树结点的值小于等于根节点的值,右子树节点的值大于根节点的值。
(2)没有一个节点深度过大。
通过上面,就可以知道,关联容器是平衡二叉树的具体应用,因为其内部是通过链表的方式组织,所以在插入的时候比vector要快,比list要慢;由于其底层是平衡二叉树,查找、插入、删除时间复杂度都应该为O(logN)。
在关联容器中查找元素和插入元素的时间复杂度都是 O(log(n))。从 begin() 到 end() 遍历整个关联容器,就是从小到大遍历整个容器。
关联容器内部的元素或关键字之间比较大小可以用<运算符,也可以用自定义的比较器。因为有序,所以在关联容器上进行查找的速度较快。
不能修改 set 或 multiset 容器中元素的值。因为元素被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。因此,如果要修改 set 或 multiset 容器中某个元素的值,正确的做法是先删除该元素,再插入新元素。同理,也不能修改 map 和 multimap 容器中元素的关键字
2.1 map/multimap
map容器提供一个键值对(key/value)容器,map与multimap差别仅仅在于multimap允许一个键对应多个值。需要包含头文件#include。对于迭代器来说,可以修改实值,而不能修改key。map会根据key自动排序。
multimap 有的成员函数,map 都有。此外,map 还有成员函数 operator[]。
#include <iostream>
#include <map> //使用map需要包含此头文件
using namespace std;
template <class T1,class T2>
ostream & operator <<(ostream & o,const pair<T1,T2> & p)
{ //将pair对象输出为 (first,second)形式
o << "(" << p.first << "," << p.second << ")";
return o;
}
template<class T>
void Print(T first,T last)
{//打印区间[first,last)
for( ; first != last; ++ first)
cout << * first << " ";
cout << endl;
}
typedef map<int,double,greater<int> > MYMAP; //此容器关键字是整型,元素按关键字从大到小排序
int main()
{
MYMAP mp;
mp.insert(MYMAP::value_type(15,2.7));
pair<MYMAP::iterator,bool> p = mp.insert(make_pair(15,99.3));
if(!p.second)
cout << * (p.first) << " already exists" << endl; //会输出
cout << "1) " << mp.count(15) << endl; //输出 1) 1
mp.insert(make_pair(20,9.3));
cout << "2) " << mp[40] << endl;//如果没有关键字为40的元素,则插入一个
cout << "3) ";Print(mp.begin(),mp.end());//输出:3) (40,0)(20,9.3)(15,2.7)
mp[15] = 6.28; //把关键字为15的元素值改成6.28
mp[17] = 3.14; //插入关键字为17的元素,并将其值设为3.14
cout << "4) ";Print(mp.begin(),mp.end());
return 0;
}
2.2 set/multiset
set的含义是集合,它是一个有序的容器,里面的元素都是排序好的支持插入、删除、查找等操作,就像一个集合一样,所有的操作都是严格在logn时间内完成,效率非常高。set和multiset的区别是:set插入的元素不能相同,但是multiset可以相同,set默认是自动排序的,使用方法类似list。
#include <iostream>
#include <set> //使用set须包含此文件
using namespace std;
int main()
{
typedef set<int>::iterator IT;
int a[5] = { 3,4,6,1,2 };
set<int> st(a,a+5); // st里是 1 2 3 4 6
pair< IT,bool> result;
result = st.insert(5); // st变成 1 2 3 4 5 6
if(result.second) //插入成功则输出被插入元素
cout << * result.first << " inserted" << endl; //输出: 5 inserted
if(st.insert(5).second)
cout << * result.first << endl;
else
cout << * result.first << " already exists" << endl;
//输出 5 already exists
pair<IT,IT> bounds = st.equal_range(4);
cout << * bounds.first << "," << * bounds.second ; //输出:4,5
return 0;
}