机试技巧与STL
vs2018 快捷键
CTRL + J 列出成员
Ctrl+E,D 格式化全部代码
Ctrl+K,F 格式化选中的代码
CTRL + SHIFT + E 显示资源视图
F12 转到定义
CTRL + F12 转到声明
CTRL + ALT + J 对象浏览
CTRL + ALT + F1 帮助目录
CTRL + F1 动态帮助
CTRL + K, CTRL + C 注释选择的代码
CTRL + K, CTRL + U 取消对选择代码的注释
CTRL + U 转小写
CTRL + SHIFT + U 转大写
F5 运行调试
CTRL + F5 运行不调试
F10 跨过程序执行
F11 单步逐句执行
头文件
标准c库
头文件 | 说明 | 头文件 | 说明 | 头文件 | 说明 |
---|---|---|---|---|---|
assert.h | 断言相关 | ctype.h | 字符类型判断 | errno.h | 标准错误机制 |
float.h | 浮点限制 | limits.h | 整形限制 | locale.h | 本地化接口 |
math.h | 数学函数 | setjmp.h | 非本地跳转 | signal.h | 信号相关 |
stdarg.h | 可变参数处理 | stddef.h | 宏和类型定义 | stdio.h | 标准I/O |
stdlib.h | 标准工具库 | string.h | 字符串和内存处理 | time.h | 时间相关 |
c++ STL
using namespace std;
头文件 | 说明 | 头文件 | 说明 | 头文件 | 说明 |
---|---|---|---|---|---|
algorithm | 通用算法 | deque | 双端队列 | vector | 向量 |
iterator | 迭代器 | stack | 栈 | map | 图(键值对) |
list | 列表 | string | 字符串 | set | 集合 |
queue | 队列 | bitset | bit类 | numeric | 数值算法 |
常用头
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
using namespace std;
常用宏定义
//求最大值和最小值
#define MAX(x,y) (((x)>(y)) ? (x) : (y))
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
//取余
#define mod(x) ((x)%MOD)
//for循环
#define FOR(i,f_start,f_end) for(int i=f_start;i<=f_end;++i)
//返回数组元素的个数
#define ARR_SIZE(a) (sizeof((a))/sizeof((a[0])))
//初始化数组
#define MT(x,i) memset(x,i,sizeof(x))
#define MEM(a,b) memset((a),(b),sizeof(a))
//符号重定义
#define LL long long
#define ull unsigned long long
#define pii pair<int,int>
//常见常数
#define PI acos(-1.0)
#define eps 1e-12
#define INF 0x3f3f3f3f //int最大值
const int INF_INT = 2147483647;
const ll INF_LL = 9223372036854775807LL;
const ull INF_ULL = 18446744073709551615Ull;
const ll P = 92540646808111039LL;
const ll maxn = 1e5 + 10, MOD = 1e9 + 7;
const int Move[4][2] = {-1,0,1,0,0,1,0,-1};
const int Move_[8][2] = {-1,-1,-1,0,-1,1,0,-1,0,1,1,-1,1,0,1,1};
结构体
定义
struct InitMember
{
int first;
double second;
char* third;
float four;
};
初始化
方法一:定义时赋值
struct InitMember test = {-10,3.141590,"method one",0.25};
方法二:定义后逐个赋值
struct InitMember test;
test.first = -10;
test.second = 3.141590;
test.third = "method two";
test.four = 0.25;
方法三:定义时乱序赋值(C++风格)
struct InitMember test = {
second:3.141590,
third:"method three",
first:-10,
four:0.25
};
方法四:构造函数
//定义图的定点
typedef struct Vertex {
int id,inDegree,outDegree;
vector<int> connectors; //存储节点的后续连接顶点编号
Vertex() : id(-1),inDegree(0),outDegree(0) {}
Vertex(int nid) : id(nid),inDegree(0),outDegree(0) {}
} Vertex;
//定义Graph的邻接表表示
typedef struct Graph {
vector<Vertex> vertexs; //存储定点信息
int nVertexs; //计数:邻接数
bool isDAG; //标志:是有向图吗
Graph(int n, bool isDAG) : nVertexs(n), isDAG(isDAG) { vertexs.resize(n); }
Graph() : nVertexs(1), isDAG(1) { vertexs.resize(1); }
//向图中添加边
bool addEdge(int id1, int id2) {
...
...
...
return true;
}
} Graph;
Graph g(8, false);
运算符重载
typedef struct{int id;int h;} node;
bool operator <(const node& a,const node & b){return (a.h)<(b.h);}
c++new的使用
常规
int *x = new int; //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址(即指针)
int *a = new int(100); //开辟一个存放整数的空间,并指定该整数的初值为100,返回一个指向该存储空间的地址
char *b = new char[10]; //开辟一个存放字符数组(包括10个元素)的空间,返回首元素的地址
float *p=new float (3.14159);//开辟一个存放单精度数的空间,并指定该实数的初值为//3.14159,将返回的该空间的地址赋给指针变量p
动态申请列大小固定的二维数组
//列值固定
const int MAXCOL = 3;
cin>>row;
//申请一维数据并将其转成二维数组指针
int *pp_arr = new int[nRow * MAXCOL];
int (*p)[MAXCOL] = (int(*)[MAXCOL])pp_arr;
//此时p[i][j]就可正常使用
动态申请大小不固定的二维数组
cin>>row>>col;
int **p = new int*[row];
for (int i = 0; i < row; i ++)
{
p[i] = new int[col];
}
常用STL
参考:
https://blog.csdn.net/f_zyj/article/details/51594851
https://download.csdn.net/download/f_zyj/9988653
简述
STL底层说明
C++ STL 的实现:
1.vector 底层数据结构为数组 ,支持快速随机访问
2.list 底层数据结构为双向链表,支持快速增删
3.deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问
deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下:
[堆1] --> [堆2] -->[堆3] --> ...
每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品.
4.stack 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
5.queue 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)
6.priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
7.set 底层数据结构为红黑树,有序,不重复
8.multiset 底层数据结构为红黑树,有序,可重复
9.map 底层数据结构为红黑树,有序,不重复
10.multimap 底层数据结构为红黑树,有序,可重复
11.hash_set 底层数据结构为hash表,无序,不重复
12.hash_multiset 底层数据结构为hash表,无序,可重复
13.hash_map 底层数据结构为hash表,无序,不重复
14.hash_multimap 底层数据结构为hash表,无序,可重复
CCF 编译出错原因: 不允许C++STL容器嵌套(需要满足相应的格式)
就是要在后面的“>”之间,必须得有一个空格,如果有多层,那每层都得有一个空格。
map<string,list<string> > user;
algorithm
头文件:lgorithm
函数参数,返回值以及具体的使用方法请自行去头文件找定义!!!
不修改内容的序列操作
函数 | 说明 |
---|---|
adjacent_find | 查找两个相邻(Adjacent)的等价(Identical)元素 |
all_ofC++11 | 检测在给定范围中是否所有元素都满足给定的条件 |
any_ofC++11 | 检测在给定范围中是否存在元素满足给定条件 |
count | 返回值等价于给定值的元素的个数 |
count_if | 返回值满足给定条件的元素的个数 |
equal | 返回两个范围是否相等 |
find | 返回第一个值等价于给定值的元素 |
find_end | 查找范围A中与范围B等价的子范围最后出现的位置 |
find_first_of | 查找范围A中第一个与范围B中任一元素等价的元素的位置 |
find_if | 返回第一个值满足给定条件的元素 |
find_if_notC++11 | 返回第一个值不满足给定条件的元素 |
for_each | 对范围中的每个元素调用指定函数 |
mismatch | 返回两个范围中第一个元素不等价的位置 |
none_ofC++11 | 检测在给定范围中是否不存在元素满足给定的条件 |
search | 在范围A中查找第一个与范围B等价的子范围的位置 |
search_n | 在给定范围中查找第一个连续n个元素都等价于给定值的子范围的位置 |
修改内容的序列操作
函数 | 说明 |
---|---|
copy | 将一个范围中的元素拷贝到新的位置处 |
copy_backward | 将一个范围中的元素按逆序拷贝到新的位置处 |
copy_ifC++11 | 将一个范围中满足给定条件的元素拷贝到新的位置处 |
copy_nC++11 | 拷贝 n 个元素到新的位置处 |
fill | 将一个范围的元素赋值为给定值 |
fill_n | 将某个位置开始的 n 个元素赋值为给定值 |
generate | 将一个函数的执行结果保存到指定范围的元素中,用于批量赋值范围中的元素 |
generate_n | 将一个函数的执行结果保存到指定位置开始的 n 个元素中 |
iter_swap | 交换两个迭代器(Iterator)指向的元素 |
moveC++11 | 将一个范围中的元素移动到新的位置处 |
move_backwardC++11 | 将一个范围中的元素按逆序移动到新的位置处 |
random_shuffle | 随机打乱指定范围中的元素的位置 |
remove | 将一个范围中值等价于给定值的元素删除 |
remove_if | 将一个范围中值满足给定条件的元素删除 |
remove_copy | 拷贝一个范围的元素,将其中值等价于给定值的元素删除 |
remove_copy_if | 拷贝一个范围的元素,将其中值满足给定条件的元素删除 |
replace | 将一个范围中值等价于给定值的元素赋值为新的值 |
replace_copy | 拷贝一个范围的元素,将其中值等价于给定值的元素赋值为新的值 |
replace_copy_if | 拷贝一个范围的元素,将其中值满足给定条件的元素赋值为新的值 |
replace_if | 将一个范围中值满足给定条件的元素赋值为新的值 |
reverse | 反转排序指定范围中的元素 |
reverse_copy | 拷贝指定范围的反转排序结果 |
rotate | 循环移动指定范围中的元素 |
rotate_copy | 拷贝指定范围的循环移动结果 |
shuffleC++11 | 用指定的随机数引擎随机打乱指定范围中的元素的位置 |
swap | 交换两个对象的值 |
swap_ranges | 交换两个范围的元素 |
transform | 对指定范围中的每个元素调用某个函数以改变元素的值 |
unique | 删除指定范围中的所有连续重复元素,仅仅留下每组等值元素中的第一个元素。 |
unique_copy | 拷贝指定范围的唯一化(参考上述的 unique)结果 |
划分操作
函数 | 说明 |
---|---|
is_partitionedC++11 | 检测某个范围是否按指定谓词(Predicate)划分过 |
partition | 将某个范围划分为两组 |
partition_copyC++11 | 拷贝指定范围的划分结果 |
partition_pointC++11 | 返回被划分范围的划分点 |
stable_partition | 稳定划分,两组元素各维持相对顺序 |
排序操作
函数 | 说明 |
---|---|
is_sortedC++11 | 检测指定范围是否已排序 |
is_sorted_untilC++11 | 返回最大已排序子范围 |
nth_element 部份排序指定范围中的元素,使得范围按给定位置处的元素划分 | |
partial_sort | 部份排序 |
partial_sort_copy | 拷贝部分排序的结果 |
sort | 排序 |
stable_sort | 稳定排序 |
二分法查找操作
函数 | 说明 |
---|---|
binary_search | 判断范围中是否存在值等价于给定值的元素 |
equal_range | 返回范围中值等于给定值的元素组成的子范围 |
lower_bound | 返回指向范围中第一个值大于或等于给定值的元素的迭代器 |
upper_bound | 返回指向范围中第一个值大于给定值的元素的迭代器 |
集合操作
函数 | 说明 |
---|---|
includes | 判断一个集合是否是另一个集合的子集 |
inplace_merge | 就绪合并 |
merge 合并 | |
set_difference | 获得两个集合的差集 |
set_intersection | 获得两个集合的交集 |
set_symmetric_difference | 获得两个集合的对称差 |
set_union | 获得两个集合的并集 |
堆操作
函数 | 说明 |
---|---|
is_heap | 检测给定范围是否满足堆结构 |
is_heap_untilC++11 | 检测给定范围中满足堆结构的最大子范围 |
make_heap | 用给定范围构造出一个堆 |
pop_heap | 从一个堆中删除最大的元素 |
push_heap | 向堆中增加一个元素 |
sort_heap | 将满足堆结构的范围排序 |
最大/最小操作
函数 | 说明 |
---|---|
is_permutationC++11 | 判断一个序列是否是另一个序列的一种排序 |
lexicographical_compare | 比较两个序列的字典序 |
max | 返回两个元素中值最大的元素 |
max_element | 返回给定范围中值最大的元素 |
min | 返回两个元素中值最小的元素 |
min_element | 返回给定范围中值最小的元素 |
minmaxC++11 | 返回两个元素中值最大及最小的元素 |
minmax_elementC++11 | 返回给定范围中值最大及最小的元素 |
next_permutation | 返回给定范围中的元素组成的下一个按字典序的排列 |
prev_permutation | 返回给定范围中的元素组成的上一个按字典序的排列 |
vector
头文件:vector
在STL的vector头文件中定义了vector(向量容器模版类),vector容器以连续数组的方式存储元素序列,可以将vector看作是以顺序结构实现的线性表。当我们在程序中需要使用动态数组时,vector将会是理想的选择,vector可以在使用过程中动态地增长存储空间。 vector模版类需要两个模版参数,第一个参数是存储元素的数据类型,第二个参数是存储分配器的类型,其中第二个参数是可选的,如果不给出第二个参数,将使用默认的分配器
下面给出几个常用的定义vector向量对象的方法示例:
vector<int> s;
// 定义一个空的vector对象,存储的是int类型的元素
vector<int> s(n);
// 定义一个含有n个int元素的vector对象
vector<int> s(first, last);
// 定义一个vector对象,并从由迭代器first和last定义的序列[first, last)中复制初值
vector的基本操作:
s[i] // 直接以下标方式访问容器中的元素
s.front() // 返回首元素
s.back() // 返回尾元素
s.push_back(x) // 向表尾插入元素x
s.size() // 返回表长
s.empty() // 表为空时,返回真,否则返回假
s.pop_back() // 删除表尾元素
s.begin() // 返回指向首元素的随机存取迭代器
s.end() // 返回指向尾元素的下一个位置的随机存取迭代器
s.insert(it, val) // 向迭代器it指向的元素前插入新元素val
s.insert(it, n, val)// 向迭代器it指向的元素前插入n个新元素val
s.insert(it, first, last)
// 将由迭代器first和last所指定的序列[first, last)插入到迭代器it指向的元素前面
s.erase(it) // 删除由迭代器it所指向的元素
s.erase(first, last)// 删除由迭代器first和last所指定的序列[first, last)
s.reserve(n) // 预分配缓冲空间,使存储空间至少可容纳n个元素
s.resize(n) // 改变序列长度,超出的元素将会全部被删除,如果序列需要扩展(原空间小于n),元素默认值将填满扩展出的空间
s.resize(n, val) // 改变序列长度,超出的元素将会全部被删除,如果序列需要扩展(原空间小于n),val将填满扩展出的空间
s.clear() // 删除容器中的所有元素
s.swap(v) // 将s与另一个vector对象进行交换
s.assign(first, last)
// 将序列替换成由迭代器first和last所指定的序列[first, last),[first, last)不能是原序列中的一部分
// 要注意的是,resize操作和clear操作都是对表的有效元素进行的操作,但并不一定会改变缓冲空间的大小
// 另外,vector还有其他的一些操作,如反转、取反等,不再一一列举
// vector上还定义了序列之间的比较操作运算符(>、<、>=、<=、==、!=),可以按照字典序比较两个序列。
// 还是来看一些示例代码吧……
/*
* 输入个数不定的一组整数,再将这组整数按倒序输出
*/
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> L;
int x;
while(cin >> x)
{
L.push_back(x);
}
for (int i = L.size() - 1; i >= 0; i--)
{
cout << L[i] << " ";
}
cout << endl;
return 0;
}
list
头文件:list
下面给出几个常用的定义list对象的方法示例:
list<int>a{1,2,3}
list<int>a(n) //声明一个n个元素的列表,每个元素都是0
list<int>a(n, m) //声明一个n个元素的列表,每个元素都是m
list<int>a(first, last) //声明一个列表,其元素的初始值来源于由区间所指定的序列中的元素,first和last是迭代器
list的基本操作:
a.begin() // 返回指向首元素的随机存取迭代器
a.end() // 返回指向尾元素的下一个位置的随机存取迭代器
a.push_front(x) // 向表头插入元素x
a.push_back(x) // 向表尾插入元素x
a.pop_back() // 删除表尾元素
a.pop_front() // 删除表头元素
a.size() // 返回表长
a.empty() // 表为空时,返回真,否则返回假
a.resize(n) // 改变序列长度,超出的元素将会全部被删除,如果序列需要扩展(原空间小于n),元素默认值将填满扩展出的空间
a.resize(n, val) // 改变序列长度,超出的元素将会全部被删除,如果序列需要扩展(原空间小于n),val将填满扩展出的空间
a.clear() // 删除容器中的所有元素
a.front() // 返回首元素
a.back() // 返回尾元素
a.swap(v) // 将a与另一个list对象进行交换
a.merge(b) // 调用结束后b变为空,a中元素包含原来a和b的元素
a.insert(it, val) // 向迭代器it指向的元素前插入新元素val
a.insert(it, n, val)// 向迭代器it指向的元素前插入n个新元素val
a.insert(it, first, last)
// 将由迭代器first和last所指定的序列[first, last)插入到迭代器it指向的元素前面
a.erase(it) // 删除由迭代器it所指向的元素
a.erase(first, last)// 删除由迭代器first和last所指定的序列[first, last)
a.remove(x) // 删除了a中所有值为x的元素
a.assign(n, val) // 将a中的所有元素替换成n个val元素
a.assign(b.begin(), b.end())
//将a变成b
string
头文件:string
string是STL的字符串类型,通常用来表示字符串。而在使用string之前,字符串通常是用char*
表示的。
string和char*
的区别
string是一个类, char*
是一个指向字符的指针。
string封装了char*
,管理这个字符串,是一个char*
型的容器。也就是说string是一个容器,里面元素的数据类型是char*
。
string不用考虑内存释放和越界。
string管理char*
所分配的内存。每一次string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。 string提供了一系列的字符串操作函数
查找find,拷贝copy,删除erase,替换replace,插入insert.
构造和析构函数:
表达式 | 效果 |
---|---|
string s | 生成一个空字符串 |
string s(str) | copy构造函数,生成一个str的复制品 |
string s(str,idx) | 将string内始于位置idx的部分当作字符串s的初值 |
string s(str,idx,len) | 将string内始于位置idx且长度最多为len的部分当作字符串s的初值 |
string s(cstr) | 以C-string字符串cstr作为字符串s的初值 |
string s(cstr,len) | 以C-string字符串cstr的前len个字符作为字符串s的初值 |
string s(num,c) | 生成一个字符串,包含num个字符c |
string s(beg,end) | 以区间[beg,end]内所有字符作为字符串s的初值 |
操作函数:
操作函数 | 效果 |
---|---|
=,assign() | 赋以新值 |
swap() | 交换两个字符串的内容 |
+=, append(),push_back() | 添加字符 |
insert() | 插入字符 |
erase() | 删除字符 |
clear() | 移除全部字符 |
resize() | 改变字符数量 |
replace() | 替换字符 |
+ | 串联字符串 |
==,!=,<,<=,>,>=,compare() | 比较字符串内容 |
size(),length() | 返回字符数量,等效函数 |
max_size() | 返回字符的最大可能个数 |
empty() | 判断字符串是否为空 |
capacity() | 返回重新分配之前的字符容量 |
reserve() | 保留一定量内存以容纳一定数量的字符 |
[ ],at() | 存取单一字符 |
>>,getline() | 从stream中读取某值 |
<< | 将某值写入stream |
copy() | 将内容复制为一个C-string |
c_str() | 将内容以C-string形式返回 |
data() | 将内容以字符数组形式返回 |
substr() | 返回某个子字符串 |
begin(),end() | 提供正常的迭代器支持 |
rbegin(),rend() | 提供逆向迭代器支持 |
pair
头文件:utility
STL的utility头文件中描述了一个看上去非常简单的模版类pair,用来表示一个二元组或元素对,并提供了按照字典序对元素对进行大小比较运算符模版函数。 Example,想要定义一个对象表示一个平面坐标点,则可以:
pair<double, double> p;
cin >> p.first >> p.second;
map
头文件:map
在STL的头文件中map中定义了模版类map和multimap,用有序二叉树表存储类型为pair<const Key, T>
的元素对序列。序列中的元素以const Key部分作为标识,map中所有元素的Key值必须是唯一的,multimap则允许有重复的Key值。
可以将map看作是由Key标识元素的元素集合,这类容器也被称为“关联容器”,可以通过一个Key值来快速决定一个元素,因此非常适合于需要按照Key值查找元素的容器。 map模版类需要四个模版参数,第一个是键值类型,第二个是元素类型,第三个是比较算子,第四个是分配器类型。其中键值类型和元素类型是必要的。
定义map对象的代码示例:
map<string, int> m;
map的基本操作:
/* 向map中插入元素 */
m[key] = value; // [key]操作是map很有特色的操作,如果在map中存在键值为key的元素对, 则返回该元素对的值域部分,否则将会创建一个键值为key的元素对,值域为默认值。所以可以用该操作向map中插入元素对或修改已经存在的元素对的值域部分。
m.insert(make_pair(key, value)); // 也可以直接调用insert方法插入元素对,insert操作会返回一个pair,当map中没有与key相匹配的键值时,其first是指向插入元素对的迭代器,其second为true;若map中已经存在与key相等的键值时,其first是指向该元素对的迭代器,second为false。
/* 查找元素 */
int i = m[key]; // 要注意的是,当与该键值相匹配的元素对不存在时,会创建键值为key(当另一个元素是整形时,m[key]=0)的元素对。
map<string, int>::iterator it = m.find(key); // 如果map中存在与key相匹配的键值时,find操作将返回指向该元素对的迭代器,否则,返回的迭代器等于map的end()(参见vector中提到的begin()和end()操作)。
/* 删除元素 */
m.erase(key); // 删除与指定key键值相匹配的元素对,并返回被删除的元素的个数。
m.erase(it); // 删除由迭代器it所指定的元素对,并返回指向下一个元素对的迭代器。
/* 其他操作 */
m.size(); // 返回元素个数
m.empty(); // 判断是否为空
m.clear(); // 清空所有元素
stack
头文件:stack
stack模版类的定义在stack头文件中。 stack模版类需要两个模版参数,一个是元素类型,另一个是容器类型,但是只有元素类型是必要的,在不指定容器类型时,默认容器的类型为deque。
定义stack对象的示例代码如下:
stack<int> s;
stack<string> ss;
s.push(x); // 入栈
s.pop(); // 出栈
s.top(); // 访问栈顶
s.empty(); // 当栈空时,返回true
s.size(); // 访问栈中元素个数
queue
头文件:queue
queue模版类的定义在queue头文件中。 queue与stack相似,queue模版类也需要两个模版参数,一个元素类型,一个容器类型,元素类型时必须的,容器类型时可选的,默认为deque类型。
定义queue对象的示例代码必须如下:
queue<int> q;
queue<double> qq;
queue的基本操作:
q.push(x); // 入队列
q.pop(); // 出队列
q.front(); // 访问队首元素
q.back(); // 访问队尾元素
q.empty(); // 判断队列是否为空
q.size(); // 访问队列中的元素个数
set
头文件:set
set是与集合相关的容器,STL为我们提供了set的实现,在编程题中遇见集合问题直接调用是十分方便的。
定义set对象的示例代码如下:
set<int> s;
set<double> ss;
s.begin() // 返回指向第一个元素的迭代器
s.clear() // 清除所有元素
s.count() // 返回某个值元素的个数
s.empty() // 如果集合为空,返回true(真)
s.end() // 返回指向最后一个元素之后的迭代器,不是最后一个元素
s.equal_range() // 返回集合中与给定值相等的上下限的两个迭代器
s.erase() // 删除集合中的元素
s.find() // 返回一个指向被查找到元素的迭代器
s.get_allocator() // 返回集合的分配器
s.insert() // 在集合中插入元素
s.lower_bound() // 返回指向大于(或等于)某值的第一个元素的迭代器
s.key_comp() // 返回一个用于元素间值比较的函数
s.max_size() // 返回集合能容纳的元素的最大限值
s.rbegin() // 返回指向集合中最后一个元素的反向迭代器
s.rend() // 返回指向集合中第一个元素的反向迭代器
s.size() // 集合中元素的数目
s.swap() // 交换两个集合变量
s.upper_bound() // 返回大于某个值元素的迭代器
s.value_comp() // 返回一个用于比较元素间的值的函数
multiset
头文件:set
在set头文件中,还定义了另一个非常实用的模版类multiset(多重集合)。多重集合与集合的区别在于集合中不能存在相同元素,而多重集合中可以存在。
定义multiset对象的示例代码如下:
multiset<int> s;
multiset<double> ss;
bitset
头文件:bitset
在 STLSTL 的头文件中 bitset中定义了模版类 bitsetbitset,用来方便地管理一系列的 bitbit 位的类。bitsetbitset 除了可以访问指定下标的 bitbit 位以外,还可以把它们作为一个整数来进行某些统计。
bitsetbitset 模板类需要一个模版参数,用来明确指定含有多少位。
定义 bitsetbitset 对象的示例代码:
const int MAXN = 32;
bitset<MAXN> bt; // bt 包括 MAXN 位,下标 0 ~ MAXN - 1,默认初始化为 0
bitset<MAXN> bt1(0xf); // 0xf 表示十六进制数 f,对应二进制 1111,将 bt1 低 4 位初始化为 1
bitset<MAXN> bt2(012); // 012 表示八进制数 12,对应二进制 1010,即将 bt2 低 4 位初始化为 1010
bitset<MAXN> bt3("1010"); // 将 bt3 低 4 位初始化为 1010
bitset<MAXN> bt4(s, pos, n);// 将 01 字符串 s 的 pos 位开始的 n 位初始化 bt4
bitsetbitset 基本操作:
bt.any() // bt 中是否存在置为 1 的二进制位?
bt.none() // bt 中不存在置为 1 的二进制位吗?
bt.count() // bt 中置为 1 的二进制位的个数
bt.size() // bt 中二进制位的个数
bt[pos] // 访问 bt 中在 pos 处的二进制位
bt.test(pos) // bt 中在 pos 处的二进制位是否为 1
bt.set() // 把 bt 中所有二进制位都置为 1
bt.set(pos) // 把 bt 中在 pos 处的二进制位置为 1
bt.reset() // 把 bt 中所有二进制位都置为 0
bt.reset(pos) // 把 bt 中在pos处的二进制位置为0
bt.flip() // 把 bt 中所有二进制位逐位取反
bt.flip(pos) // 把 bt 中在 pos 处的二进制位取反
bt[pos].flip() // 同上
bt.to_ulong() // 用 bt 中同样的二进制位返回一个 unsigned long 值
os << bt // 把 bt 中的位集输出到 os 流
图模板
不带出入度的最简模板
#include <iostream>
#include <vector>
#include <set>
using namespace std;
#define MAX(a, b) ((a) > (b) ? (a) : (b) )
//定义图的定点
typedef struct Vertex {
int id;
vector<int> connectors; //存储节点的后续连接顶点编号
Vertex() : id(-1) {}
Vertex(int nid) : id(nid) {}
} Vertex;
//定义Graph的邻接表表示
typedef struct Graph {
vector<Vertex> vertexs; //存储定点信息
int nVertexs; //计数:邻接数
bool isDAG; //标志:是有向图吗
Graph(int n, bool isDAG) : nVertexs(n), isDAG(isDAG) { vertexs.resize(n); }
//向图中添加边
bool addEdge(int id1, int id2) {
if (!(MAX(id1, id2) < vertexs.size())) return false;
if (isDAG) {
vertexs[id1].connectors.push_back(id2);
}
else {
vertexs[id1].connectors.push_back(id2);
vertexs[id2].connectors.push_back(id1);
}
return true;
}
//广度优先搜索
vector<int> BFS(int start) {
set<int> visited;
vector<int> g, rst;
g.push_back(start);
visited.insert(start);
while(g.size() > 0) {
int id = g[0];
g.erase(g.begin());
rst.push_back(id);
for(int i = 0; i < vertexs[id].connectors.size(); i++) {
int id1 = vertexs[id].connectors[i];
if (visited.count(id1) == 0) {
g.push_back(id1);
visited.insert(id1);
}
}
}
return rst;
}
//深度优先搜索
vector<int> DFS(int start) {
set<int> visited;
vector<int> g, rst;
g.push_back(start);
//cout << "push " << start << " ";
visited.insert(start);
rst.push_back(start);
bool found;
while(g.size() > 0) {
int id = g[g.size()-1];
found = false;
for(int i = 0; i < vertexs[id].connectors.size(); i++) {
int id1 = vertexs[id].connectors[i];
if (visited.count(id1) == 0) {
g.push_back(id1);
rst.push_back(id1);
visited.insert(id1);
//cout << "push " << id1 << " ";
found = true;
break;
}
}
if (!found) {
int id2 = g[g.size()-1];
rst.push_back(-1 * id2);
//cout << "pop " << id2 << " ";
g.pop_back();
}
}
//cout << endl;
return rst;
}
} Graph;
int main() {
Graph g(8, false);
g.addEdge(0, 1);
g.addEdge(0, 3);
g.addEdge(1, 2);
g.addEdge(3, 4);
g.addEdge(3, 5);
g.addEdge(4, 5);
g.addEdge(4, 6);
g.addEdge(5, 6);
g.addEdge(5, 7);
g.addEdge(6, 7);
vector<int> bv = g.BFS(0);
cout << "宽度优先搜索节点顺序:";
for(int j = 0; j < bv.size(); j++)
cout << bv[j] << " ";
cout << endl;
cout << "深度优先搜索节点顺序:";
Graph g1(6, false);
g1.addEdge(0, 1);
g1.addEdge(0, 4);
g1.addEdge(0, 5);
g1.addEdge(1, 5);
g1.addEdge(4, 5);
g1.addEdge(5, 2);
g1.addEdge(5, 3);
g1.addEdge(2, 3);
vector<int> route = g1.DFS(0);
for(int i = 0; i < route.size(); i++)
cout << route[i] << " ";
cout << endl;
char ch;
cin >> ch;
return 0;
}
带出入度的 (2019推免试题)
#include <algorithm>
#include <iostream>
#include <vector>
#include <queue>
#define MAX(a, b) ((a) > (b) ? (a) : (b) )
using namespace std;
int n,m;
vector<int> inDegreelist,outDegreelist;
//定义图的定点
typedef struct Vertex {
int id,inDegree,outDegree;
vector<int> connectors; //存储节点的后续连接顶点编号
Vertex() : id(-1),inDegree(0),outDegree(0) {}
Vertex(int nid) : id(nid),inDegree(0),outDegree(0) {}
} Vertex;
//定义Graph的邻接表表示
typedef struct Graph {
vector<Vertex> vertexs; //存储定点信息
int nVertexs; //计数:邻接数
bool isDAG; //标志:是有向图吗
Graph(int n, bool isDAG) : nVertexs(n), isDAG(isDAG) { vertexs.resize(n); }
Graph() : nVertexs(1), isDAG(1) { vertexs.resize(1); }
//向图中添加边
bool addEdge(int id1, int id2) {
if (!(MAX(id1, id2) < vertexs.size())) return false;
if (isDAG) {
vertexs[id1].connectors.push_back(id2);
vertexs[id1].outDegree++;
vertexs[id2].inDegree++;
}
else {
vertexs[id1].connectors.push_back(id2);
vertexs[id2].connectors.push_back(id1);
vertexs[id1].outDegree++;
vertexs[id1].inDegree++;
vertexs[id2].outDegree++;
vertexs[id2].inDegree++;
}
return true;
}
} Graph;
Graph g;
void init(){
cin>>n>>m;
g=Graph(n, true);
int src,dst;
while(m--){
cin>>src>>dst;
g.addEdge(src,dst);
}
vector<Vertex>::iterator it = g.vertexs.begin();
while(it!=g.vertexs.end()){
inDegreelist.push_back(it->inDegree);
outDegreelist.push_back(it->outDegree);
it++;
}
}
int countin(int n){
return count(inDegreelist.begin(),inDegreelist.end(),n);
}
int countout(int n){
return count(outDegreelist.begin(),outDegreelist.end(),n);
}
bool Is_List(){
//有一个inDegree为0的头和一个outDegree为0的尾,且其余节点入度与出度都为1;
return (countin(0)==1)&&(countout(0)==1)&&(countin(1)==n-1)&&(countout(1)==n-1);
}
bool Is_Tree(){
//有一个inDegree为0的头且其余节点inDegree均为1,且不是链表;
return (countin(0)==1)&&(countin(1)==n-1);
}
bool topologicalSort(){//拓扑排序判断有环无环
int num=0;//记录加入拓扑排序的顶点数
queue<int> q;
for(int i=0;i<n;i++){
if(inDegreelist[i]==0){
q.push(i);//将所有入度为0的顶点入队
}
}
while(!q.empty()){
int u=q.front();//取队首顶点u
q.pop();
for(int i=0;i<g.vertexs[u].connectors.size();i++){
int v=g.vertexs[u].connectors[i];//u的后继节点v
inDegreelist[v]--;//v的入度减1
if(inDegreelist[v]==0){//顶点v的入度减为0则入队
q.push(v);
}
}
g.vertexs[u].connectors.clear();//清空u的所有出边
num++;//加入拓扑排序的顶点数加1
}
if(num==n) return true;//加入拓扑排序的顶点为n,则拓扑排序成功,图无环
else return false;//否则拓扑排序失败,图有环
}
int main(){
init();
if(n==0||m==0){
cout<<"error"<<endl;
}
if(Is_List()){
cout<<"list"<<endl;
}
else if(Is_Tree()){
cout<<"tree"<<endl;
}
else if(topologicalSort()){
cout<<"no ring"<<endl;
}
else{
cout<<"have ring"<<endl;
}
return 0;
}
图算法:找出u到v的所有路径-邻接表
#include<stdio.h>
#include<stdlib.h>
#ifndef BASE
#define BASE
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status;
typedef int bool;
#endif
#define VertexType char //点类型
#define VRType int //边类型
#define maxSize 100
void Visit(VertexType e) {
printf("%c", e);
}
#define MAX_VERTEX_NUM 20
typedef enum{DG, UDG} GraphKind;
typedef struct ArcNode{
int adjV; //边指向的顶点
VRType weight; //权重
struct ArcNode *next;
}ArcNode; //边
typedef struct VNode{
VertexType data;
ArcNode *firstarc;
}VNode, AdjList[MAX_VERTEX_NUM]; //顶点
typedef struct{
GraphKind kind;
int vernum,arcnum;
AdjList vers;
}ALGraph;
/*------------------------
|7.14 创建有向图的邻接表|
------------------------*/
Status InitGraph_AL(ALGraph *pG) { //初始化
int i;
pG->arcnum = 0;
pG->vernum = 0;
for (i=0; i<MAX_VERTEX_NUM; ++i)
pG->vers[i].firstarc = NULL; //VC++6.0中指针初始化为0xcccccccc
return OK;
}
int LocateVex_AL(ALGraph G, VertexType e) { //定位值为e的元素下标
int i;
for (i=0; i<G.vernum; ++i) {
if (G.vers[i].data == e) {
return i;
}
}
return -1;
}
Status CreateDG_AL(ALGraph *pG) { //创建有向图的邻接表
//输入规则:顶点数目->弧的数目->各顶点的信息->各条弧的信息
int i,a,b;
char tmp[MAX_VERTEX_NUM];
char h,t;
ArcNode *p, *q;
InitGraph_AL(pG); //VC++6.0中指针初始化为0xcccccccc,如果不将指针初始化为NULL,会出错
//图的类型
pG->kind = DG;
//顶点数目
scanf("%d", &i); if (i<0) return ERROR;
pG->vernum = i;
//弧的数目
scanf("%d", &i); if (i<0) return ERROR;
pG->arcnum = i;
//各顶点信息
scanf("%s", tmp);
for (i=0; i<pG->vernum; ++i) pG->vers[i].data=tmp[i];
//弧的信息
for (i=0; i<pG->arcnum; ++i) {
scanf("%s", tmp);
h = tmp[0]; t = tmp[2];
a = LocateVex_AL(*pG, h);
b = LocateVex_AL(*pG, t);
if (a<0 || b<0) return ERROR;
p = (ArcNode *)malloc(sizeof(ArcNode)); if (!p) exit(OVERFLOW);
p->adjV=b;p->next=NULL;
if (pG->vers[a].firstarc) { //已经有边了
for (q = pG->vers[a].firstarc; q->next; q=q->next) ; //找到最后一条
q->next = p;
} else { //第一条边
pG->vers[a].firstarc = p;
}
}
return OK;
}
/*----------------------------------------------------------------
|7.28 有向图-从u-v的所有简单路径 |
----------------------------------------------------------------*/
int visit[MAX_VERTEX_NUM]; //前面定义了
VertexType paths[maxSize][MAX_VERTEX_NUM]; //存放路径
int path[MAX_VERTEX_NUM]; //路径
int pathnum=0; //当前是第几条路径
void FindAllPath(ALGraph G, int u,int v,int k) { //u->v当前是第k个位置
int i;
ArcNode *p;
visit[u]=1; //走到了u
path[k]=u; //添加到路径->下标位置为k的结点是u(第k+1个是u)
if (u==v) { //找到了
for (i=0; i<=k; i++) {//复制到paths
paths[pathnum][i] = G.vers[path[i]].data;
}
paths[pathnum][i]='\0'; //结束符
pathnum++; //找下一条路径
} else {
//u的邻边开始找
for (p=G.vers[u].firstarc; p; p=p->next) {
if (visit[p->adjV]==0)
FindAllPath(G, p->adjV, v, k+1); //去这个邻接点找
}
}
// 回溯到上一个结点
// 注意:回溯应该写在外面-->也就是不管有没有找到都要回溯
visit[u]=0;
path[k]=0;
}
int main() {
/*7.28
6
11
ABCDEF
B,A
B,D
C,B
C,F
D,C
D,E
D,F
E,A
F,A
F,B
F,E
B->A
A->B
D->A
*/
int i,j;
int cnt;
ALGraph G;
char tmp[20];
CreateDG_AL(&G);
while (1) {
scanf("%s", tmp); //A->B
i = LocateVex_AL(G, tmp[0]);
j = LocateVex_AL(G, tmp[3]);
for (cnt=0; cnt<MAX_VERTEX_NUM; cnt++) visit[cnt]=0;
pathnum=0;
printf("7.28 输出所有 %c 到 %c 的路径\n", tmp[0], tmp[3]);
FindAllPath(G, i, j, 0);
if (pathnum==0) {
printf("\t- 走不通\n");
}
for (i=0; i<pathnum; i++) {
printf("\t%d %s\n", i+1, paths[i]);
}
}
return 0;
}
树模板
注释版
#include<bits/stdc++.h>
#include<cmath>
#define mem(a,b) memset(a,b,sizeof a);
using namespace std;
typedef long long ll;
const int maxn=50;
int mid[maxn],po[maxn],pr[maxn];
int first;
struct node
{
int l,r;
}T[maxn];
// 中序+先序=>二叉树
int mid_pr_build(int la,int ra,int lb,int rb) // la,ra:表示中序遍历 lb,rb:表示先序遍历
{
// 这里不能等于,因为假设:len==1,则la==ra,直接返回,但是实际上是有一个 rt 的,却没被建立
if(la>ra) return 0;
int rt=pr[lb]; // 因为先序遍历第一个是根节点
int p1=la,p2;
while(mid[p1]!=rt) p1++; // 在中序遍历中找到根节点
p2=p1-la;
T[rt].l=mid_pr_build(la,p1-1,lb+1,lb+p2); // 左子树(锁定左子树范围的下标)
T[rt].r=mid_pr_build(p1+1,ra,lb+p2+1,rb); // 右子树(锁定右子树范围的下标)
return rt;
}
// 中序+后序=>二叉树
int mid_po_build(int la,int ra,int lb,int rb) // la,ra:表示中序遍历 lb,rb:表示后序遍历
{
if(la>ra) return 0;
int rt=po[rb]; // 因为后序遍历最后一个是根节点
int p1=la,p2;
while(mid[p1]!=rt) p1++; // 在中序遍历中找到根节点
p2=p1-la;
T[rt].l=mid_po_build(la,p1-1,lb,lb+p2-1); // 左子树(锁定左子树范围的下标)
T[rt].r=mid_po_build(p1+1,ra,lb+p2,rb-1); // 右子树(锁定右子树范围的下标)
return rt;
}
// 求树高
int getHeight(int rt)
{
if(rt==0) return 0;
return 1+max(getHeight(T[rt].l),getHeight(T[rt].r));
}
// 层序遍历
void bfs(int rt)
{
queue<int> q;
vector<int> v;
q.push(rt);
while(!q.empty())
{
int w=q.front();
q.pop();
v.push_back(w);
if(T[w].l!=0) q.push(T[w].l);
if(T[w].r!=0) q.push(T[w].r);
}
int len=v.size();
for(int i=0;i<len;i++)
printf("%d%c",v[i],i==len-1?'\n':' '); // 推荐这种写法,简洁
}
// 先序遍历
void preT(int rt)
{
if(rt==0) return;
printf(first?first=0,"%d":" %d",rt);
preT(T[rt].l);
preT(T[rt].r);
}
// 中序遍历
void midT(int rt)
{
if(rt==0) return;
midT(T[rt].l);
printf(first?first=0,"%d":" %d",rt);
midT(T[rt].r);
}
// 后序遍历
void postT(int rt)
{
if(rt==0) return;
postT(T[rt].l);
postT(T[rt].r);
printf(first?first=0,"%d":" %d",rt);
}
int main()
{
int n;
while(~scanf("%d",&n))
{
first=1;
for(int i=0;i<n;i++) scanf("%d",&po[i]); // 后序结点
// for(int i=0;i<n;i++) scanf("%d",&pr[i]); // 先序结点
for(int i=0;i<n;i++) scanf("%d",&mid[i]); // 中序结点
int rt=mid_po_build(0,n-1,0,n-1); // 中+后,返回根节点
// int rt=mid_pr_build(0,n-1,0,n-1); // 中+先,返回根节点
bfs(rt); // 层序遍历
// preT(rt); // 先序遍历
// puts("");
// postT(rt); // 后序遍历
// puts("");
// midT(rt); // 中序遍历
// puts("");
}
return 0;
}
简化版(Val As Index,若数据不在1~N内,则可能越界)
#include<bits/stdc++.h>
#include<cmath>
#define mem(a,b) memset(a,b,sizeof a);
using namespace std;
typedef long long ll;
const int maxn=50;
int mid[maxn],po[maxn],pr[maxn];
int first;
struct node
{
int l,r;
}T[maxn];
int mid_pr_build(int la,int ra,int lb,int rb)
{
if(la>ra) return 0;
int rt=pr[lb];
int p1=la,p2;
while(mid[p1]!=rt) p1++;
p2=p1-la;
T[rt].l=mid_pr_build(la,p1-1,lb+1,lb+p2);
T[rt].r=mid_pr_build(p1+1,ra,lb+p2+1,rb);
return rt;
}
int mid_po_build(int la,int ra,int lb,int rb)
{
if(la>ra) return 0;
int rt=po[rb];
int p1=la,p2;
while(mid[p1]!=rt) p1++;
p2=p1-la;
T[rt].l=mid_po_build(la,p1-1,lb,lb+p2-1);
T[rt].r=mid_po_build(p1+1,ra,lb+p2,rb-1);
return rt;
}
int getHeight(int rt)
{
if(rt==0) return 0;
return 1+max(getHeight(T[rt].l),getHeight(T[rt].r));
}
void bfs(int rt)
{
queue<int> q;
vector<int> v;
q.push(rt);
while(!q.empty())
{
int w=q.front();
q.pop();
v.push_back(w);
if(T[w].l!=0) q.push(T[w].l);
if(T[w].r!=0) q.push(T[w].r);
}
int len=v.size();
for(int i=0;i<len;i++)
printf("%d%c",v[i],i==len-1?'\n':' ');
}
void preT(int rt)
{
if(rt==0) return;
printf(first?first=0,"%d":" %d",rt);
preT(T[rt].l);
preT(T[rt].r);
}
void midT(int rt)
{
if(rt==0) return;
midT(T[rt].l);
printf(first?first=0,"%d":" %d",rt);
midT(T[rt].r);
}
void postT(int rt)
{
if(rt==0) return;
postT(T[rt].l);
postT(T[rt].r);
printf(first?first=0,"%d":" %d",rt);
}
int main()
{
int n;
while(~scanf("%d",&n))
{
first=1;
for(int i=0;i<n;i++) scanf("%d",&po[i]);
// for(int i=0;i<n;i++) scanf("%d",&pr[i]);
for(int i=0;i<n;i++) scanf("%d",&mid[i]);
int rt=mid_po_build(0,n-1,0,n-1);
// int rt=mid_pr_build(0,n-1,0,n-1);
bfs(rt);
// preT(rt);
// postT(rt);
// midT(rt);
}
return 0;
}
简化版(Val Not As Index,可以存任意的 Val)
#include<bits/stdc++.h>
#include<cmath>
#define mem(a,b) memset(a,b,sizeof a)
#define ssclr(ss) ss.clear(), ss.str("")
#define INF 0x3f3f3f3f
#define MOD 1000000007
using namespace std;
typedef long long ll;
const int maxn=5e4+1000;
int f;
int pre[maxn], in[maxn];
struct node
{
int l,r,d;
}T[maxn];
int create(int l1,int r1,int l2,int r2) // in pre
{
if(l2>r2) return -1;
int rt=l2;
int p1=l1,p2;
while(in[p1]!=pre[rt]) p1++;
p2=p1-l1;
T[rt].d=pre[rt];
T[rt].l=create(l1,p1-1,l2+1,l2+p2);
T[rt].r=create(p1+1,r1,l2+p2+1,r2);
return rt;
}
void postT(int rt)
{
if(rt==-1 || !f) return;
postT(T[rt].l);
postT(T[rt].r);
if(f) f=0, printf("%d\n",T[rt].d);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&pre[i]);
for(int i=0;i<n;i++) scanf("%d",&in[i]);
int rt=create(0,n-1,0,n-1);
f=1, postT(rt);
return 0;
}