火龙信奥
  • 分享
  • 课程
  • 在线题库
  • CSES
    • GESP
    • CSP
  • 打卡
    • 代码对战
    • 快速对战
  • 题单
  • 知识课堂
  • 在线比赛
  • 团队
  • 荣誉墙
  • 商城
  • 登录 / 注册

基础算法模块讲义

作者: 作者的头像   huolong , 时间:2026-09-25 15:11:03 , 所有人可见, 阅读  31

基础算法模块讲义

本模块包含 快速排序、归并排序、二分、高精度、前缀和与差分、双指针算法、位运算、离散化、区间合并 共 9 个核心基础算法。这些算法是算法竞赛和日常编程中的“基本功”。


1. 快速排序 (Quick Sort)

核心思想:分治法 (Divide and Conquer)

想象老师让你把全班同学按身高从矮到高排队。如果直接比会很慢,快速排序的聪明之处在于“分治”: 1. 找基准 ($x$):在数组中随便挑一个数字作为“基准数”(比如正中间的数)。 2. 分区 (Partition):调整数组,把所有小于等于 $x$ 的数全赶到左边,把所有大于等于 $x$ 的数全赶到右边。 3. 递归:此时基准数已经落在了它最终排好队时的正确位置。接着用同样的方法处理左边的一群人和右边的一群人,直到整个数组有序。

双指针分区原理图解

定义两个指针 $i$ 和 $j$,分别从数组的最左端和最右端出发,向中间靠拢:

  l                 mid                 r
 [3]  5   1   2   [4]  6   8   7   9
  i                                 j
  • $i$ 往右走,直到遇到一个 $\ge x$ 的数停下。
  • $j$ 往左走,直到遇到一个 $\le x$ 的数停下。
  • 交换 $i$ 和 $j$ 指向的数,然后继续,直到 $i$ 和 $j$ 相遇。

C++ 代码模板

void quick_sort(int q[], int l, int r) {
    if (l >= r) return;
    int x = q[(l + r) >> 1], i = l - 1, 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]);
    }
    quick_sort(q, l, j);
    quick_sort(q, j + 1, r);
}

2. 归并排序 (Merge Sort)

核心思想:先分后合

  • 分:把一个长数组从中间切成两半,一直递归切到每个子区间只有 1 个数字(1个数字本身就是有序的)。
  • 合(归并):把两个已经有序的小数组,合并成一个大有序数组。

归并过程数学图解

假设有两个有序数组 [1, 3, 5] 和 [2, 4, 6]:

[1, 3, 5]     [2, 4, 6]     -->     临时大盒子:[1, 2, 3, 4, 5, 6]
   ^             ^
指针 i         指针 j
  • 比较 $i$ 和 $j$ 指向的数字,谁小就把谁先放入临时盒子,对应的指针往后移一位。

C++ 代码模板

int tmp[100010];

void merge_sort(int q[], int l, int r) {
    if (l >= r) return;
    int mid = (l + r) >> 1;

    merge_sort(q, l, mid); 
    merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r) {
        if (q[i] <= q[j]) tmp[k++] = q[i++];
        else tmp[k++] = q[j++];
    }
    while (i <= mid) tmp[k++] = q[i++];
    while (j <= r) tmp[k++] = q[j++];

    for (i = l, j = 0; i <= r; i++, j++) q[i] = tmp[j];
}

3. 二分 (Binary Search)

核心思想

玩“猜数字”游戏(1到100之间),每次猜中间数,根据“大了”或“小了”把范围砍掉一半。二分查找能把原本需要挨个找的 $O(n)$ 复杂度,直接降到 $O(\log n)$。

整数二分的两个核心模板

初中生最容易被二分的边界卡住。记住:根据你的 check 条件决定怎么更新区间,选错模板会死循环。

  • 模板 1(找满足某种性质的右边界,常用于 l = mid, r = mid - 1 的对立情况):
int bsearch_1(int l, int r) {
    while (l < r) {
        int mid = (l + r + 1) >> 1; // 注意这里一定要加 1
        if (check(mid)) l = mid;    // check 成立,说明答案在 [mid, r]
        else r = mid - 1;
    }
    return l;
}
  • 模板 2(找满足某种性质的左边界):
int bsearch_2(int l, int r) {
    while (l < r) {
        int mid = (l + r) >> 1;     // 这里不需要加 1
        if (check(mid)) r = mid;    // check 成立,说明答案在 [l, mid]
        else l = mid + 1;
    }
    return l;
}

4. 高精度 (High Precision)

核心原理

计算机里的 int 最大只能存约 $2 \times 10^9$,long long 也就 $9 \times 10^{18}$。如果要算 100位数字 + 100位数字 怎么办? 我们用数组来模拟小学列竖式计算的过程。

  • 数组存数法:把大数字的个位存在数组下标 0 的地方,十位存 1…… 例如数字 12345 存为:vector<int> A = {5, 4, 3, 2, 1};

高精度加法模板 (C++)

vector<int> add(vector<int> &A, vector<int> &B) {
    vector<int> C;
    int t = 0; // 进位
    for (int i = 0; i < A.size() || i < B.size(); i++) {
        if (i < A.size()) t += A[i];
        if (i < B.size()) t += B[i];
        C.push_back(t % 10); // 当前位留下的数字
        t /= 10;             // 新的进位
    }
    if (t) C.push_back(t);   // 最高位如果还有进位,补上
    return C;
}

