常用代码模板1 - 基础算法

常用代码模板1 - 基础算法

作者:CodeWin

快速排序

cpp 复制代码
void qsort(int a[], int l, int r)
{
	if (l >= r) // 分治的边界,区间长度为 1
		return;
	// i:从左到右找第一个不符合要求的位置, j:从右到左找第一个不符合要求的位置, 基准值
	int i = l, j = r, midx = a[l + r >> 1];
	while (i <= j)
	{
		while (i <= j && a[i] < midx)
			i++;
		while (i <= j && a[j] > midx)
			j--;
		if (i <= j)				  // 将左右两侧不符合的要求的位置,交换值
			swap(a[i++], a[j--]); // 且靠拢一步
	}
	if (l < j) // 递归分治两部分
		qsort(a, l, j);
	if (i < r)
		qsort(a, i, r);
}

归并排序

cpp 复制代码
int tmp[N]; // 临时存放数组,O(n)

void merge_sort(int a[], int l, int r)
{
    if (l >= r)
        return;
    int mid = l + r >> 1;
    merge_sort(a, l, mid); // 先分治,后处理
    merge_sort(a, mid + 1, r);
    int tmp[N];
    int k = l, i = l, j = mid + 1; // k:表示临时数组的存放指针, i/j:分别表示为两部分[l,mid] / [mid+1,r]的各自左指针
    while (i <= mid && j <= r)
        if (a[i] <= a[j]) // 小的优先放,<= 也能确定算法的稳定性
            tmp[k++] = a[i++];
        else
            tmp[k++] = a[j++];
    while (i <= mid) // 左侧剩余的有序元素
        tmp[k++] = a[i++];
    while (j <= r) // 右侧剩余的有序元素
        tmp[k++] = a[j++];
    for (i = l; i <= r; i++) // 有序后归位
        a[i] = tmp[i];
}

二分查找类

二分查找 - 整数

开区间,非常实用且美观的二分板子

  • 查找 第一个 ≥ t a r g e t \ge target ≥target 元素所在位置。
  • 查找最 后一个 ≥ t a r g e t \ge target ≥target 元素所在位置。
cpp 复制代码
bool check(int x)
{
    /*检查 x 是否满足某种性质*/
}

// 查找第一个满足 >= target 的元素
int bsearch1(int target, int a[], int l, int r)
{
    l -= 1, r += 1; // 开区间
    while (l + 1 < r)
    {
        int mid = l + r >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid;
    }
    return r; // 答案落在右端点
}

// 查找最后一个满足 >= target 的元素
int bsearch2(int target, int a[], int l, int r)
{
    l -= 1, r += 1; // 开区间
    while (l + 1 < r)
    {
        int mid = l + r >> 1;
        if (check(mid))
            l = mid;
        else
            r = mid;
    }
    return l; // 答案落在左端点
}

二分查找 - 浮点数

cpp 复制代码
bool check(double x)
{
    /*检查 x 是否满足某种性质*/
}

double bsearch3(double l, double r)
{
    const double eps = 1e-6; // eps 表示精度,去取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid))
            r = mid;
        else
            l = mid;
    }
    return l; // l/r都可以
}

高精度

高精度 - 加法

省略了「逆序存放」,逆序存放后下标从 1 开始

cpp 复制代码
void gadd(int a[], int b[], int len)
{
    for (int i = 1; i <= len; i++)
    {
        c[i] += a[i] + b[i];
        c[i + 1] = c[i] / 10; // 模拟进位
        c[i] %= 10;
    }
    if (c[len + 1]) // 最后进位可能会位数增加
        len++;
    // 处理前导 0
    while (c[len] == 0 && len > 1)
        len--;
    for (int i = len; i >= 1; i--)
        cout << c[i];
}

高精度 - 减法

省略了「逆序存放」、「处理负数」

cpp 复制代码
void gsub(int a[], int b[], int len)
{
    for (int i = 1; i <= len; i++)
    {
        if (a[i] < b[i])
            a[i] += 10, a[i + 1] -= 1;
        c[i] = a[i] - b[i];
    }
    while (c[len] == 0) // 处理前导0,998-996=002
        len--;
    for (int i = len; i >= 1; i--)
        cout << c[i];
    if (len < 1) // 恰好为 0 的情况
        cout << 0;
}

高精度 - 乘法

省略,逆序存放,竖式乘法,规律:c[i+j-1] = a[i]*b[j]

  • 注意特判,结果为 0 的情况也要输出
cpp 复制代码
void gmul(int a[], int b[], int len)
{
    for (int i = 1; i <= s1.size(); i++)
        for (int j = 1; j <= s2.size(); j++)
            c[i + j - 1] += a[i] * b[j];
    // 先相乘,后处理进位问题
    for (int i = 1; i <= len; i++)
    {
        c[i + 1] += c[i] / 10; // 模拟进位
        c[i] %= 10;
    }
    // 处理前导0
    while (c[len] == 0)
        len--;
    for (int i = max(1, len); i >= 1; i--) // 结果为 0 的情况,用 max 特判
        cout << c[i];
}

