一、排序
1.快排:
算法思想:利用双指针,设置界定值,从界定值左边寻找比界定值大的数,在右边寻找比界定值小的数,找到后两者进行交换,即可完成一次排序。完成一次排序后只能保证界定值左右均比界定值大或小,但是左右仍然为乱序,需将左右两边进行递归快排,从而达到排序目的
代码模板:
void quick_sort(int q[], int l, int r)
{
if(l > r)
return ;
//step1:选取界定值
int mid = q[(l + r) / 2];
//step2:进行第一次快排
int i = l - 1;
int j = r + 1;
while(i < j)
{
//找两边比界定值大和小的值
do i ++; while(q[i] < x);
do j --; while(q[j] > x);
if(i < j)
swap(q[i], q[j]);
}
//step3:对两边进行递归快排
quick_sort(q, l, i);
quick_sort(q, j + 1, r);
}
完整模板:Acwing.785.快速排序
2.归并排序
算法思想:归并即对界定值两边分别进行排序后,合并为一个有序数组
应用方面:求逆序对
代码模板:
void merge_sort(int q[], int l, int r)
{
if(l >= r)
return ;
//step1:确定分界点
int mid = (l + r) / 2;
//step2:对分界点两边进行归并排序
merge_sort(q, l, mid);
merge_sort(q, mid + 1, r);
//step3:合并排序后的数组
int temp[], k;
int i = l, j = mid + 1;
while(i <= mid && j <= r)
{
if(q[i] < q[j]) temp[k ++] = q[i ++];
else temp[k ++] = q[j ++];
}
//处理合并之后剩余未合并的
while(i <= mid) temp[k ++] = q[i ++];
while(j <= r) temp[k ++] = q[j ++];
//将临时数组赋值给q
for(int i = l, j = 0; i <= r; i ++ , j ++ )
q[i] = temp[j];
}
完整模板:Acwing.787.归并排序
二、二分
算法思想:由于暴搜需要扫描整个数组,当数组有序时,可采用二分查找,每次将区间缩小一半,远远降低时间复杂度
应用方面:查找
代码模板:
//要查找的值为x,数组为q[]
int l = 0, r = n - 1;
//模板一:
while(l < r)
{
int mid = (l + r + 1) / 2;
if(q[mid] <= x) l = mid;
else r = mid - 1;
}
//模板二
while(l < r)
{
int mid = (l + r) / 2;
if(q[mid] >= x) r = mid;
else l = mid + 1;
}
三、前缀和与差分
算法思想:
前缀和:数组中前i个数的和
差分:为前缀和的逆运算,即将一个数分成若干数相加
前缀和应用很广泛,求和,求子矩阵和等等都可用到
差分最明显的特征为:当需要将一个区间内的数作相同变化时,即可用差分
代码模板:
//前缀和
for(int i = 1; i <= n; i ++ )
{
s[i] = s[i - 1] + a[i];
}
//差分:
比如需要将[l,r]区间中的所有数加c
//先求差分数组
//仅仅对l操作即可,因为差分数组求前缀和以后即可达到整个数组都操作,再对r+1进行反操作就能保证仅仅是这个区间内操作
b[l] += c;
b[r + 1] -= c;
//再对差分数组求前缀和即可达到整个区间操作的目的,且时间复杂度仅仅为O(1);
for(int i = 1; i <= n; i ++ )
b[i] = b[i - 1] + b[i];
四、双指针
五、位运算
快速求二进制数中1的个数
代码模板:
int x,count;
while(x)
{
x -= x & -x;
count ++;
}
六、DFS和BFS
DFS是一个深度搜索的过程,将一条路径搜索完之和才会回溯搜索其他路径,常用于排列组合问题,DFS需要用递归。
BFS是一个广度搜索的过程,它会将一个结点所连接的路径全部搜索出来后再往下继续搜索,因此它会有一个结点搜没搜过的特性,利用这个特性可以计算最短路径问题,BFS需要用队列来实现
关于DFS什么时候需要回溯的问题:
1.当搜索方式为选或者不选的时候,即比如排列数字,走迷宫等,当前有几个可以选择,我可以选这个也可以选那个,选了一个后,另一个等回溯到这里时再选,这个时候需要回溯,即恢复现场,保证只是在当前做选择后的路径不一样,即选择分支。
2.而当搜索方式为每个点各自为一种情况,比如洪水灌溉,红与黑等,我要搜索其中有多少我可以走的(计数问题),每个点独立,即每次走完一个点后需要标记这个点,不需要回溯,如果这种情况回溯的话,那么肯定会重复计数,导致陷入死循环。
代码模板:需要回溯Acwing.842.排列数字
不需要回溯Acwing.1113.红与黑
/*****DFS******/
void dfs(int x)
{
//等于n说明一条路径已经搜索完,将这条路径输出
if(x == n)
{
for(int i = 0; i < n; i ++)
printf("%d",path[i]);
puts("");
return ;
}
//否则证明还没有搜索完该条路径,向下继续搜索
else
{
for(int i = 1; i <= n; i ++ )
{
if(!st[i])
{
path[x] = i;
st[i] = true;
//一个结点搜索完之和,直接搜索该结点的下一个结点
dfs(x + 1);
//执行到这里说明一条路径搜索完了,该回溯搜索其他路径
path[i] = 0;
st[i] = false;
}
}
}
}
BFS
代码模板:
//BFS
int bfs()
{
//定义一个队列
queue<int> q;
//将初始状态压入队列中
q.push(...);
//while循环直到队列为空,即搜索到最后一个元素
while(!q.empty())
{
//取到当前队首元素
auto t = q.front();
//弹出队首元素
q.pop();
//位置向前移动
int x = ...., y= .....
//判断移动后的位置是否符合条件
if(.......)
{
//符合条件则更新题中所求的东西
//接着将移动后的位置压入队列中
q.push(...);
}
}
}
完整模板题:Acwing.844.走迷宫
七、并查集
算法思想:并查集是对集合进行的操作,当需要合并集合时,只需要将一个集合的根节点接在另一个集合的根节点之下即可,并查集中最重要的操作:路径压缩,将每个结点都直接连在根节点之下
代码模板:
//并查集基本都会有的查找祖宗的操作
void find(int x)
{
if(p[x] == x) return x; //p[x]为一个结点的父节点,如果一个结点的父节点是自己,那么它就是根节点
else
p[x] = find(p[x]); //并查集的路径压缩,即将每个结点都直接连在根节点之下
}
int main()
{
for(int i = 1; i <= n; i ++ )
p[i] = i; //并查集初始化每个结点的父节点都是自己本身
}