更多 STL 相关知识可看:http://c.biancheng.net/stl/
STL(Standard Template Library),即标准模板库,是一个具有工业强度的,高效的C++程序库。它被容纳于C++标准程序库(C++ Standard Library)中,是ANSI/ISO C++标准中最新的也是极具革命性的一部分。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性。
vector : 向量,是一个变长数组。使用vector需添加头文件< vector>
1.定义:
vector<typename> name;
如果 typename 也是一个STL容器,那么 >> 之间得加空格,不然有的编译器会认为是移位操作。如:
vector<vector<int> > name;
定义二维边长数组:
vector<typename> Arrayname[size];
这样 Arrayname[0] ~ Arrayname[size - 1] 中的每一个都是一个 vector 容器。
vector 初始化:
1)指定大小初始化,初始化后仍可用 push_back
方法增加元素:
vector<int> v(6); //容器的 size 初始化为 6
2)指定大小且初始化容器内的元素:
vector<int> v(6,3); //容器内初始化 6 个 元素 3
vector<int> v = {1,2,3,4,5,6}; //初始化 6 个元素
3)用已存在的 vector
初始化新的 vector
容器:
vector<int> v1 = {1,2,3};
vector<int> v2(v1);
4)二维数组的初始化:
vector<int> v(4,4);
vector<vector<int> > v2(4,v);
2.vector 容器内的元素访问
1)通过下标访问,与数组相同
2)通过迭代器访问
vector<typename>::iterator it;
这样就得到了迭代器 it ,并且可以通过 *it 来访问 vector 内的元素
如:
vector<int> vi;
vector<int>::iterator it = vi.begin(); //vi.begin()为取 vi 的首地址,而 it 指向这个地址
for(int i = 0; i < 5; i++){
printf("%d",*(it + i));
}
//vi[i] 和 *(vi.begin() + i) 是等价的
与 begin() 函数不同, end() 函数是取尾元素地址的下一个地址。此外,迭代器还实现了两种自加操作:++it 和 it++
于是还有另一种遍历方法:
for(vector<int>::iterator it = vi.begin(); it != vi.end(); it++){
printf("%d",*it);
}
在常用STL容器中,只有在 vector 和 string 中,才允许使用 vi.begin() + 3 这种迭代器加上整数的方法。
3.其他常用方法:
1 ) push_back()
push_back(x); //就是在 vector 后面添加一个元素 x 。
vi = i.push_back(x);
2 ) pop_back()
pop_back(); //就是用以删除 vector 的尾元素。
vi.pop_back();
3 ) size()
//用于获取 vector 内的元素个数
vi.size();
4 ) clear()
//用于清空 vector 中的所有元素
vi.clear();
5 ) insert()
&esmsp;insert
函数可用于插入一个数,也可用于合并两个 vector
向 vector
中插入一个数:
insert(it,x); //用来向 vector 的任意迭代器 it 处插入一个元素 x
//v1 : [1,2]
vi.insert(vi.begin() + 2, 3); //将1插入 vi[2] 的位置 --> [1,2,3]
合并 vector
:
//v1 : [1,2,3] v2 : [3,4]
v1.insert(v1.end(),v2.begin(),v2.end()); //--> [1,2,3,4,5]
6 ) erase()
erase() 有两种用法:删除单个元素、删除一个区间内的所有元素。
//删除一个元素:
vi.erase(vi.begin() + 3); // 删除vi[3]
//删除一个区间
erase(first,last);
vi.erase(vi.begin() + 1, vi.begin() + 4);
set是集合,是一个内部自动有序且不含重复元素的容器
要使用 set ,需添加 set 头文件 < set>
1.定义
set<typename> name;
set 的数组定义与 vector 相同
set<typename> Arrayname[size];
这样 Arrayname[0] ~ Arrayname[size - 1] 中的每一个都是一个set 容器。
2.set 容器内的元素访问
set 只能通过迭代器 iterator 访问
set<typename>::iterator it;
set 不支持 *(it + i) 的访问方式,因此只能按如下方式枚举
set<int> st;
for(set<int>::iterator it = st.begin(); it != st.end(); it++){
printf("%d",*it);
}
3.常用方法
1)insert()
insert(x); //可将 x 插入 set 容器中,并自动递增和去重,时间复杂度为O(logN)
st.insert(x);
2)find()
find(value); //返回 set 中对应值为 value 的迭代器,时间复杂度为O(logN)
set<int>::iterator it = st.find(x);
printf("%d",*it);
set
查找失败时会返回 set::end()
的迭代器
3)erase()
erase()
有两种用法:删除单个元素、删除一个区间内的所有元素。
//删除一个元素:
st.erase(st.find(x));
//删除一个区间
erase(first,last);
set<int>::iterator it = st.find(30);
st.erase(it, st.end());
4)size()
//用于清空 set 中的所有元素
st.clear();
5)clear()
//用于清空 set 中的所有元素
st.clear();
set 最主要的作用是自动去重并按升序排序,因此碰到需要去重但是不方便直接开数组的情况,可以尝试用 set 解决。
需添加 string 头文件 < string>
定义:
string str;
str = "abcd";
可像字符数组一样访问 string
for(int i = 0; i < str.length(); i++){
printf("%c",str[i]);
}
如果要读入和输出整个字符串,则只能用 cin 和 cout
cin 和 cout 在 iostream 头文件中
若真要用 printf 来输出 string 类型,可用 c_str() 将 string 类型转化为字符数组进行输出
printf("%s",str.c_str());
1.通过迭代器访问
由于 string 不像其他 STL 容器那样需要参数,因此可以直接如下定义:
string::iterator it;
for(string::iterator it = str.begin(); it != str.end(); it++){
printf("%c",*it);
}
string 与 vector 一样,支持直接对迭代器进行加减某个数字,如 str.begin() + 3;
2.operator +=
这是 string 的加法,可以将两个 string 直接拼接起来:
str3 = str2 + str1;
str1 += str2;
3. compare operator
两个 string 类型可以直接使用 ==、!=、<、<=、>、>= 比较大小,比较规则是字典顺序。
4.常用函数
1)length()、size()
都是返回 str 的长度。
str.length()、str.size();
2)insert()
insert(pos,string):在 pos 号位置插入字符串 string
insert(it,it1,it2):it 为原字符串的欲插入位置,it2 和 it3 为待插字符的首尾迭代器,用来表示串 [it2,it3) 将被插在 it 的位置上。
str.insert(str.begin() + 3, str2.begin(),str2.end());
//str 中 三号位将被挤到后面去
3)erase()
erase() 有两种用法:删除单个元素、删除一个区间内的所有元素。
//删除一个元素:
str.erase(it);
str.erase(str.begin() + 3);
//删除一个区间
erase[first,last);
str.erase(str.brgin() + 2, str.end() - 1);
或
str.erase(pos,length)
//其中 pos 为需要开始删除的位置,length 为删除的字符个数。
如:string str = "abcdefg";
str.erase(3,2);
//结果:abcfg
4)clear()
str.clear();
5)substr()
substr(pos,len); //返回从 pos 号位开始,长度为 len 的子串
string s = "abcdefg";
cout << s.substr(0,4) << endl; //--> "abcd"
6)string::npos
string::npos 是一个常数,其本身值为 -1,但由于是 unsigned_int 类型,因此实际上也可以认为是 unsigned_int 类型的最大值。string::npos 用以作为 find 函数匹配失败时候的返回值,可以认为 string::npos 等于 -1 或 4294967295。
if(string::npos == -1){
···
}
if(string::npos == 4294967295){
···
}
7)find()
str.find(str2),当 str2 是 str 的子串时,返回其在 str 中第一次出现的位置,如果 str2 不是 str 的子串,那么返回 string::npos。
str.find(str2,pos); //从 str 的 pos 号位置开始匹配 str2 ,返回值与上相同。
8)replace()
str.replace(pos,len,str2); //把 str 从 pos 号位置开始,长度为 len 的子串替换为 str2。
str.replace(it1,it2,str2); //把 str 的迭代器 [it1,it2) 范围的子串替换为 str2。
map 翻译为映射,可以将任何基本类型(包括 STL 容器)映射到任何基本类型(包括 STL 容器)。
需要添加头文件< map>
1.定义
map<typename, typename> mp; //(键-值)对,前一个是键的类型后一个是值类型
如果是字符串到整型的映射,必须使用 string 而不能使用 char 数组。(可以字符
到整型的映射)
map<string, int> mp;
也可将一个 set 容器映射到一个字符串:
map<set<int>, string> mp;
2.map 容器内的元素访问
通过下标访问
map<char,int> mp;
mp['c'] = 20;
mp['c'] = 30; //20被覆盖
printf("%d",mp['c']);
通过迭代器访问,map 能用 it -> first 来访问键,用 it -> second 来访问值。
map<typename,typename>::iterator it;
for(map<char,int>::iterator it = mp.begin(); it != mp.end(); it++){
printf("%c %d",it -> first,it -> second);
}
map 会以键从小到大的顺序自动排序,这是由于 map 内部是使用红黑树实现的(set)也是,在建立映射的过程中会自动实现从小到大的排序功能。
3.常用方法
1)find()
find(key); //返回键为 key 的映射的迭代器。
map<char,int>::iterator it = mp.find('c');
当 find 找不到时,返回 mp.end() 的迭代器
2)erase()
erase() 有两种用法:删除单个元素、删除一个区间内的所有元素。
删除一个元素:
map<char,int>::iterator it = mp.find('b');
mp.erase(it); //it 为需删除元素的迭代器
或
mp.erase(key); //key 为欲删除的映射的键
删除一个区间
erase(first,last); //first 为要删区间的起始迭代器,last 为要删区间末尾下一个地址
map<char,int>::iterator it = mp.find('b');
mp.erase(it,mp.end());
3)size()
用来获取 map 中映射的对数
4)clear()
用来清空 map 中的所有元素
4.map 常见用途
①需要建立字符(字符串)与整数之间的映射的题目,使用 map 可减少代码量。
②判断大整数或者其他类型数据是否存在的题目时,可以把 map 当 bool 数组用。
③字符串和字符串的映射也有可能会遇到。
map 中的键和值是唯一的,而如果一个键需要对应多个值,就只能用 multimap
C++ 11 标准中还增加了 unorder_map,以散列代替 map 内部的红黑树实现,可以用来
处理需要映射而不按 key 来排序的需求。
队列,是实现了一个先进先出顺序的容器。需添加头文件 < queue>
定义:
queue<typename> name;
1.queue 容器中的元素访问
在STL中只能通过 front() 来访问队首元素,或是通过 back() 来访问队尾元素。
2.常用方法
1)push()
push(x) 将 x 进行入队
2)front()、back()
3)pop()
令队首元素出队
4)empty()
检测 queue 是否为空,返回 true 则空,返回 false 则非空
5)size()
返回 queue 内元素个数。
使用STL的 queue 时,元素入队的 push 操作只是制造了该元素的一个副本入队,因此在入队后对原元素的修改不会影响队列中的副本,而对队列中的副本修改也不会改变原元素。
优先队列,其底层是用堆来实现的。队首一定是当前队列中优先级最高的那一个。
需添加 < queue> 头文件
定义:
priority_queue<typename> name;
1.piority_queue 容器内的元素访问
优先队列没有 front() 函数,也没有 back() 函数,而只能通过 top() 函数来访问队首元素(堆顶元素),也就是优先级最高的元素。
priority_queue<int> q;
printf("%d",q.top());
2.常用函数
1)push()
push(x) 将 x 进行入队
2)top()
top() 可获得队首元素。
3)pop()
pop() 令队首元素出队
4)empty()
检测优先队列是否为空,返回 true 则空,返回 false 则非空。
5)size()
返回 queue 内元素个数。
3.优先队列内元素优先级的设置
1)基本数据类型的优先级设置
优先队列对基本数据类型的优先级设置一般是数字大的优先级高或按字典顺序大的优先
级高。
对基本数据类型来说,下面两种定义是等价的:
priority_queue<int> q;
priority_queue<int, vector<int>,less<int> > q;
其中,vector< int> 是用来承载底层数据结构堆的容器,而第三个参数, less< int> 则是对第一个参数的比较类,less< int> 表示数字大的优先级越大,而 greater< int> 表示数字小的优先级越大。
2)结构体优先级的设置
举一个水果的例子:
struct fruit{
string name;
int price;
};
若希望按水果的价格高的为优先级高,就需要重载小于号 “<”。
struct fruit{
string name;
int price;
friend bool operator < (fruit f1,fruit f2){
return f1.price < f2.price;
}
};
重载大于号会编译错误,因为从数学上来说只需要重载小于号,即 f1 > f2 等价于判断 f2 < f1 ,而 f1 == f2 等价于判断 !(f1 < f2) && !(f2 < f1)
如果希望按水果价格低的为优先级高,则将 return 中的小于号改为大于号即可
struct fruit{
string name;
int price;
friend bool operator < (fruit f1,fruit f2){
return f1.price > f2.price;
}
};
示例:
priority_queue<fruit> q;
fruit f1,f2,f3;
q.push(f1);
q.push(f2);
q.push(f3);
cout << q.top().name << q.top().price;
优先队列的重写函数与 sort() 中的 cmp 的效果是相反的。其原因在于优先队列本身的默认规则就是优先级高的放在队首。
也可将比较函数放在结构体外面:
struct cmp{
bool operator () (fruit f1, fruit f2){
return f1.price > f2.price;
}
};
这种情况下,就该按如下方式定义优先队列
priority_queue<fruit,vector<fruit>, cmp> q;
如果结构体内的数据较为庞大(如出现了字符串或者数组),建议使用引用来提高效率,此时比较类的参数中需要加上 “const” 和 “&”。
friend bool operator < (const fruit &f1,const fruit &f2){
return f1.price > f2.price;
}
friend bool operator < (const fruit &f1,const fruit &f2){
return f1.price < f2.price;
}
一些基本数据结构或是其他 STL 容器(例如 set )也能通过这种方式来定义优先级
栈,后进先出的容器
定义:
stack<typename> name;
只能通过 top() 来访问栈顶元素
常用函数:
1)push()
push(x);
2)top()
3)pop()
弹出栈顶元素
4)empty()
检测栈是否为空,返回 true 则空,返回 false 则非空。
5)size()
返回栈内元素个数
STL 中,没有实现栈的清空,所以如果需要实现栈的清空,可以用一个 while 循环实现
while( !st.empty()){
st.pop();
}
当想要将两个元素绑在一起作为一个合成元素,又不想要因此定义一个结构体时,就能使用 pair 作为一个代替品。即 pair 可看做一个内部有两个元素的结构体,且这两个元素的类型是可以指定的.
如:
struct pair{
typename1 first;
typename2 second;
};
1.定义
pair<typename1,typename2> name;
初始化:
pair<string,int> p ("abc",4);
若想在代码中临时构建一个 pair ,有如下两种方法:
1)将类型定义写在前面,后面用小括号内两个元素的方式
pair<string,int> ("abc",3);
2)使用自带的 make_pair 函数
make_pair("abc",4);
2. pair 中的元素访问
pair 中只有两个元素,分别是 first 和 second,只需按正常结构体的方式去访问即可
pair<string,int> p;
p.first = "abc";
p.second = 4;
p = make_pair("qqq",12);
p = pair<string,int>("aaa",33);
3.比较操作数
两个 pair 类型数据可以直接使用 ==、!=、<、<=、>、>= 比较大小,比较规则是先以 first 的大小作为标准,只有当 first 相等时才去判别 second 的大小。
4.常见用途
1)用来代替二元结构体及其构造函数
2)作为 map 的键值对来进行插入。
map<string,int> mp;
mp.insert(pair<string,int>("abc",5));
该头文件中包含的重要函数有:
char nzBuf[50] = "Hello world!";
string str = "abcdadddd";
int nLen = count(nzBuf, nzBuf+strlen(nzBuf),'l'); //结果:3
int nLen_new = count(str.begin(),str.end(),'a');//结果:2
char nzBuf[50] = "Hello world!";
string str = "abcddddd";
char* nzRet = find(nzBuf,nzBuf+49, 'o'); //结果nzRet为"o world!指向'o'起头的字符串;
string::iterator a = find(str.begin(),str.end(), 'c');//结果:"cddddd"
int aa =str.find('c'); //string类自带函数方法,可以返回索引位置,结果为2
char nzBuf[50] = "Hello world!";
char nzBuf1[50] = "wo";
char* nRel = search(nzBuf,nzBuf+strlen(nzBuf),nzBuf1,nzBuf1+strlen(nzBuf1));//结果:"world!"
char nzBuf[50] = "Hello world!";
char nzBuf1[50] = "abcdefg";
swap(nzBuf,nzBuf1); //字符数组存储发生交换
char nzBuf[50] = "Hello world!";
reverse(nzBuf,nzBuf+strlen(nzBuf)); //结果为“!dlrow olleH”
sort(首地址元素,尾地址元素的下一个地址,比较函数(选填));
排序功能,无需多说,用处很广。但是单独默认参数下的排序是升序,而添加一个返回bool类型的函数如下cmp才可以实现降序,函数名或形参名可以任意更换,主要记住函数完成的功能是返回前一个参数大于后一个参数的判断值。而小于则是升序的排列。
对基本数据类型:
bool cmp(int a,int b)
{
return a>b; //可以理解为当 a > b 时,把 a 放在 b前面
}
int nData[10] = {1,3,4,2,5,8,1,2,10,2};
sort(nData,nData+10); //升序排列
sort(nData,nData+10,cmp);//降序排列
对结构体:
struct node{
int x,y;
}ssd[10];
bool cmp(node a,node b){ //按 x 的值从大到小对结构体数组排序
return a.x > b.x;
}
bool cmp(node a,node b){ //先按 x 从大到小排序,若 x 相等,则按照 y 从小到大排序
if(a.x != b.x){
return a.x > b.x;
}else{
return a.y < b.y;
}
}
sort(ssd,ssd + 10,cmp);
对于容器:STL标准容器中,只有 vector、string、deque 是可以使用 sort 的
//以 vector 为例
vector<int> vi;
sort(vi.begin(),vi.end(),cmp);
//以 string 为例
string str[3] = {"aaa","bbb","ccc"};
sort(str,str + 3,cmp);
max是返回两个元素中值最大的元素,max_element是返回给定范围中值最大的元素。min是返回两个元素中值最小的元素,而min_element是返回给定范围中值最小的元素。注意两者之间的区别,一个是两个数比较,而另一个是多个值之间比较。
如果想要返回三个数 x、y、z的最大值,可以使用 max(x,max(x,z));
int nData[10] = {1,3,4,2,5,8,1,2,10,2};
int* n = max_element(nData,nData+10);//结果:10
int nn = max(1,5); //结果:5
int* m = min_element(nData,nData+10);//结果:1
int mm = min(nData[0],nData[2]); //结果:1(看清楚,只比较数组中第1个和第3个数,即4和1中寻找最小的数)
注意两点,第一点,该函数仅是处理元素连续重复的情况,而不是整个指定范围中重复的元素。所以如果想移除整个整个范围重复元素,先进行排序然后再调用该函数。
第二点,unique并不是真正的把重复的元素删除,其实是,该函数把重复的元素移到后面去了,然后依然保存到了原数组中、函数返回去重后最后一个元素的地址。
string str = "abcdddddbccccd";
string::iterator rel = unique(str.begin(),str.end()); //*rel="d"
for(string::iterator it = str.begin();it != rel; it++){
cout << *it << endl;
} //输出结果为:"abcdbcd"
next:给出一个序列在全排列中的下一个序列
prev:给出一个序列在全排列中的上一个序列
两者的功能返回分别为,给定范围中的元素组成的下一个按字典序的排列,以及返回给定范围中的元素组成的上一个按字典序的排列。 由上面的性质,我们可以根据数据字典排序做升序或者降序的全排列。
int arr[N] = {1,2,3,4}; //一定注意该处是最小排列情况,下面会详解原因
do
{
for(int i=0; i < N; i++)
printf("%d ",arr[i]);
putchar('\n');
}while(next_permutation(arr,arr+N)); //升序
函数输入之所以要求必须是一个升序的排列,原因在于函数运行一次对输入的数组进行移动排列一次后,在函数退出前判断移动后的数组是否升序,如果升序则函数返回布尔变量false,否则返回true。这样当你输入的是一个升序的排列后,每运行一次函数就对数组进行一次移动得到一个新的排列,函数对数组的移动排列采用递归方式。当所有排列方式都遍历一遍后函数最后一次输出的又是一个升序的排列,也就是和你最先输入的数组一样的排列。
所以要做到函数实现字典的全排列,先要将数据进行排序,初始状态一定要是数据内部是升序的情况,才能依次迭代而打印出所有的全排列的结果。
同样的道理,降序排序则先让数据初始状态属于降序的给过:
int arr[N] = {4,3,2,1};
do
{
for(int i=0; i < N; i++)
printf("%d ",arr[i]);
putchar('\n');
} while(prev_permutation(arr,arr+N)); //降序
和 memset 不同,这里的赋值可以是数字类型对应范围中的任意值。
int a[5] = {1,2,3,4,5};
fill(a,a + 5,233); //将 a[0] ~ a[4] 均赋值为233
lower_bound() 和 upper_bound()需要用在一个有序的数组或容器中
lower_bound(first,last,val) 用来寻找在数组或容器中的 [first,last) 范围内第一个值大于等于 val 的元素的位置,如果是数组,返回该位置的指针,如果是容器,返回该位置的迭代器。
upper_bound(first,last,val) 用来寻找在数组或容器中的 [first,last) 范围内第一个值大于 val 的元素的位置,如果是数组,返回该位置的指针,如果是容器,返回该位置的迭代器。
若数组或容器内没有需要寻找的元素,则 lower_bound() 和 upper_bound() 均返回可以插入该位置的指针或迭代器。
复杂度均为 O(log(last - first))。
int a[5] = {4,5,6,7,8};
//需用临时指针获得下标
int *pos = lower_bound(a,a + 5, 6);
cout << *pos << endl; //输出6
// 或直接减去首地址
cout << pos - a << endl; //输出2
int *pos1 = upper_bound(a,a + 5, 6);
cout << *pos1 << endl; //输出7
// 或直接减去首地址
cout << pos1 - a; //输出3