高精度 - 除法 (高除低)

  • 注意分母 b 不能是零;
  • 前导 0 处理到倒数第二位,可能结果是 0,保留一位
  • 省略了,除法不需要逆序存放。
cpp 复制代码
void gdiv(int a[], int b, int len)
{
    int res = 0;
    for (int i = 1; i <= len; i++)
    {
        res = res * 10 + a[i];
        c[i] = res / b;
        res %= b;
    }
    int k = 1;
    while (c[k] == 0 && k < len) // 处理前导0teng`
        k++;
    for (k = max(1, k); k <= len; k++) // 注意结果为 0 的情况,用 max 特殊处理
        cout << c[k];
}

前缀和与查分

一维前缀和

  • O ( 1 ) O(1) O(1) 区间和查询: s u m [ R ] − s u m [ L − 1 ] sum[R] - sum[L-1] sum[R]−sum[L−1]
cpp 复制代码
// 原值, 前缀和数组
const int N = 1e5 + 10;
int a[N], sum[N], T;

int main() {
	...
	// 构造前缀和数组
	for (int i = 1; i <= n; i++) {
		sum[i] = sum[i - 1] + a[i];
	}
	// O(1) 时间内访问区间 [L,R] 的和
	cin >> T;
	while ( T-- ) {
		int L, R;
		cin >> L >> R;
		cout << sum[R] - sum[L - 1] << endl;
	}
}

一维差分

  • O ( 1 ) O(1) O(1) 区修: b [ l ] + c , b [ r + 1 ] − c b[l]+c, b[r+1]-c b[l]+c,b[r+1]−c;
cpp 复制代码
const int N = 1e5 + 10;
int a[N], b[N]; // 原值、差分数组
int n;

int main() {
	...
	// 构造差分数组
	for (int i = 1; i <= n; i++)
		b[i] = a[i] - a[i - 1];
	// O(1) 时间复杂度内修改区间的值
	int q, L, R, c;
	cin >> q;
	while (q--) {
		cin >> L >> R >> c; // [L,R] + c
		b[L] += c, b[R + 1] -= c;
	}
	// 前缀和运算,将差分数组 b[] 还原到原值数组 b[]
	for (int i = 1; i <= n; i++)
		b[i] = b[i] + b[i - 1]; 
	// 输出原值数组 b[] 
	for (int i = 1; i <= n; i++) 
		cout << b[i] << endl; 
}

二维前缀和

  • 构造: s u m [ i ] [ j ] = s u m [ i − 1 ] [ j ] + s u m [ i ] [ j − 1 ] − s u m [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] sum[i][j] =sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j] sum[i][j]=sum[i−1][j]+sum[i][j−1]−sum[i−1][j−1]+a[i][j];
  • 输出: s u m [ c ] [ d ] − s u m [ c ] [ b − 1 ] − s u m [ a − 1 ] [ d ] + s u m [ a − 1 ] [ b − 1 ] sum[c][d] - sum[c][b-1] - sum[a-1][d] + sum[a-1][b-1] sum[c][d]−sum[c][b−1]−sum[a−1][d]+sum[a−1][b−1];
cpp 复制代码
int a[N][N], sum[N][N];
int n, m, q;

int main() {
	cin >> n >> m >> q;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			cin >> a[i][j];
	// 构造二维前缀和数组, O(nm)
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j];
	// q 次询问, 每次 O(1)
	for(int i=1,a,b,c,d;i<=q;i++){
		cin >> a >> b >> c >> d;
		cout << sum[c][d] - sum[c][b-1] - sum[a-1][d] + sum[a-1][b-1] << endl;
	}
	return 0;
}

二维差分

  • 构造:

    cpp 复制代码
    b[aa][bb] += a[i][j],b[aa + 1][bb] -= a[i][j],b[aa][bb + 1] -= a[i][j],b[aa + 1][bb + 1] += a[i][j]
  • 区修 aa,bb) - (cc,dd) + k

    cpp 复制代码
    b[aa][bb] += k; //左上
    b[cc + 1][bb] -= k; //左下
    b[aa][dd + 1] -= k; //右上
    b[cc + 1][dd + 1] += k; // 右下
cpp 复制代码
const int N = 1e3 + 10;
int n, m, q;
int a[N][N], b[N][N];

void insert( int aa, int bb, int cc, int dd, int k ) {
	b[aa][bb] += k; //左上
	b[cc + 1][bb] -= k; //左下
	b[aa][dd + 1] -= k; //右上
	b[cc + 1][dd + 1] += k; // 右下
}

