三元运算符
三元运算符只支持表达式而不支持语句
一.表达式:
定义:由操作符和操作数构成的式子。
操作数就是常量或变量。
操作符则是+,=,/,*,/=,(),[],&,&&等等等等,也可以叫做运算符。
三元表达式注意点:
....
int main() {
// cout << 三元表达式的注意点,每行单独 cout << 一个三元表达式后,在这行后面就不要再输出其他内容了。
cout << a && b ? 1 : 0 << " "; // 会报错
cout << " "; // 不会报错
cout << (a || b) ? 1 : 0; // 和 cout << a || b ? 1 : 0; 不一样
cout << (a & !b || !a &b) ? 1 : 0;
cout << !(a | b) ? 1 : 0;
}
注:任何表达式都是有返回值的。可以理解为表达式本身也有值。
关闭输入输出同步流
ios::sync_with_stdio(false);
常用函数
ceil 返回的是大于x的最小整数 若a / b (均为int类型)则可以用 (a + b - 1) / b 来实现向上取整
floor(x) 返回的是小于或等于x的最大整数
一些等价写法
!= -1 和 ~
循环语句
1.while的多种用法
while (true) {
cin >> x;
if (!x) break;
}
while (cin >> x && x) {} // 上个while语句中的cin >> x是可以写到while()里的,cin本身是有返回值的,如果返回0,表示没有读到值(读到了结束的位置,即读到了文件结束符EOF(-1),返回0或者false,表示输入结束)。
while (cin >> x, x) {} // 逗号表达式的值等于最后一个表达式的值
// scanf读到结束会返回-1
while也可以用来控制循环次数,比如知道了循环次数n,使用while(n–)即可控制循环次数(因为先判断后才减一),但是循环体里若要用到n,循环体里的 n 已经执行过判断后的 i-- 了,如最开始 n == 3 , 经过判断且 n-- 后,首次进入循环体里的 n 会是 2 ,所以最好不要这样,不如用for循环。
2.判断奇数
判断一个整数是否为奇数时,不能用 i % 2 == 1 ,当 i 为负奇数时,会返回负数(当取余操作返回一个非零的结果时,它与左操作数具有相同的正负符号。因此,用 % 2 判断是否为奇数时,不为奇数时返回得到0,为奇数时,可以得到 -1 和 1。),因此无法判断。
因此,可以使用 i % 2 (i % 2 在判断语句里等价于 i % 2 != 0 )。
还可以使用 (i & 1) != 0 (因为奇数的二进制最后一位为1,偶数的二进制最后以为0。)
数组
数组中是会存其长度的
定义在函数外数(全局变量)的数组会保存在堆空间,若未初始化默认值都会是0。
定义在函数内的数组会保存在栈空间,若未初始化默认值是随机的。
感觉还可以把数组的下标理解为前面有几个数
最好不要用变量来定义一个数组的长度,有的编译器可能不支持,最好用一个常量。
1.初始化方法
①一维数组初始化方法
int a[3] = {0, 1, 2};
int b[] = {0, 1, 2};
int c[5] = {0, 1, 2}; //定义了一个长度是5的数组,没有赋值的元素,默认值为0。
int f[10] = {0}; //定义了一个长度为10的数组,元素值均为0。
②多维数组初始化方法
多维数组在C++中是通过转化成一位数组实现的
int b[3][4] = {{1, 2, 3, 4}, {2, 2, 3, 4}, {3, 2, 3, 4}};
int b[3][4] = {{1, 2, 3, 4},
{2, 2, 3, 4},
{3, 2, 3, 4}};
③memset函数
用里的memset函数,如memset(a, 0, 40),其中a为数组名,第二位为数组中每一个字节所要赋的值(只有赋0或者负数的时候才好实现存入什么初始化为什么的功能,如第二位为1,是将每一个字节赋值为0000001),40为所要初始化的长度(0是int类型,若想将数组的10个元素初始化为Int类型的数据,则要将初始化的长度设置为40),第三位也可以直接用sizeof()来设置(size of 可以不加括号,如size of a)。
memset(a, -1, size of a); //将a数组的所有元素均初始化为-1
//memset会比下面这种方法初始化快很多
for (int i = 0; i < 1000; i++) a[i] = -1;
字符串
1.强制类型转换
常用ASCII码:A~Z : 65~90 a~z : 97~122 0 ~9 : 48~57
2.字符数组
字符串就是字符数组加上结束符’\0’
用字符串来初始化字符数组时要注意,每个字符串结尾会暗含一个’\0’字符,因为字符数组的长度至少要比字符串的长度多1。
#include <iostream>
using namespace std;
int main() {
// 只是一个普通的字符数组
char a1[] = {'C', '+', '+'};
// 长度为4的字符数组,存储的字符串的长度是3。
char a2[] = {'C', '+', '+', '\0'}
// 如果定义数组大小为3,则会报错。
char a3[4] = "C++";
// 有可能会输出两个C++,会一直读到a2数组的结束符(a1和a2开辟的地址恰巧连续)。
cout << a1;
}
3.字符数组的输入和输出
输入
数组名→首元素地址
scandf和cin 一般是读到空格、回车或者是结束符就会停止
如果想读入一行字符串(就算有空格):gets() 不安全,已经被淘汰了,可以用fgets(参数①,参数②,参数③) 函数,参数①为要读入的字符串名,参数②为读入的字符长度的最大值,参数③为读入的文件,可以为stdin。如 fgets(s, 100, stdin); // 读入一行到字符串或字符数组里
cin.getline(①,②); // 读用字符数组初始化的字符串
getline(参数①,参数②),如getline(cin, string); // 读String类型的字符串
char s[100]此类的字符数组形式的字符串,在使用scanf输入时,只需要scanf(“%s”, s); 即可,s不需要加$。
string无法用scanf读,cin可以读。
输出
// puts();
char str[100] = {'z', 'e', 'c'};
puts(str); // zec
cout << str; // zec
4.字符数组的常用操作
引入头文件 #include <string.h> 或 #include
①求长度 strlen(str) // 不会包含\0
②比较 strcmp(a, b) // 按照字典序来比较,a > b 成立返回1,不成立返回0。
注意:若string想要使用 strcmp(); 则应该使用c_str(),如strcmp(a.c_str(), b.c_str()); 。
③复制 strcpy(a, b) // 将字符串b复制给从a开始的字符数组
5.遍历字符数组中的字符
#include <iostream>
#include <string.h>
using namespace std;
int main() {
char a[100] = "hello world";
// strlen()最好不要放到判断的部分,不然效率很低,每次判断都要执行一次strlen()。放到初始化部分里,则可以只用执行一次即可。
for (int i = 0, len = strlen(a); i < len; i++)
cout << a[i];
return 0;
}
// 也可以不用求字符串长度,for (int i = 0; a[i]; i++)
6.标准库类型 string
有,可以不用写。
①定义和初始化
#include <iostream>
#include <cstring>
using namespace std;
int main() {
string s1; // 空字符串
string s2 = s1; // s2时s1的一个副本
string s3 = "chao" // s3是该字符串字面值的一个副本
string s4(10, 'c') // s4的内容是:cccccccccc
}
string 类型的数据可以直接使用 cin 和 cout 来输入和输出。无法使用 scanf 来输入,可以使用 printf 来输出,如printf(“%s\n”, s1.c_str());,不过不能用 printf 直接输出string。
输入:
string s1, s2;
getline(cin, s1); // getline有空格也没事
cin.getline(s2, 100);
cin >> s1; // 只能读入第一个字符串
②操作
①empty();
②size(); // 复杂度为o(1),在string里专门有一个存放string长度的变量,执行很快;size 是无符号整数,因此 s.size() <= -1 一定成立。
③:比较,支持 > < >= <= == != 等所有比较操作,按字典序进行比较。
④:两个string可以相加
⑤:字面值和string对象相加
在做加法运算时,字面值和字符都会被转化成string对象,因此直接相加就是把这些字面量串联起来。
字符串进行相加操作时,必须确保每个加法运算符的两侧的运算对象至少有一个是string类型。
⑥:substr(); 如 a.substr(i, len); 第一个参数为开始的位置,第二个参数为所要截取的长度。如 a.substr(i) 从位置i开始一直截取到最后。
③string对象中的字符
如何处理string对象中的字符?
将string对象当成字符数组来处理或使用基于范围的for语句
string s = "Hello world";
for (int i = 0; i < s.size; i++) cout << s[i] << endl;
for (char c : s) cout << c << endl;
for (char $c : s) c++ // 可以改变s的值,上面char c : s 则不行。
函数
声明的时候可以只写函数类型
①数组形参
在函数中队数组中的值进行修改,会影响函数外面的数组。
一维数组形参的写法:
// 三种等价写法
void print(int *a) {};
void print(int a[]) {};
void print(int a[10]) {};
多维数组的写法:
// 除了第一维之外,其余维度的大小必须指定。
void print(int(*a)[10]) {};
void print(int a[][10]) {};
②:函数参数默认值
只能给最后面连续的几个参数定义(也可以全都定义默认值)
int abc(int a = 0, int b = 0, int c = 0) {};
int abc(int a, int b = 0, int c = 0) {};
int abc(int a, int b, int c = 0) {};
int abc(int a = 0, int b) {}; // 报错
③:sizeof()
void asize(int a) {}
结构体、类、指针、引用
struct Person {
int age, height;
double money;
person() {};
person(int a_age, int _height, double _money) {
age = _age;
height = _height;
money = _money;
}
};
结构体运算符重载
// 如该题:三元组排序,定义一个结构体,根据int x 的大小升序排列。
#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 10000;
struct Data {
int x;
double y;
string z;
bool operator< (const Data &t) const { // 结构体中重载小于号
return x < t.x;
}
}a[N];
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) cin >> a[i].x >> a[i].y >> a[i].z;
sort(a, a + n); // sort(begin(), end());
for (int i = 0; i < n; i++) {
cout << fixed << setprecision(2) << a[i].x << ' ' << a[i].y << ' ' << a[i].z << endl;
}
}
指针
堆从下往上开辟空间
栈从上往下开辟空间
指针指向存放变量的值的地址,因此我们可以通过指针来修该变量的值。类似通过数组下标来修改数组里面的值。
数组名是一种特殊的指针,指针可以做运算。
// 存放一个数据的地址,即可实现指向该数据。
int a = 10;
int* p = &a; // 定义了一个int类型的指针p,p指向a的地址
cout << *p; // 10, *相当于一种操作符。p指向a,然后通过*p即可访问a的值。
cout << 10;
*p = 23;
cout << *p; // 23
cout << a; // 23
int** q = &p; // 指针的指针,此处指针q为存放指针p的地址。
int*** o = &q;
引用
引用和指针类似,相当于给变量起个别名。
int a = 23;
int &p = a; // 引用、别名
链表
如定义一个链表的节点的结构体,val则为该节点的值,next则为一个存放了该节点指向的下一个节点的地址的指针,即next的值为下一个节点的地址,next指向下一个节点。
struct Node {
int val;
Node* next; // 定义一个指针,存放下一个节点的地址,注意节点的地址和节点存放的地址的区别。
Node(int_val) : val(_val), next(NULL) {};
};
Node a = node(1);
a.next, a.val; // 如果调用是一个变量,不是一个指针,需要这样。
// C++中可以用new:生成一个结构体,然后把这个结构体的指针放到p里
Node* p = new Node(1); // 加new返回的是地址,不加new返回的是值。
p->next = p; // next指针,是可以指向另外一个地址的,此处p指向自己。且此处p为一个指针,所以要用->调用。可以理解为用->来访问其成员变量。
auto q = new node(2); // 此处 auto 能自动推断出q为Node*
p->next = q; // 让p指向q,然后q会指向一个空指针。
auto o = new node(3);
q->next = o; // 让q指向p,然后o会指向一个空指针。
头结点:一般会把头节点的地址存到 head 里面,一般说的是第一个节点的地址,而不是第一个节点本身(值)。
遍历单链表:
Node* p = new Node(1); // 指针只p存了该节点的地址,指针p所指向的内容为node结构体类型。Node* p 表示p是一个返回Node结构体类型数据的指针,。
Node* q = new Node(2);
Node* o = new Node(3);
p->next = q; // 将q指向的节点(因为指针q存放了该节点的地址,则指针q可以指向该节点,如上面*p即可访问指针p指向的数据的值)的地址赋给p指向的节点存放的地址,即可以实现p存放的节点指向q存放的节点。
q->next = o;
Node* head = p; // 把指针p指向的节点的地址(指针p的值是一个节点的地址)给指针head。
// 链表的删除:将这个点跳过去或者遍历时遍历不到这个点
// 删除节点,如删除val = 1的节点
head->next = head->next-next;
// 在链表中添加节点
Node* u = new Node(4); // 刚创建出来时只会指向一个空指针
u->next = head; // head中本来存放的是指针p的值(即指针p指向的节点(之前的头节点(存放在指针head中))的地址),即此时将之前的头结点的地址赋给指针u,即可以实现此时u指向的节点指向之前的头节点(此时指针u存放的是之前的头节点的地址)。
head = u; // 更新指针head存放的头节点的地址
for (Node* i = head;i != null; i->next) { // head的值是node结构体类型数据(此处为一个节点)的地址,将这个值赋给i,则i指向的值自然也为一个node结构体类型的数据,因此此处也要 Node* i。指针i存放的是一个node结构体类型的数据的地址(此处即一个节点的地址),该节点的地址的值则为该节点(该地址中存放了该节点),因为再通过->即可实现访问该结构体类型数据的成员变量(访问该节点存放的下一个节点的地址)。
cout << i->val << endl;
}
空节点的写法:
0 NULL nullptr
STL容器、位运算与常用库
STL容器
除了队列,优先队列,栈,其它所有的STL元素都有 clear() 函数。
vector
在数组结尾插入或删除是o(1),在数组开头插入或删除是o(n)。
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> a;
vector<int> b[23];
vector<vector<int> c; // 相当于一个二维数组
a.size(); // 返回vector的实际长度(包含的元素个数),时间复杂度o(1)。
a.empty(); // 返回bool类型,表明vector是否为空,时间复杂度o(1)。
a.clear(); // 把当前vector的元素清空
// 迭代器(一般用得少)
//vector的迭代器是随机访问迭代器,可以把vector的迭代器与一个整数相加减,其行为和指针的移动相似。可以把vector的两个迭代器相减,其结果也和指针相减类似得到两个迭代器对应下标之间的距离。
// 声明了一个保存int类型数据的vector的迭代器
vector::iterator it = a.beagin(); // begin函数返回指向vector中第一个元素的迭代器。例如a是一个非空的vector,则*a.begin()与a[0]的作用相同。
*it; // 即可以取it的值
// 所有的容器都可以视作一个 前闭后开 的结构, end函数返回vector的尾部(最后一个元素的下一个位置,相当于没有),即第n个元素再往后的边界。*a.end()与a[n]都是越界访问,其中n == a.size()。
// front()取第一个元素
cout << a.front << a[0] << *a.begin() << endl; // 三种写法都等价
// back()取最后一个元素
cout << a.back() << a[a.size() - 1] << endl; // 两种等价写法
a.push_back(23); // 在最后插入一个元素
a.pop_back(); // 删除最后一个元素
struct Rec {
int x, y;
};
vector<Rec> c; // 其类型也可以是一个结构体
// vector自带比较运算,按照字典来比较,先比较两个数里的第一个位置,如果一样再比较第二个位置。
}
遍历vector
//下面代码都实现了遍历 vector<int>a,并输出所有的元素。
for (int i = 0; i < a.size(); i++) cout << a[i] << endl;
for (vector<int>::iterator it = a.begin(); it != a.end(); it++) cout << *it << endl;
for (auto i = a.begin(); i != a.end(); i++) cout << *i << endl;
for (int x : a) cout << x << ' ';
queue(队列)和优先队列(堆)
进行插入操作的端称为队尾,进行删除操作的端成为队头。
队列:先进先出
#include主要包括循环队列queue和优先队列priority_queue(出的时候会优先出最大值)两个容器。
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<int> q;
queue<double> a;
q.push(1); // 插入元素
q.pop(); // 弹出元素,并返回该元素。
q.front(); // 返回对头元素
q.back(); // 返回队尾元素
struct Rec {
int a, b;
bool operator< (const Rec& t) const { // 重载的写法
return a < t.a;
}
}; // 若要定义结构体类型的priority_queue,结构体中必须重载小于号。如果用的是小根堆的话,就要重载大于号。
queue<Rec> b;
priority_queue<int> a; // 默认是大根堆
priority_queue<int,vector<int>,greater<int>> b; // 小根堆
priority_queue<pair<int,int>> q;
a.push(1); // 插入一个数
a.top(); //取最大值
a.pop(); // 删除最大值
// 清空一个队列,初始化即可。
q = queue<int>();
}
stack
栈,先进后出。
函数栈和这个栈不一样,函数栈只是逻辑上是一个栈,是用汇编实现的。
#include <iostream>
#include <stack>
int main() {
stack<int> stk;
stk.push(1);
stk.top();
stk.pop();
}
双端队列deque
两头都可进可出,均为o(1)。
支持随机存储
#include <deque>
...
deque<int> a;
a.begin(), a.end();
a.front(), a.back();
a.push_back(1), a.push_front(2);
a[0];
a.pop_back(), a.pop_front();
a.clear();
set
底层是用平衡树实现的
用来实现动态维护一个有序的集合(只有key)
#include
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> a; // 元素不能重复
multiset<int> b; // 元素可以重复
multiset<double> c;
struct Rec {
int x, y;
bool operator< (const Rec& t) const { // 必须要重载小于号
return x < t.x;
}
}
// 与vector类似
a.size();
a.empty();
a.clear();
// set也支持迭代器,set和multiset的迭代器称为“双向访问迭代器”,不支持随机访问,支持 * 解除引用,只支持 "++" 和 "--"两个与算术相关的操作。
set<int>::iterator it; // 若it+,则it会指向下一个元素(只在元素从小到大排序的结果中,排在it下一名的元素),it--,同理。
it = a.begin(); // 指向集合中最小元素的迭代器
it++, it--;
++it, --it;
a.end(); // set和vector一样是一个前闭后开的容器,end()是用来指向集合中最大元素的下一个位置的迭代器,因为--a.end()指向的是集合中最大的元素的迭代器。
a.insert(x); // 若元素已存在,则不会重复插入元素。时间复杂度o(logn)。
a.find(x); // 在集合中查找等于x的元素,并返回指向该元素的迭代器,若不存在则返回 a.end()。时间复杂度为o(logn)。
if (a.find(x) == a.end()); // 判断x在a中是否存在
// lower_bound/upper_bound,这两个函数的用法与find类似,但查找的条件略有不同,时间复杂度为o(logn)。
// 注意都是查找最小的一个,但一个是大于等于,一个是大于。
a.lower_bound(x); // 查找大于等于x的元素中最小的一个,并返回相应的迭代器。
a.upper_bound(x); // 查找大于x的元素最小的一个,并返回相应的迭代器。
a.erase(it); // 若it是一个迭代器,则表示从s中删除迭代器it指向的元素,时间复杂度为o(logn)。
a.erase(x); // 若x是一个元素,则表示从a中删除所有等于x的元素。时间复杂度为o(k+logn)。
a.count(x); // 返回集合中和x相等的元素的个数,时间复杂度为o(k+logn),k为元素的个数。
}
map
一种映射
其内部是现实一颗以key为关键字的红黑树
map的key和value可以是任意类型,其中key必须定义小于号运算符。
int main() {
// map容器是一个键值对key-vlue的映射
map<int, int> a; // 其类型为一个二元组
a[1] = 2;
a[1000] = 23;
cout << a[1];
cout << a[1000];
map<string, int> b;
b["zec"] = 2;
cout << b["zec"];
map<string, vector<int>> c;
c["zec"] = vector<int>();
cout << a["zec"].size() << endl;
c["zec"] = vector<int>({0, 1, 2, 3, 4});
cout << a["yxc"].[2] << endl; // 3
a.insert({"lzy", {0, 1, 2, 3, 4}}); // 插入一个二元组
// size/empty/clear/begin/end 均与set类似
// insert/erase 与set类似,但其参数均为pair<key_type, <value_type>。
// a.find(x) 在变量名为h的map中查找key为x的二元组
}
pair
pair就是一个二元组
int main() {
pair<int, string> a, b;
a = make_pairJ(4, "abc");
if (a == b) {}; // 可进行比较,如 < > <= >= != 等,pair自带比较运算。比较时是双关键字比较,先比较first,再比较second。
cout << a.first; // 4
cout << a.second; // abc
}
unordered_set
unordered_set 和 unorder_multiset
底层是用哈希表实现的
用法和set一样,除了没有 lower_bound 和 upper_bound 。其他相同的用法,unordered_set 的操作的时间复杂度均为o(1)。
缺点:不支持二分
#include <unordered_set>
...
int main() {
unordered_set<int> a;
unordered_multiset<int> b; // 可以存储重复的元素
}
unorder_map
#include <unorder_map>
...
int main() {
unorder_map<int,int> a;
// unorder_multimap 不常用
}
bitset
用来进行位运算的容器
#include <bitset>
...
int main() {
bitset<1000> a; // 定义的是长度
a[0] = 1;
a[1] = 1;
a.set(23); // 把第23位设置成1
a.reset(23); // 把第23位设置成0
cout << a.count() << endl;
}
位运算
/*
(AND)与 &
(OR)或 |
(NOT)取反 ~
(XOR)异或 ^ 其运算可以看成不进位的加法
进行位运算时,每一位分别进行位运算即可。
<< 左移k位等价于乘以2的k次方
>> 右移k位等价于除以2的k次方
*/
常用操作
x >> k & 1; // 求x的第k位数字(从右往左,第0位开始,右移k位,把该数二进制下的这个要求的位的数左移到最低位,即可和1(0...0...01)最低位的1进行比较,与出来的结果就是要求的位上的数(由于1除最低外全都是0,因此对应比较的位也都是0,因此不用管,就比较最低位就行了,因此要把要求的位移到最低位)。
/*
如7的二进制是1011,1的二进制是0...0...01
不左移,求该数二进制下的第0位数字,1011 & 0001 ,后三位101每一位和000每一位分别进行与运算,必定都是0,再看第0位1和1进行与运算,为1,则求出第0位数字为1。
左移1位,为0101,与0001进行与运算,由于0001后三位都是0,和0101进行与运算,后三位也必定都为0,而此时的第0位1(原来的第一位)和0001的第0位1进行与运算,得到1(也就是所要求的位上的值,因为后面几位和1的后几位0与运算必定为0,然后让要求的这位左移到第0位,则可以和1与,与运算的结果为1。若这位为0,则和1与运算的结果为0,若为1,则和1与运算的结果与1,因此能实现。
*/
// -x 取反再加1:~x + 1(0...0...01)
lowbit(x) = x & -x; //返回x的最后一位1(从左往右)
// 如:10010 则会得到00010
算法库algorithm
1.reverse
reverse(a.begin(), a.end()); // 反转一个vector
reverse(a + 1, a + 1 + n);
2.unique
判重,把不重复和重复了但不多余的数(如三个1中的一个1)放到end前。
返回去重后的尾迭代器(或指针),任然为前闭后开,即这个迭代器是去重之后末尾元素的下一个位置。该函数常用于离散化,利用迭代器(或指针)的减法,可计算出去重后的元素个数。
#include <iostream>
#include <algorithm>
#include <vector>
int main {
int a[] = {1, 1, 2, 2, 3, 3, 4};
int m = unique(a, a + 7) - a; // 闭 开
cout << m;
for (int i = 0 ; i < m; i++) {
cout << a[i] << ' '; // 1 2 3 4
}
vector<int> b({1, 1, 2, 2, 3, 3, 4});
// int n = unique(a.begin(), a.end()) - b.end();
a.erase(unique(a.begin(), a.end()), a.end()); // 效果和上面一样,erase的区间(左闭右开)为unique返回的尾迭代器和原来a的end,则实现了删除了重复且多余的数。
for (auto x : a) cout << x << ' ' << endl; // 1 2 3 4
}
3.random_shuffle
4.sort
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> a({1, 2, 3, 4, 5});
sort(a.begin(), a.end()); // 升序
sort(a.begin(), a.end(), greater<int>); // 降序
// 第三个参数还可以自定义
bool cmp(int a, int b) { // 若a>b成立,则a排在b前面。(a是否应该排在b前面)
return a > b;
}
sort(a.begin(), a.end(), cmp);
//还可以给结构体排序
}
5.lower_bound/upper_bound
6.next_permutation
可以求出比当前数组排列大的下一个排列
#include <algorithm>
...
int main() {
vector<int> nums;
next_permutation(nums.begin(), nums.end());
}