5. 前缀和与差分 (Prefix Sum & Difference)

前缀和(秒算区间和)

  • 定义:前缀和数组 $S_i$ 表示原数组前 $i$ 个数字的总和。 $$S_i = a_1 + a_2 + \dots + a_i$$
  • 公式应用:想求原数组从第 $L$ 个数到第 $R$ 个数的和?不需要循环,直接用: $$\text{Sum} = S_R - S_{L-1}$$
  • 二维前缀和公式:以 $(x_1, y_1)$ 为左上角、$(x_2, y_2)$ 为右下角的子矩阵和为: $$S[x_2][y_2] - S[x_1-1][y_2] - S[x_2][y_1-1] + S[x_1-1][y_1-1]$$

差分(秒改区间值)

  • 定义:差分数组 $B$ 的前缀和就是原数组 $A$(即 $A$ 是 $B$ 的前缀和)。
  • 神级应用:如果要让原数组从第 $L$ 个数到第 $R$ 个数的每一个数都加上同一个常数 $c$,传统做法要循环 $R-L+1$ 次。差分做法只需要两步: $$B[L] += c, \quad B[R+1] -= c$$ 最后做一次前缀和还原,效率极高!

6. 双指针算法 (Two Pointers)

核心思想

用两个指针(比如下标 i 和 j)在数组或字符串上同步移动,把原本需要两层嵌套循环($O(n^2)$)的暴力搜索,降维打击到线性时间复杂度 $O(n)$。

经典应用场景:最长连续不重复子序列

给定一个数组,求一个最长的连续区间,使得区间内部没有任何重复的数字。

int j = 0;
for (int i = 0; i < n; i++) {
    count[a[i]]++; // 窗口右边界往右扩
    while (count[a[i]] > 1) { // 如果有重复,左边界往右缩,直到不重复
        count[a[j]]--;
        j++;
    }
    res = max(res, i - j + 1); // 记录最大长度
}

7. 位运算 (Bitwise Operations)

常用的 5 个基础符号

  • & (按位与):两位全为 1,结果才为 1。
  • | (按位或): 有一个为 1,结果就为 1。
  • ^ (异或): 相同为 0,不同为 1(不进位的加法。性质:$x \mathbin{\hat{}} x = 0$, $x \mathbin{\hat{}} 0 = x$)。
  • << (左移): 向左乘 2。
  • >> (右移): 向右除 2。

核心神器:Lowbit

  • 定义:$\text{lowbit}(x)$ 返回 $x$ 的二进制表示中,最低位的 1 及其后面的所有 0 构成的数值。
  • 数学公式: $$\text{lowbit}(x) = x \& (-x)$$
  • 例子:若 $x = 10$(二进制 1010),最低位的 1 在第 2 位(从右往左数),$\text{lowbit}(10) = 2$(二进制 10)。
  • 用途:用来统计一个数字在二进制下有多少个 1。

8. 离散化 (Discretization)

核心思想

  • 场景:题目给你的坐标范围非常大(比如达到 $10^9$ 级别),但是涉及到的有效点非常少(比如只有 3000 个)。如果直接开一个 $10^9$ 大小的数组内存直接爆掉。
  • 做法:只保留数据之间的相对大小关系,把大数字“压缩”映射到较小的从 $0$ 或 $1$ 开始的连续整数编号。
  • 实现三步曲:
  • 把所有需要用到的坐标存入数组并排序。
  • 用 unique 函数去重。
  • 用二分查找 (lower_bound) 查询某个大数字离散化之后对应的新编号。

9. 区间合并 (Interval Merging)

核心思想

给你一堆有交集的区间(例如 $[1, 3], [2, 6], [8, 10], [15, 18]$),把所有有重叠的区间合并成一个大区间。

算法步骤

  1. 把所有区间按照左端点从小到大进行排序。
  2. 维护当前正在合并的临时区间 $[st, ed]$。
  3. 扫描后面的每个区间:
  4. 如果后面区间的左端点 $\le ed$,说明有交集,更新当前区间的右边界:ed = max(ed, next_ed)。
  5. 如果后面区间的左端点 $> ed$,说明断开了,把当前的 $[st, ed]$ 放入答案,然后更新 $[st, ed]$ 为新的区间。

—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com

关于火龙

  • 关于我们
  • 学员获奖
  • 预约试听
  • ACM课程
  • CSP课程
  • 学习指南

帮助中心

  • 用户协议
  • 打字练习 HOT
  • 在线画图
  • DevC++下载
  • CSP报名
  • GESP官网

推荐课程

  • C++零基础入门(可试看)
  • C++进阶提升
  • GESP考级辅导
  • GESP打卡
  • CSP-J/S打卡

公众号

火龙信奥公众号二维码

© 2017-2026 义乌市睿码科技有限公司版权所有 浙ICP备2021013995号

火龙信奥
请输入登录信息


请完成安全验证
验证码底图 滑块
向右拖动滑块完成验证
请输入用户名 / 绑定的手机号码



请输入注册信息(手机号验证码注册)





验证码5分钟有效,60秒内不可重复获取,每日最多3次

微信登录

微信登录二维码

正在生成二维码...

账号已过期,请续期。
去续期

绑定手机号

📱

为了更好地保护您的账号安全,享受完整的平台服务

请您尽快绑定手机号码