int main() {
	...
	// 构造差分数组
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			insert( i, j, i, j, a[i][j] );
	// 区间修改 O(1)
	for(int i=1,aa,bb,cc,dd,k;i<=q;i++){
		cin >> aa >> bb >> cc >> dd >> k;
		insert( aa,bb,cc,dd,k );
	}
	// 前缀和还原,将差分数组还原序列的值
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) // 二维前缀和式子
			b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1];
	...
}

位运算

  • 求 n n n 的第 k k k 位数字:n >> k & 1

  • 返回 n n n 的最后一位 1 1 1:lowbit(n) = n & -n

双指针算法

cpp 复制代码
for (int i = 0, j = 0; j < n ; j++)
{
    while( j<i && check( i,j ) ) j++;
    // 具体的代码逻辑
    ...
}
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并sort中合并两个有序序列的操作

离散化

解决,一段 区间值域较大 ,如: [ − 1 0 9 , 1 0 9 ] [-10^9, 10^9] [−109,109] ,通过创建数组及下标来访问某个值 ,修改某段区间是做不到,会超出最大可空间存储,实际上值域存在的个数又不是很多 ,如 1 0 5 10^5 105 个元素,这时就可以 利用离散化,将一端较大的值域离散到「紧靠」在一起的数组元素,通过二分来查找出原值锁在的位置。

三步骤

  1. 放入分布交离散的元素到「待离散」的数组中。
  2. 排序、去重,离散的元素「紧靠」在一起,通过下标映射。
  3. 二分查找离散后「x」的下标 k,通过 k 来访问离散后值。
cpp 复制代码
vector<int> alls; //存储所有{待离散化}的值
int n;

int find( int k ) {
	int l = 0, r = alls.size(); // 开区间
	while ( l + 1 < r ) {
		int mid = (l + r) / 2 ;
		if ( alls[mid] >= k )
			r = mid;
		else
			l = mid;
	}
	return r + 1; //+1,代表分布在 [1,...,all.size()] ,不加以就从 0 开始
}

int main() {
	int n;
	cin >> n;
	for (int i = 1, x; i <= n; i++)
		cin >> x, alls.push_back(x); // 读入到「待离散」的数组中

	sort(alls.begin(), alls.end()); // 将所有值排序
	alls.erase( unique( alls.begin(), alls.end() ), alls.end() ); // 去重元素

	// 二分求出 x 对应的离散化的位置
	int x;
	cin >> x;
	cout << find( x ) << endl;
	
	return 0;
}

区间合并

cpp 复制代码
// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
    vector<PII> res;

    sort(segs.begin(), segs.end());

    int st = -2e9, ed = -2e9;
    for (auto seg : segs)
        if (ed < seg.first)
        {
            if (st != -2e9) res.push_back({st, ed});
            st = seg.first, ed = seg.second;
        }
        else ed = max(ed, seg.second);

    if (st != -2e9) res.push_back({st, ed});

    segs = res;
}

深搜

cpp 复制代码
auto dfs( ...  ){
    if( 搜索边界 ) // 一般是最小问题初始化 / 到达答案则返回
        return ...;
    // 具体问题具体分析
    dfs( ... );// 缩小子问题, 递归求解
}

dfs( ... ); 

广搜

cpp 复制代码
#include<queue>

queue< 类型 > q;
q.push( <初始状态> );
while( q.size() ){
    auto top = q.front(); // 队头状态
    q.pop(); //出队
    for( ; ; ) { // 课扩展状态的枚举
    if( check() ) // 符合要求的元素进队
    	q.push();     
    }
    //..... 具体问题具体分析
}

回溯算法

Carl(卡尔)的板子

cpp 复制代码
void backtarcking( 参数 ){
    if( 终止条件 ){
        记录结果
        return;
    }
    else{
        for( 选择:本层集合元素(树中节点孩子的数量或集合的大小) ){
           	处理节点;
            backtarcking( 路径,选择列表 ); // 递归
            回溯,撤销处理结果;
        }
    }
}
相关推荐
立志成为coding大牛的菜鸟.1 小时前
力扣139-单词拆分(Java详细题解)
java·算法·leetcode
星夜孤帆2 小时前
LeetCode之数组/字符串
java·算法·leetcode
present12272 小时前
利用matlab bar函数绘制较为复杂的柱状图,并在图中进行适当标注
算法·matlab·数据分析·学习方法
金博客4 小时前
QT进行音频录制
c++·qt·音视频
就这样很好8804 小时前
排序算法总结
java·算法·排序算法
TT-Kun5 小时前
C++ | vector 详解
开发语言·c++
weixin_486681145 小时前
C++系列-STL中find相关的算法
java·c++·算法
码了三年又三年5 小时前
ArrayList、LinkedList和Vector的区别
开发语言·c++·链表
月夕花晨3745 小时前
C++学习笔记(14)
c++·笔记·学习
我是真爱学JAVA5 小时前
第四章 类和对象 课后训练(1)
java·开发语言·算法