Ⅰ、STL
一、vector(变长数组)
变长数组,基本思想是倍增。注意这里“变长”的含义与C99中的不同,这里的“变长”指的是数组长度可动态变化,但是C99中的“变长”指的是可以以变量作为数组长度,但数组长度仍然是不变的。
1.初始化
方法1:vector<int> a(10,3);
表示定义了一个长度为10的vector,它里面的每个数字都是3。vector<int> a[10];
表示定义了一个vector数组,里面有10个vector。a.clear()表示清空vector。
2.支持的函数
a.size()返回a这个vector里元素的个数。a.empty()返回a这个vector是不是空的。(size和empty所有容器都有,它们俩的时间复杂度都是O(1))a.front()返回vector的第一个数,a.back()返回vector的最后一个数,a.push_back()向vector的最后插入一个数,a.pop_back()把vector的最后一个数删掉。迭代器:begin是vector的第一个数,end是vector的最后一个数的后面一个数。vector支持随机寻址。vector的三种遍历方式如下:
vector<int> a;
for(int i = 0;i<10;i++) a.push_back(i);
//下标遍历
for(int i = 0;i<a.size();i++) cout<<a[i]<<endl;
//迭代器遍历
for(vector<int>::iterator i = a.begin();i!=a.end();i++) cout<<*i<<endl;
//C11新语法,auto表示让系统自动推断变量类型。
for(auto x : a) cout<<x<<endl;
3.倍增的思想
系统为某个程序分配空间时,所需时间与空间大小无关,与申请次数有关。如一次申请1000大小的数组与申请1000次大小为1的数组,后者所需的时间可能是前者的1000倍。vector是这样工作的:每次当数组长度不够时,就把数组长度乘以2,并把原来的数据copy进来。这样可以减少申请次数。
二、string(字符串)
1.substr()
返回某个子串。第一个参数是起始位置,第二个参数是长度。
2.c_str()
返回str这个字符数组的头指针
三、queue(队列,先进先出)
1.push()
往队尾插入。
2.front()
返回队头元素。
3.pop()
把队头弹出。
4.back()
返回队尾元素。
queue没有cleaer函数,如果我们想清空一个队列,直接重新构造一个队列即可。
四、priority queue(优先队列,即堆,默认大根堆)
1.push()
往堆里插入一个元素。
2.top()
返回堆顶。
3.pop()
把堆顶弹出。
若想定义小根堆,直接插入相反数即可。那么相反数的大根堆就是小根堆。或:
priority_queue<int,vector<int>,greater<int>> q;
五、stack(栈)
1.push()
往栈顶添加一个元素。
2.top()
返回栈顶元素。
3.pop()
弹出栈顶元素。
六、deque(双端队列,队头队尾都可以插入删除)
1.size()
2.clear()
3.front()
返回第一个元素。
4.back()
返回最后一个元素。
5.push_back()
向最后插入一个元素。
6.pop_back()
弹出最后一个元素。
7.push_front()
向队首插入一个元素。
8.pop_front()
从队首弹出一个元素。
七、set,map,multiset,multimap(基于红黑树实现,可动态维护有序序列)
set里不可以有重复元素,multiset可以有。set/multiset:
1.insert()
插入一个数。
2.find()
寻找一个数。
3.count()
返回某个数出现的次数。
4.erase()
(1)输入一个数x,则删除这个数。
(2)输入一个迭代器,则删除这个迭代器。
5.lower_bound()
返回大于等于x的最小的数。
6.upper_bound()
返回大于x的最小的数。
八、unordered_set,unordered_map,unordered_multiset,unordered_multimap(基于哈希表实现,无序)
九、bitset(压位)
十、pair(二元组)
pair的两个数据类型可以任意。如pair<int,string>p;
p.first表示第一个元素,p.second表示第二个元素。
Ⅱ、搜索与图论(1)
深度优先搜索(DFS)和宽度优先搜索(BFS)是两种主要的搜索方式,而基于这两种搜索方式我们有树与图的深度优先遍历和宽度优先遍历。DFS用到的主要数据结构是栈(stack),空间复杂度为O(h)(这里h是树的高度)。它不具有最短性,而且stack并不需要我们实现。BFS用到的主要数据结构是队列(queue),空间复杂度为O(2^h),且具有最短性,需要我们实现队列。下面就让我们来具体看看吧!
一、深度优先搜索(DFS)
对整个空间进行搜索,搜索的结构像树一样。深度优先搜索会尽量往深处搜索,搜索到叶节点时就会回溯。它会便回溯边观察是否可以往别的叶节点处搜索。使用stack。它搜索时只需要记住当前路径上的点,因此它需要的空间与高度成正比,为O(h)。它不具有最短性(最短性见BFS)。如图是它的搜索顺序——
DFS的两个重要概念:剪枝和回溯。每个DFS都对应一棵搜索树。下面我们通过具体的问题来看看DFS具体是怎么实现的吧!
思路如图(以n == 3为例):
DFS的路径如图中绿线所示。注意,DFS过程中并不会把整棵树存下来,而是只存当前路径上的信息。此外,在DFS将一条路径走到叶节点之前,它是不知道其它路径是怎样的。例如走1-12-123这条路径的时候,它并不知道还有1-13-132等其它路径。下面我们就来看看如何具体用代码实现吧!
#include <iostream>
using namespace std;
const int N = 10;
//path路径数组,存储当前路径上该层的值
int n,path[N];
//bool数组,bool[i]的值表示数字i是否已经在这条路径上被用过
bool isUsed[N];
void dfs(int x)
{
//这里不能写成(i=1;i<=n)!因为下面从第零层开始搜索,path[0]=1!
//如果x==n,说明已经到叶子节点,直接输出即可。
if(x==n)
{
for(int i = 0;i<n;i++)
{
printf("%d ",path[i]);
}
printf("\n");
}
for(int i = 1;i<=n;i++)
{
if(!isUsed[i])
{
path[x] = i;
//递归之前修改
isUsed[i] = true;
dfs(x+1);
//搜索完毕后要把数字i的使用状态恢复成“未使用”因为递归会递归到最后一层再回来,所以递归函数实际上帮我们实现了“回溯”。同时,由于恢复bool值的语句写在递归后面,也就相当于搜索完一条完整路径后把数字恢复成“未使用”的状态,正和我们的心意。例如路径1-12-123,在第三层看来,数字2已经被使用过了;但是在第一层看来,数字2还没被使用过。
//这里的恢复并不是对于每个i都会执行,例如在1-12-123这条路径,只有在i=3时才会执行。
//由于path数组的值会不断被覆盖,所以我们并不需要将它恢复成0.
isUsed[i] = false;
}
}
}
int main()
{
scanf("%d",&n);
dfs(0);
}
可能大家对于递归后的isUsed[i]=false并不太理解,那就让我们通过调试来一探究竟吧!
可以看到,当一层走到叶子节点后,只有最后一个数字的isUsed状态会被修改回false状态。
下面让我们再看一道例题吧!
一种直接的思路是这样的l,像上一道题一样,列出所有的排列,然后再把不合法的排列舍去。代码如下:
#include<iostream>
#include<cmath>
using namespace std;
const int N = 20;
//path1数组用于存储数字的排列,其中path[k]的值表示在第k行中'Q'应该放在第几列,这样全排列后就避免了行与列的冲突。
int n,path1[N];
char path2[N][N];
bool isUsed[N];
void dfs(int x)
{
for(