文章目录
(0)C++STL介绍
在 ACM 竞赛中,需要用到数组、字符串、队列、堆栈、链表、平衡二叉检索树等数据结构和排序、搜索等算法,以提高程序的时间、空间运行效率。这些数据结构,如果都需要手工来编写,那是相当麻烦的事情。
幸运的是,ANSI C++中包含了一个 C++ STL(Standard Template Library),即 C++标准模板库,又称 C++泛型库,它在 std 命名空间中定义了常用的数据结构和算法,使用起来十分方便。
(0)C++STL组件
STL 提供三种类型的组件:容器、迭代器和算法,它们都支持泛型程序设计标准。
-
容器主要有两类:顺序容器和关联容器。顺序容器(vector、list、deque 和 string等)是一系列元素的有序集合。关联容器(set multiset、map和multimap)包含查找元素的键值。
-
迭代器的作用是遍历容器。
-
STL 算法库包含四类算法:排序算法、不可变序算法、变序性算法和数值算法。
(一)Vector容器
vector 向量容器不但能像数组一样对元素进行随机访问,还能在尾部插入元素,是一种简单、高效的容器,完全可以代替数组。
值得注意的是,vector 具有内存自动管理的功能,对于元素的插入和删除,可动态调整所占的内存空间。
使用 vector 向量容器,需要头文件包含声明“#include ”。vector 文件在C:\Program Files\Microsoft Visual Studio\VC98\Include 文件夹中可以找到
vector 容器的下标是从 0 开始计数的,也就是说,如果 vector 容器的大小是 n,那么,元素的下标是 0~n-1。对于 vector 容器的容量定义,可以事先定义一个固定大小,事后,可以随时调整其大小;也可以事先不定义,随时使用 push_back()方法从尾部扩张元素,也可以使用 insert()在某个元素位置前插入新元素。
vector 容器有两个重要的方法,begin()和 end()。begin()返回的是首元素位置的迭代器;end()返回的是最后一个元素的下一元素位置的迭代器。
1)创建vector
创建 vector 对象常用的有三种形式。
(1)不指定容器的元素个数,如定义一个用来存储整型的容器:vector v;
(2)创建时,指定容器的大小,如定义一个用来存储 10 个 double 类型元素的向量容器:vector v(10);
注意,元素的下标为 0~9;另外,每个元素的值被初始化为 0.0。
(3)创建一个具有 n 个元素的向量容器对象,每个元素具有指定的初始值:vector v(10,8.6);
上述语句定义了 v 向量容器,共有 10 个元素,每个元素的值是 8.6。
2)尾部元素扩张
- 通常使用 push_back()对 vector 容器在尾部追加新元素。尾部追加元素,vector 容器会自动分配新内存空间。可对空的 vector 对象扩张,也可对已有元素的 vector 对象扩张。
- insert()方法可以在 vector 对象的任意位置前插入一个新的元素,同时,vector 自动扩张一个元素空间,插入位置后的所有元素依次向后挪动一个位置。
vector<int> v(3);
v[0]=2;
v[1]=7;
v[2]=9;
//在最前面插入新元素,元素值为 8
v.insert(v.begin(),8);
//在第 2 个元素前插入新元素 1
v.insert(v.begin()+2,1);
//在向量末尾追加新元素 3
v.insert(v.end(),3);
//定义迭代器变量
vector<int>::iterator it;
for(it=v.begin();it!=v.end();it++)
{
//输出迭代器上的元素值
cout<<*it<<" ";
}
3)访问 Vector元素
- 下标访问:
vector<int> v(3);
v[0]=2;
v[1]=7;
v[2]=9;
cout<<v[0]<<" "<<v[1]<<" "<<v[2]<<endl;
- 迭代器访问
常使用迭代器配合循环语句来对 vector 对象进行遍历访问,迭代器的类型一定要与它要遍历的 vector 对象的元素类型一致。
vector<int> v(3);
v[0]=2;
v[1]=7;
v[2]=9;
//定义迭代器变量
vector<int>::iterator it;
for(it=v.begin();it!=v.end();it++)
{
//输出迭代器上的元素值
cout<<*it<<" ";
}
//换行
cout<<endl;
return 0;
4)元素的删除
- erase()方法可以删除 vector 中迭代器所指的一个元素或一段区间中的所有元素。
- clear()方法则一次性删除 vector 中的所有元素。
vector<int> v(10);
//给向量赋值
for(int i=0;i<10;i++)
{
v[i]=i;
}
//删除 2 个元素,从 0 开始计数
v.erase(v.begin()+2);
//定义迭代器变量
vector<int>::iterator it;
for(it=v.begin();it!=v.end();it++)
{
//输出迭代器上的元素值
cout<<*it<<" ";
}
//换行
cout<<endl;
//删除迭代器第 1 到第 5 区间的所有元素
v.erase(v.begin()+1,v.begin()+5);
for(it=v.begin();it!=v.end();it++)
{
//输出迭代器上的元素值
cout<<*it<<" ";
}
//换行
cout<<endl;
//清空向量
v.clear();
//输出向量大小
cout<<v.size()<<endl;
5)元素的排序
- reverse 反向排列算法需要定义头文件“#include ”,algorithm 文件位于C:\Program Files\Microsoft Visual Studio\VC98\Include 文件夹中。
- reverse 算法可将向量中某段迭代器区间元素反向排列,看下面这段代码:
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main(int argc, char* argv[])
{
vector<int> v(10);
//给向量赋值
for(int i=0;i<10;i++)
{
v[i]=i;
}
//反向排列向量的从首到尾间的元素
reverse(v.begin(),v.end());
//定义迭代器变量
vector<int>::iterator it;
for(it=v.begin();it!=v.end();it++)
{
//输出迭代器上的元素值
cout<<*it<<" ";
}
//换行
cout<<endl;
return 0;
}
输出结果:
9 8 7 6 5 4 3 2 1 0
- 使用 sort 算法对向量元素排序
使用 sort 算法,需要声明头文件“#include ”。
sort 算法要求使用随机访问迭代器进行排序,在默认的情况下,对向量元素进行升序排列,下面这个程序很好地说明了 sort 算法的使用方法:
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main(int argc, char* argv[])
{
vector<int> v;
int i;
//赋值
for(i=0;i<10;i++)
{
v.push_back(9-i);
}
//输出排序前的元素值
for(i=0;i<10;i++)
{
cout<<v[i]<<" ";
}
//回车换行
cout<<endl;
//排序,升序排列
sort(v.begin(),v.end());
//输出排序后的元素值
for(i=0;i<10;i++)
{
cout<<v[i]<<" ";
}
//回车换行
cout<<endl;
return 0;
}
运行结果:
9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8 9
-
自定义排序方法
还可以自己设计排序比较函数,然后,把这个函数指定给 sort 算法,那么,sort 就根据这个比较函数指定的排序规则进行排序。下面的程序自己设计了一个排序比较函数 Comp,要求对元素的值由大到小排序
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
//自己设计排序比较函数:对元素的值进行降序排列
bool Comp(const int &a,const int &b)
{
if(a!=b)return a>b;
else return a>b;
}
int main(int argc, char* argv[])
{
vector<int> v;
int i;
//赋值
for(i=0;i<10;i++)
{
v.push_back(i);
}
//输出排序前的元素值
for(i=0;i<10;i++)
{
cout<<v[i]<<" ";
}
//回车换行
cout<<endl;
//按 Comp 函数比较规则排序
sort(v.begin(),v.end(),Comp);
//输出排序后的元素值
for(i=0;i<10;i++)
{
cout<<v[i]<<" ";
}
//回车换行
cout<<endl;
return 0;
}
运行结果:
0 1 2 3 4 5 6 7 8 9
9 8 7 6 5 4 3 2 1 0
6) 向量的大小
- 使用 size()方法可以返回向量的大小,即元素的个数。
- 使用 empty()方法返回向量是否为空。
演示:
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main(int argc, char* argv[])
{
vector<int> v(10);
//给向量赋值
for(int i=0;i<10;i++)
{
v[i]=i;
}
//输出向量的大小,即包含了多少个元素
cout<<v.size()<<endl;
//输出向量是否为空,如果非空,则返回逻辑假,即 0,否则返回逻辑真,即 1
cout<<v.empty()<<endl;
//清空向量
v.clear();
//输出向量是否为空,如果非空,则返回逻辑假,即 0,否则返回逻辑真,即 1
cout<<v.empty()<<endl;
return 0;
}
运行结果:
10
0
1
例1:用 vector 向量容器装入 10 个整数,然后,使用迭代器 iterator 和 accumulate 算法统计出这 10 个元素的和
include "stdafx.h"
//cin 和 cout 需要
#include <iostream>
//向量需要
#include <vector>
//accumulate 算法需要
#include <numeric>
using namespace std;
int main(int argc, char* argv[])
{
//定义向量 v
vector<int> v;
int i;
//赋值
for(i=0;i<10;i++)
{
//尾部元素扩张方式赋值
v.push_back(i);
}
//使用 iterator 迭代器顺序遍历所有元素
for(vector<int>::iterator it=v.begin();it!=v.end();it++)
{
//输出迭代器当前位置上的元素值
cout<<*it<<" ";
}
cout<<endl;//回车换行
//统计并输出向量所有元素的和
cout<<accumulate(v.begin(),v.end(),0)<<endl;
return 0;
}
(二)String基本字符系列容器
C 语言只提供了一个 char 类型用来处理字符,而对于字符串,只能通过字符串数组来处理,显得十分不便。C++STL 提供了 string 基本字符系列容器来处理字符串,可以把 string理解为字符串类,它提供了添加、删除、替换、查找和比较等丰富的方法。
虽然使用 vector这样的向量也可以处理字符串,但功能比不上 string。向量的元素类型可以是 string,如 vector这样的向量,实际上就类似于 C 语言中的字符串数组。
使用 string 容器,需要头文件包含声明“#include ”。string 文件在 C:\ProgramFiles\Microsoft Visual Studio\VC98\Include 文件夹中可以找到。
1)创建String对象
下面这条语句创建了字符串对象 s,s 是一个空字符串,其长度为 0:
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
cout<<s.length()<<endl;
return 0;
}
运行结果:
0
2) 给String赋值
1)直接给字符串对象赋值
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s="hello,C++STL.";
cout<<s<<endl;
return 0;
}
运行结果:
hello,C++STL.
2)把字符指针赋给一个字符串对象
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
char ss[5000];
//scanf 的输入速度比 cin 快得多
//scanf 是 C 语言的函数,不支持 string 对象
scanf("%s",&ss);
//把整个字符数组赋值给 string 对象
s=ss;
//输出字符对象
cout<<s<<endl;
return 0;
}
运行结果(先从键盘上输入“hello,string.”):
hello,string.
hello,string.
3)从 string 对象尾部添加字符
在 string 对象的尾部添加一个字符(char),采用“+”操作符即可
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s=s+'a';
s=s+'b';
s=s+'c';
cout<<s<<endl;
return 0;
}
- 在 string 对象的尾部添加一个字符(char),采用“+”操作符即可
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s=s+"abc";
s=s+"123";
cout<<s<<endl;
return 0;
}
运行结果:
abc123
采用 append()方法,代码如下:
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s.append("abc");
s.append("123");
cout<<s<<endl;
return 0;
}
运行结果:
abc123
5) 给 string 对象插入字符
可以使用 insert()方法把一个字符插入到迭代器位置之前
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s="123456";
//定义迭代器
string::iterator it;
//迭代器位置为字符串首
it=s.begin();
//把字符'p'插入到第 1 个字符前(注意,字符位置是从 0 开始计数)
s.insert(it+1,'p');
cout<<s<<endl;
return 0;
}
运行结果:
1p23456
6)访问 string 对象的元素
一般使用下标方式随机访问 string 对象的元素,下标是从 0 开始计数的。另外,string对象的元素是一个字符(char),这点一定要清楚。
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s="abc123456";
//输出 string 对象的首元素
cout<<s[0]<<endl;
//两个相同的字符相减值为 0
cout<<s[0]-'a'<<endl;
return 0;
}
运行结果:
a
0
- 删除 string 对象的元素
(1)清空一个字符串,则直接给它赋空字符串即可。
(2)使用erase()方法删除迭代器所指的那个元素或一个区间中的所有元素。代码如下:
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s="abc123456";
//定义迭代器变量,指向字符串对象首元素
string::iterator it=s.begin();
//删除第 3 个元素,元素位置从 0 开始计数
s.erase(it+3);
cout<<s<<endl;
//删除 0~4 区间的所有元素
s.erase(it,it+4);
cout<<s<<endl;
//清空字符串
s="";
//输出字符串的长度
cout<<s.length()<<endl;
return 0;
}
运行结果:
abc23456
3456
0
- 返回string对象的长度
采用 length()方法可返回字符串的长度;采用 empty()方法,可返回字符串是否为空,
如果字符串为空,则返回逻辑真,即 1,否则,返回逻辑假,即 0。代码如下:
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s="abc123456";
//输出字符串的长度
cout<<s.length()<<endl;
//清空字符串
s="";
//判断字符串是否为空
cout<<s.empty()<<endl;
return 0;
}
运行结果:
9
1
- 替换 string 对象的字符
使用 replace()方法可以很方便地替换 string 对象中的字符,replace()方法的重载函数相
当多,常用的只有一两个,具体代码如下:
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s="abc123456";
//从第 3 个开始,将连续的 3 个字符替换为“good”
//即将“abc”替换为“good”
s.replace(3,3,"good");
cout<<s<<endl;
return 0;
}
运行结果:
abcgood456
10)搜索 string 对象的元素或子串
采用 find()方法可查找字符串中的第一个字符元素(char,用单引号界定)或者子串(用
双引号界定),如果查到,则返回下标值(从 0 开始计数),如果查不到,则返回 4294967295。
find()方法有很多重载函数,下面的代码,仅举出了一种用法。
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s="cat dog cat";
//查找第一个字符‘c’,返回下标值
cout<<s.find('c')<<endl;
//查找第一个子串“c”,返回下标值
cout<<s.find("c")<<endl;
//查找第一个子串“cat”,返回下标值
cout<<s.find("cat")<<endl;
//查找第一个子串“dog”,返回下标值
cout<<s.find("dog")<<endl;
//查找第一个子串“dogc”,查不到则返回 4294967295
cout<<s.find("dogc")<<endl;
return 0;
}
运行结果:
0
0
0
4
4294967295
11)string 对象的比较
string 对象可与使用 compare()方法与其他字符串相比较。如果它比对方大,则返回 1;
如果它比对方小,则返回-1;如果它与对方相同(相等),则返回 0。代码如下
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s="cat dog cat";
//s 比“cat”字符串大,返回 1
cout<<s.compare("cat")<<endl;
//s 与“cat dog cat”相等,返回 0
cout<<s.compare("cat dog cat")<<endl;
//s 比“dog”小,返回-1
cout<<s.compare("dog")<<endl;
return 0;
}
运行结果:
1
0
-1
12)用 reverse 反向排序 string 对象
采用 reverse()方法可将 string 对象迭代器所指向的一段区间中的元素(字符)反向排
序。reverse()方法需要声明头文件“#include <algorithm>”。代码如下:
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s="123456789";
reverse(s.begin(),s.end());
cout<<s<<endl;
return 0;
}
运行结果:
987654321
- string 对象作为 vector 元素
string 对象可以作为 vector 向量的元素,这种用法,类似于字符串数组。
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
int main(int argc, char* argv[])
{
vector<string> v;
v.push_back("Jack");
v.push_back("Mike");
v.push_back("Tom");
cout<<v[0]<<endl;
cout<<v[1]<<endl;
cout<<v[2]<<endl;
cout<<v[0][0]<<endl;
cout<<v[1][0]<<endl;
cout<<v[2].length()<<endl;
return 0;
}
运行结果:
Jack
Mike
Tom
J
M
3
- string 类型的数字化处理
比赛中常常需要将读入的数字的每位分离出来,如果采用取余的方法,花费的时间就会太长,这时候,我们可以将读入的数据当成字符串来处理,这样就方便、省时多了。下面这个程序演示了求一个整数各位的和:
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
s="1234059";
int i;
int sum=0;
for(i=0;i<s.length();i++)
{
if(s[i]=='0')sum+=0;
else if(s[i]=='1')sum+=1;
else if(s[i]=='2')sum+=2;
else if(s[i]=='3')sum+=3;
else if(s[i]=='4')sum+=4;
else if(s[i]=='5')sum+=5;
else if(s[i]=='6')sum+=6;
else if(s[i]=='7')sum+=7;
else if(s[i]=='8')sum+=8;
else if(s[i]=='9')sum+=9;
}
cout<<sum<<endl;
return 0;
}
运行结果:
24
- string 对象与字符数组互操作
下面这个程序演示了字符数组与 string 对象的输入与输出:
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s;
char ss[100];
//输入字符串到字符数组中
scanf("%s",&ss);
//字符数组赋值线字符串对象
s=ss;
//用 printf 输出字符串对象,要采用 c_str()方法
printf(s.c_str());
//换行
cout<<endl;
//用 printf 输出字符数组
printf("%s",ss);
//换行
cout<<endl;
//用 cout 输出字符串对象
cout<<s<<endl;
//用 cout 输出字符数组
cout<<ss<<endl;
return 0;
}
输出结果(从键盘输入“abc123”字符串后回车):
abc123
abc123
abc123
abc123
abc123
- string 对象与 sscanf 函数
在 C 语言中,sscanf 函数很管用,它可以把一个字符串按你需要的方式分离出子串,
甚至是数字。下面这个程序演示了 sscanf 函数的具体用法:
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
string s1,s2,s3;
char sa[100],sb[100],sc[100];
//将字符串分离成子串,分隔符为空格
sscanf("abc 123 pc","%s %s %s",sa,sb,sc);
s1=sa;
s2=sb;
s3=sc;
cout<<s1<<" "<<s2<<" "<<s3<<endl;
//将字符串分离成数字,分隔符为空格
//当用到数字的时候,跟 scanf 一样,它要传指针地址
int a,b,c;
sscanf("1 2 3","%d %d %d",&a,&b,&c);
cout<<a<<" "<<b<<" "<<c<<endl;
//将字符串分离成数字,分隔符为“,”和“$”
//当用到数字的时候,跟 scanf 一样,它要传指针地址
int x,y,z;
sscanf("4,5$6","%d,%d$%d",&x,&y,&z);
cout<<x<<" "<<y<<" "<<z<<endl;
return 0;
}
运行结果:
abc 123 pc
1 2 3
4 5 6
(三) set 集合容器
set 集合容器实现了红黑树(Red-Black Tree)的平衡二叉检索树的数据结构,在插入元素时,它会自动调整二叉树的排列,把该元素放到适当的位置,以确保每个子树根节点的键值大于左子树所有节点的键值,而小于右子树所有节点的键值;另外,还得确保根节点左子树的高度与右子树的高度相等,这样,二叉树的高度最小,从而检索速度最快。要注意的是,它不会重复插入相同键值的元素,而采取忽略处理。图 2-3 是一个典型的红黑树。
平衡二叉检索树的检索使用中序遍历算法,检索效率高于 vector、deque 和 list 等容器。
另外,采用中序遍历算法可将键值由小到大遍历出来,所以,可以理解为平衡二叉检索树
在插入元素时,就会自动将元素按键值由小到大的顺序排列。对于 set 容器中的键值,不可直接去修改。因为如果把容器中的一个键值修改了,set容器会根据新的键值旋转子树,以保持新的平衡,这样,修改的键值很可能就不在原先那个位置上了。换句话来说,构造 set 集合的主要目的就是为了快速检索。
multiset(多重集合容器)、map(映照容器)和 multimap(多重映照容器)的内部结
构也是平衡二叉检索树。
使用 set 前,需要在程序的头文件中包含声明“#include ”;set 文件在 C:\ProgramFiles\Microsoft Visual Studio\VC98\Include 文件夹中,它包含了 set 和 multiset 两种容器的定义。
1) 创建set集合对象
创建 set 对象时,需要指定元素的类型,这一点与其他容器一样。下面的程序详细说
明了如何创建集合对象。
#include <set>
using namespace std;
int main(int argc, char* argv[])
{
//定义元素类型为 int 的集合对象 s,当前没有任何元素
//元素的排列采用默认的比较规则,当然,可以自定义比较规则函数
set<int> s;
return 0;
}
2) 元素的插入与中序遍历
采用 insert()方法把元素插入集合中去,插入的具体规则在默认的比较规则下,是按元素值由小到大插入,如果自己指定了比较规则函数,则按自定义比较规则函数插入。使用前向迭代器对集合中序遍历,其结果正好是元素排序的结果。
下面这个例子说明了 insert()方法的使用方法:
#include <set>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
//定义元素类型为 int 的集合对象 s,当前没有任何元素
set<int> s;
//插入了 5 个元素,但由于 8 有重复,第二次插入的 8 并没有执行
s.insert(8);//第一次插入 8,可以插入
s.insert(1);
s.insert(12);
s.insert(6);
s.insert(8);//第二次插入 8,重复元素,不会插入
//中序遍历集合中的元素
set<int>::iterator it;//定义前向迭代器
//中序遍历集合中的所有元素
for(it=s.begin();it!=s.end();it++)
{
cout<<*it<<" ";
}
cout<<endl;//回车换行
return 0;
}
运行结果:
1 6 8 12
3)元素的反向遍历
使用反向迭代器 reverse_iterator 可以反向遍历集合,输出的结果正好是集合元素的反向排序结果。它需要用到 rbegin()和 rend()两个方法,它们分别给出了反向遍历的开始位置和结束位置。
#include <set>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
//定义元素类型为 int 的集合对象 s,当前没有任何元素
set<int> s;
//插入了 5 个元素,但由于 8 有重复,第二次插入的 8 并没有执行
s.insert(8);//第一次插入 8,可以插入
s.insert(1);
s.insert(12);
s.insert(6);
s.insert(8);//第二次插入 8,重复元素,不会插入
//反向遍历集合中的元素
set<int>::reverse_iterator rit;//定义反向迭代器
for(rit=s.rbegin();rit!=s.rend();rit++)
{
cout<<*rit<<" ";
}
cout<<endl;//回车换行
return 0;
}
运行结果:
12 8 6 1
4)元素的删除
与插入元素的处理一样,集合具有高效的删除处理功能,并自动重新调整内部的红黑树的平衡。
删除的对象可以是某个迭代器位置上的元素、等于某键值的元素、一个区间上的元素和清空集合。
#include <set>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
//定义元素类型为 int 的集合对象 s,当前没有任何元素
set<int> s;
//插入了 5 个元素,但由于 8 有重复,第二次插入的 8 并没有执行
s.insert(8);//第一次插入 8,可以插入
s.insert(1);
s.insert(12);
s.insert(6);
s.insert(8);//第二次插入 8,重复元素,不会插入
//删除键值为 6 的那个元素
s.erase(6);
//反向遍历集合中的元素
set<int>::reverse_iterator rit;//定义反向迭代器
for(rit=s.rbegin();rit!=s.rend();rit++)
{
cout<<*rit<<" ";
}
cout<<endl;//回车换行
//清空集合
s.clear();
//输出集合的大小,为 0
cout<<s.size()<<endl;
return 0;
}
运行结果:
12 8 1
0
5)元素的检索
使用 find()方法对集合进行搜索,如果找到查找的键值,则返回该键值的迭代器位置,
否则,返回集合最后一个元素后面的一个位置,即 end()。
下面这个程序详细讲述了如何使用 find()方法对集合进行检索:
#include <set>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
//定义元素类型为 int 的集合对象 s,当前没有任何元素
set<int> s;
//插入了 5 个元素,但由于 8 有重复,第二次插入的 8 并没有执行
s.insert(8);//第一次插入 8,可以插入
s.insert(1);
s.insert(12);
s.insert(6);
s.insert(8);//第二次插入 8,重复元素,不会插入
set<int>::iterator it;//定义前向迭代器
//查找键值为 6 的元素
it=s.find(6);
if(it!=s.end())//找到
cout<<*it<<endl;
else//没找到
cout<<"not find it"<<endl;
//查找键值为 20 的元素
it=s.find(20);
if(it!=s.end())//找到
cout<<*it<<endl;
else//没找到
cout<<"not find it"<<endl;
return 0;
}
运行结果:
6
not find it
(四)map映射容器
map 映照容器的元素数据是由一个键值和一个映照数据组成的,键值与映照数据之间具有一一映照的关系。
map 映照容器的数据结构也是采用红黑树来实现的,插入元素的键值不允许重复,比较函数只对元素的键值进行比较,元素的各项数据可通过键值检索出来。由于 map 与 set采用的都是红黑树的数据结构,所以,用法基本相似。
使用 map 容器需要头文件包含语句“#include
1)map 创建、元素插入和遍历访问
创建 map 对象,键值与映照数据的类型由自己定义。在没有指定比较函数时,元素的插入位置是按键值由小到大插入到黑白树中去的,这点和 set 一样。下面这个程序详细说明了如何操作 map 容器。
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
//定义 map 对象,当前没有任何元素
map<string,float> m;
//插入元素,按键值的由小到大放入黑白树中
m["Jack"]=98.5;
m["Bomi"]=96.0;
m["Kate"]=97.5;
//前向遍历元素
map<string,float>::iterator it;
for(it=m.begin();it!=m.end();it++)
{
//输出键值与映照数据
cout<<(*it).first<<" : "<<(*it).second<<endl;
}
return 0;
}
运行结果:
Bomi : 96
Jack : 98.5
Kate : 97.5
程序编译时,会产生代号为“warning C4786”的警告,“4786”是标记符超长警告的代号。可以在程序的头文件包含代码的前面使用“#pragma warning(disable:4786)”宏语句,强制编译器忽略该警告。4786 号警告对程序的正确性和运行并无影响。
2) 删除元素
与 set 容器一样,map 映照容器的 erase()删除元素函数,可以删除某个迭代器位置上的元素、等于某个键值的元素、一个迭代器区间上的所有元素,当然,也可使用 clear()方法清空 map 映照容器。
#pragma warning(disable:4786)
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
//定义 map 对象,当前没有任何元素
map<int,char> m;
//插入元素,按键值的由小到大放入黑白树中
m[25]='m';
m[28]='k';
m[10]='x';
m[30]='a';
//删除键值为 28 的元素
m.erase(28);
//前向遍历元素
map<int,char>::iterator it;
for(it=m.begin();it!=m.end();it++)
{
//输出键值与映照数据
cout<<(*it).first<<" : "<<(*it).second<<endl;
}
return 0;
}
运行结果:
10 : x
25 : m
30 : a
3) 元素反向遍历
可以使用反向迭代器 reverse_iterator 反向遍历 map 照映容器中的数据,它需要 rbegin()方法和 rend()方法指出反向遍历的起始位置和终止位置。
#pragma warning(disable:4786)
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
//定义 map 对象,当前没有任何元素
map<int,char> m;
//插入元素,按键值的由小到大放入黑白树中
m[25]='m';
m[28]='k';
m[10]='x';
m[30]='a';
//反向遍历元素
map<int,char>::reverse_iterator rit;
for(rit=m.rbegin();rit!=m.rend();rit++)
{
//输出键值与映照数据
cout<<(*rit).first<<" : "<<(*rit).second<<endl;
}
return 0;
}
运行结果:
30 : a
28 : k
25 : m
10 : x
4) 元素的搜索
使用 find()方法来搜索某个键值,如果搜索到了,则返回该键值所在的迭代器位置,否则,返回 end()迭代器位置。由于 map 采用黑白树数据结构来实现,所以搜索速度是极快的。
下面这个程序搜索键值为 28 的元素:
#pragma warning(disable:4786)
#include <map>
#include <string>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
//定义 map 对象,当前没有任何元素
map<int,char> m;
//插入元素,按键值的由小到大放入黑白树中
m[25]='m';
m[28]='k';
m[10]='x';
m[30]='a';
map<int,char>::iterator it;
it=m.find(28);
if(it!=m.end())//搜索到该键值
{
cout<<(*it).first<<" : "<<(*it).second<<endl;
}
else
{
cout<<"not found it"<<endl;
}
return 0;
}
运行结果:
28 : k
- 自定义比较函数
将元素插入到 map 中去的时候,map 会根据设定的比较函数将该元素放到该放的节点上去。在定义 map 的时候,如果没有指定比较函数,那么采用默认的比较函数,即按键值由小到大的顺序插入元素。在很多情况下,需要自己编写比较函数。
编写比较函数与 set 比较函数是一致的,因为它们的内部数据结构都是红黑树。编写法有两种。
(1)如果元素不是结构体,那么,可以编写比较函数。下面这个程序编写的比较规则是要求按键值由大到小的顺序将元素插入到 map 中:
#pragma warning(disable:4786)
#include <map>
#include <string>
#include <iostream>
using namespace std;
//自定义比较函数 myComp
struct myComp
{
bool operator()(const int &a,const int &b)
{
if(a!=b)return a>b;
else
return a>b;
}
};
int main(int argc, char* argv[])
{
//定义 map 对象,当前没有任何元素
map<int,char,myComp> m;
//插入元素,按键值的由小到大放入黑白树中
m[25]='m';
m[28]='k';
m[10]='x';
m[30]='a';
//使用前向迭代器中序遍历 map
map<int,char,myComp>::iterator it;
for(it=m.begin();it!=m.end();it++)
{
cout<<(*it).first<<" : "<<(*it).second<<endl;
}
return 0;
}
运行结果:
30 : a
28 : k
25 : m
10 : x
(2)如果元素是结构体,那么,可以直接把比较函数写在结构体内。下面的程序详细说明了如何操作:
#pragma warning(disable:4786)
#include <map>
#include <string>
#include <iostream>
using namespace std;
struct Info
{
string name;
float score;
//重载“<”操作符,自定义排序规则
bool operator < (const Info &a) const
{
//按 score 由大到小排列。如果要由小到大排列,使用“>”号即可
return a.score<score;
}
};
int main(int argc, char* argv[])
{
//定义 map 对象,当前没有任何元素
map<Info,int> m;
//定义 Info 结构体变量
Info info;
//插入元素,按键值的由小到大放入黑白树中
info.name="Jack";
info.score=60;
m[info]=25;
info.name="Bomi";
info.score=80;
m[info]=10;
info.name="Peti";
info.score=66.5;
m[info]=30;
//使用前向迭代器中序遍历 map
map<Info,int>::iterator it;
for(it=m.begin();it!=m.end();it++)
{
cout<<(*it).second<<" : ";
cout<<((*it).first).name<<" "<<((*it).first).score<<endl;
}
return 0;
}
运行结果:
10 : Bomi 80
30 : Peti 66.5
25 : Jack 60
例:
用 map 实现数字分离,对数字的各位进行分离,采用取余等数学方法操作是很耗时的。而把数字当成字符串,使用 map 的映照功能,很方便地实现了数字分离。下面这个程序将一个字符串中的字符当成数字,并将各位的数值相加,最后输出各位的和。
#pragma warning(disable:4786)
#include <string>
#include <map>
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
//定义 map 对象,当前没有任何元素
map<char,int> m;
//赋值:字符映射数字
m['0']=0;
m['1']=1;
m['2']=2;
m['3']=3;
m['4']=4;
m['5']=5;
m['6']=6;
m['7']=7;
m['8']=8;
m['9']=9;
/*上面的 10 条赋值语句可采用下面这个循环来简化代码编写
for(int j=0;j<10;j++)
{
m['0'+j]=j;
}
*/
string sa,sb;
sa="6234";
int i;
int sum=0;
for(i=0;i<sa.length();i++)
{
sum+=m[sa[i]];
}
cout<<"sum = "<<sum<<endl;
return 0;
}
运行结果:
sum = 15