C++ 复赛冲刺集训课件

📦 模块一:基本数据类型与控制流 小低/小高通用

打好基础是拿分的关键!这一模块是所有题目的基石,务必熟练掌握!

数据类型详解

通用

🧠 核心逻辑

想象你有很多不同大小的"盒子"来装东西:

  • int 就像一个普通大小的盒子,能装大约 21亿 以内的整数。大部分题目用这个就够了!
  • long long 是一个超大号的箱子,能装超级大的数字(19位数)!当题目说"数据范围 10^18"时,必须用它!
  • double 是一个能装小数的盒子,比如 3.14、0.618。但要注意,它有时候会"四舍五入"产生误差!
  • char 是一个小格子,只能装一个字符,比如 'A'、'5'、'@'。记住要用单引号!
  • bool 是一个开关,只有两个状态:true(开/真)或 false(关/假)。

💣 避坑指南

致命错误1:数据溢出!
当计算结果超过 int 的范围时,会变成"负数"或"乱码"!比如:int a = 1000000 * 1000000; 结果是错的!
解决方法:看到大数运算,直接用 long long,或者在数字后面加 LL,如 1000000LL * 1000000。
致命错误2:double 比较陷阱!
double a = 0.1 + 0.2; 实际上 a 不等于 0.3!因为计算机存小数有精度问题。
解决方法:判断两个小数是否相等,要看它们的差是否小于一个很小的数,如 abs(a - b) < 0.000001。
致命错误3:字符和数字混淆!
char c = '5'; 这里的 '5' 不是数字5!它的ASCII码是53!
解决方法:要把字符 '5' 变成数字5,用 c - '0'(字符减去字符0)。

💻 标准模板

data_types_demo.cpp
#include <iostream>
using namespace std;

int main() {
    // int 类型:能装约21亿,适合大部分整数题目
    int score = 100;
    int bigNum = 2000000000;  // 接近int上限
    
    // long long 类型:能装超大数字,范围约9.2×10^18
    long long hugeNum = 9000000000000000000LL;
    // 注意:大数运算时,至少有一个数要是long long
    long long result = 1000000LL * 1000000LL;
    
    // double 类型:存储小数,注意精度问题
    double pi = 3.14159265358979;
    double price = 9.99;
    
    // char 类型:存储单个字符,用单引号
    char grade = 'A';
    char digit = '7';
    int numFromChar = digit - '0';  // 字符转数字:'7' -> 7
    
    // bool 类型:只有 true 或 false
    bool isPassed = true;
    bool isEmpty = false;
    // bool可以参与运算:true=1,false=0
    int count = isPassed + 5;  // count = 6
    
    return 0;
}
数据类型 范围 占用空间 使用场景
int -2,147,483,648 ~ 2,147,483,647(约±21亿) 4字节 大部分整数运算
long long 约±9.2×10^18(19位数) 8字节 大数运算、防止溢出
double 约±1.7×10^308(15-16位有效数字) 8字节 小数运算、科学计算
char -128 ~ 127 或 0 ~ 255 1字节 单个字符
bool true / false 1字节 条件判断、标记

运算符实战

通用

🧠 核心逻辑

整除(/)和求余(%)是考试的"常客"!

  • 整除(/):两个整数相除,结果只保留整数部分。比如 7 / 2 = 3(不是3.5!)
  • 求余(%):求除法的余数。比如 7 % 2 = 1(7除以2余1)
实战技巧:
• 取个位数:n % 10(比如 123 % 10 = 3)
• 取后两位:n % 100(比如 1234 % 100 = 34)
• 判断奇偶:n % 2(余数是0就是偶数,是1就是奇数)
• 去掉个位:n / 10(比如 123 / 10 = 12)

逻辑运算符用来组合多个条件:

  • &&(与):两边都要满足,才返回 true。就像"我要吃饭 并且 喝水"
  • ||(或):只要一边满足,就返回 true。就像"我要吃苹果 或者 香蕉"
  • !(非):取反。true 变 false,false 变 true

💣 避坑指南

陷阱1:负数求余!
在C++中,负数求余的结果可能是负数!比如 -7 % 3 = -1(不是2!)
解决方法:如果需要正余数,用 ((a % b) + b) % b。
陷阱2:逻辑运算符写错!
&& 和 || 很容易写错位置!比如 if (a > 0 || b > 0 && c > 0) 的意思是?
记住:&& 的优先级比 || 高!不确定就加括号!
陷阱3:除以零!
任何数除以0都会导致程序崩溃!一定要检查除数是否为0!

💻 标准模板

operators_demo.cpp
#include <iostream>
using namespace std;

int main() {
    // ========== 整除和求余的实战应用 ==========
    int n = 12345;
    
    // 分离各位数字
    int ge = n % 10;        // 个位:5
    int shi = n / 10 % 10;  // 十位:4
    int bai = n / 100 % 10; // 百位:3
    
    // 判断奇偶性
    if (n % 2 == 0) {
        cout << "是偶数" << endl;
    } else {
        cout << "是奇数" << endl;
    }
    
    // 判断能否被3整除
    if (n % 3 == 0) {
        cout << "能被3整除" << endl;
    }
    
    // ========== 逻辑运算符 ==========
    int age = 12;
    int score = 85;
    
    // && 与:两个条件都要满足
    if (age >= 10 && score >= 60) {
        cout << "符合参赛条件" << endl;
    }
    
    // || 或:只要一个条件满足
    if (score >= 90 || score < 60) {
        cout << "成绩优秀或不及格" << endl;
    }
    
    // ! 非:取反
    bool isRaining = false;
    if (!isRaining) {
        cout << "可以出去玩" << endl;
    }
    
    // 组合使用:加括号更清晰
    if ((age >= 10 && age <= 15) && (score >= 80 || score == 100)) {
        cout << "符合小高组优秀选手条件" << endl;
    }
    
    return 0;
}

循环嵌套进阶

通用

🧠 核心逻辑

多层循环就像"套娃",外层循环每走一步,内层循环就要完整跑一遍!

想象一个学校有3个年级,每个年级有4个班,每个班有5个小组:

  • 外层循环遍历年级(3次)
  • 中层循环遍历班级(每个年级4次,共12次)
  • 内层循环遍历小组(每个班5次,共60次)
执行顺序口诀:
外层走一步,内层跑一圈!
外层变一次,内层全走遍!

💣 避坑指南

陷阱1:循环变量搞混!
内外层循环用了相同的变量名,或者把 i 写成 j,导致死循环或结果错误!
解决方法:外层用 i,内层用 j,再内层用 k,养成固定习惯!
陷阱2:边界搞错!
for (int i = 0; i < n; i++) 和 for (int i = 1; i <= n; i++) 执行次数一样,但 i 的值不同!
记住:从0开始用 <,从1开始用 <=!
陷阱3:忘记初始化!
在循环外定义的变量,每次循环前要重新初始化!否则会保留上一次的值!

💻 标准模板

nested_loops_demo.cpp
#include <iostream>
using namespace std;

int main() {
    // ========== 打印矩形星号 ==========
    // 外层控制行数,内层控制每行的星号数
    for (int i = 0; i < 5; i++) {       // 5行
        for (int j = 0; j < 8; j++) {   // 每行8个星号
            cout << "* ";
        }
        cout << endl;  // 每行结束后换行
    }
    
    // ========== 打印九九乘法表 ==========
    for (int i = 1; i <= 9; i++) {       // 外层:被乘数
        for (int j = 1; j <= i; j++) {   // 内层:乘数,j <= i 实现三角形
            cout << j << "×" << i << "=" << i*j << "\t";
        }
        cout << endl;
    }
    
    // ========== 遍历二维矩阵 ==========
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    
    // 外层遍历行,内层遍历列
    for (int i = 0; i < 3; i++) {       // 3行
        for (int j = 0; j < 4; j++) {   // 4列
            cout << matrix[i][j] << " ";
        }
        cout << endl;
    }
    
    // ========== 找最大值的位置 ==========
    int maxVal = -1, maxRow = 0, maxCol = 0;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            if (matrix[i][j] > maxVal) {
                maxVal = matrix[i][j];
                maxRow = i;
                maxCol = j;
            }
        }
    }
    cout << "最大值:" << maxVal << ",位置:(" << maxRow << "," << maxCol << ")" << endl;
    
    return 0;
}

🗂️ 模块二:复杂数据结构 仅限小高组

掌握这些数据结构,你就能处理更复杂的问题!这是小高组拿高分的关键!

二维数组

小高专属

🧠 核心逻辑

二维数组就像一个"表格"或"棋盘",有行和列两个维度!

  • 想象一个教室的座位表:第几排(行)、第几列(列)
  • 数组名[行下标][列下标] 就能找到对应位置的数据
  • 下标从0开始!arr[0][0] 是第一行第一列
坐标系概念:
• 第一个下标是"行"(上下方向),从上往下数
• 第二个下标是"列"(左右方向),从左往右数
• arr[i][j] 表示第 i+1 行、第 j+1 列的元素

💣 避坑指南

陷阱1:下标越界!
定义 int arr[3][4],但访问 arr[3][4] 会越界!有效下标是 arr[0][0] 到 arr[2][3]!
记住:数组大小是 n,最大下标是 n-1!
陷阱2:行列搞反!
写成 arr[j][i] 而不是 arr[i][j],导致结果完全错误!
口诀:外层循环行(i),内层循环列(j),访问用 arr[i][j]!
陷阱3:数组开太大!
在函数内定义的数组不能太大!int arr[10000][10000] 会爆栈!
解决方法:大数组定义在函数外(全局变量),或使用 vector。

💻 标准模板

array_2d_demo.cpp
#include <iostream>
using namespace std;

int main() {
    // ========== 二维数组的定义和初始化 ==========
    int arr[3][4];  // 3行4列的二维数组
    
    // 方式1:直接初始化
    int matrix[3][4] = {
        {1, 2, 3, 4},    // 第0行
        {5, 6, 7, 8},    // 第1行
        {9, 10, 11, 12}  // 第2行
    };
    
    // ========== 双重循环读入数据 ==========
    int n, m;
    cin >> n >> m;  // 读入行数和列数
    
    int data[100][100];  // 预留足够空间
    for (int i = 0; i < n; i++) {       // 遍历每一行
        for (int j = 0; j < m; j++) {   // 遍历每一列
            cin >> data[i][j];  // 读入第i行第j列的元素
        }
    }
    
    // ========== 双重循环遍历输出 ==========
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cout << data[i][j] << " ";
        }
        cout << endl;  // 每行结束后换行
    }
    
    // ========== 常见操作:求每行/每列的和 ==========
    // 求每行的和
    for (int i = 0; i < n; i++) {
        int rowSum = 0;
        for (int j = 0; j < m; j++) {
            rowSum += data[i][j];
        }
        cout << "第" << i << "行的和:" << rowSum << endl;
    }
    
    // 求每列的和
    for (int j = 0; j < m; j++) {
        int colSum = 0;
        for (int i = 0; i < n; i++) {
            colSum += data[i][j];
        }
        cout << "第" << j << "列的和:" << colSum << endl;
    }
    
    // ========== 对角线元素 ==========
    // 主对角线:i == j
    for (int i = 0; i < n; i++) {
        cout << data[i][i] << " ";  // 主对角线元素
    }
    // 副对角线:i + j == n - 1
    for (int i = 0; i < n; i++) {
        cout << data[i][n-1-i] << " ";  // 副对角线元素
    }
    
    return 0;
}

String 字符串操作

小高专属

🧠 核心逻辑

string 就像一串"珠子",每个珠子是一个字符!

  • s.size()s.length():获取字符串长度(有多少个字符)
  • s.substr(起始位置, 长度):截取子串,就像从珠串上取下一段
  • s[i]:访问第 i 个字符(从0开始)
  • s[i] - '0':把字符数字转成真正的数字
字符转数字的原理:
字符 '0' 的 ASCII 码是 48,'1' 是 49,'2' 是 50...
所以 '5' - '0' = 53 - 48 = 5,就把字符变成了数字!

💣 避坑指南

陷阱1:substr 参数搞错!
s.substr(2, 3) 是从下标2开始,截取3个字符,不是截取到下标3!
记住:第一个参数是起始位置,第二个参数是截取长度!
陷阱2:越界访问!
用 s[i] 访问时,i 必须小于 s.size()!否则会出错!
解决方法:循环条件用 i < s.size(),不要用 i <= s.size()!
陷阱3:忘记头文件!
使用 string 需要包含 <string> 头文件(虽然很多编译器会自动包含,但考试时最好写上)!

💻 标准模板

string_demo.cpp
#include <iostream>
#include <string>  // 使用string需要这个头文件
using namespace std;

int main() {
    // ========== 字符串的定义和输入 ==========
    string s1 = "Hello";       // 直接初始化
    string s2;                    // 空字符串
    cin >> s2;                    // 读入一个字符串(遇空格停止)
    
    // ========== 获取字符串长度 ==========
    string s = "Hello World";
    cout << "长度:" << s.size() << endl;    // 11
    cout << "长度:" << s.length() << endl; // 11(两种写法等价)
    
    // ========== 遍历字符串的每个字符 ==========
    for (int i = 0; i < s.size(); i++) {
        cout << s[i] << " ";  // 输出每个字符
    }
    cout << endl;
    
    // ========== 截取子串 substr ==========
    string text = "HelloWorld";
    
    // substr(起始位置, 长度)
    string sub1 = text.substr(0, 5);   // "Hello",从位置0开始截5个
    string sub2 = text.substr(5, 5);   // "World",从位置5开始截5个
    string sub3 = text.substr(2);       // "lloWorld",从位置2截到末尾
    
    cout << sub1 << " | " << sub2 << " | " << sub3 << endl;
    
    // ========== 字符转数字 ==========
    string numStr = "2024";
    int year = 0;
    
    // 方法1:逐位转换
    for (int i = 0; i < numStr.size(); i++) {
        int digit = numStr[i] - '0';  // 字符转数字
        year = year * 10 + digit;      // 累加构建数字
    }
    cout << "年份:" << year << endl;  // 2024
    
    // 方法2:使用 stoi 函数(C++11)
    int year2 = stoi(numStr);  // 直接转换
    
    // ========== 字符串拼接 ==========
    string first = "Hello";
    string second = "World";
    string result = first + " " + second;  // "Hello World"
    
    // ========== 常见操作:统计某字符出现次数 ==========
    string sentence = "Hello World";
    int count = 0;
    for (int i = 0; i < sentence.size(); i++) {
        if (sentence[i] == 'l') {  // 统计字母'l'出现的次数
            count++;
        }
    }
    cout << "字母l出现了" << count << "次" << endl;  // 3次
    
    return 0;
}

🧩 模块三:必考算法思维 小低/小高通用

算法思维是编程的灵魂!掌握这些思维方式,你就能解决各种问题!

枚举与模拟

小低重点

🧠 核心逻辑

枚举就是"穷举所有可能",一个一个试,找到符合条件的答案!

就像你要找一把能开锁的钥匙,把钥匙串上的每一把都试一遍,总能找到对的那把!

枚举三步走:
1. 确定枚举对象(要试什么?)
2. 确定枚举范围(从哪里试到哪里?)
3. 确定判断条件(什么情况是答案?)

模拟就是"照着题目说的做",题目让你干什么,你就一步一步实现!

比如题目说"小明每天存5元,存了10天",你就用循环算:5 × 10 = 50元

💣 避坑指南

陷阱1:边界搞错!
题目说"1到100",你写成 for (int i = 0; i < 100; i++),漏掉了100!
解决方法:仔细读题,从几开始,到几结束,用 <= 还是 < 要想清楚!
陷阱2:遗漏情况!
枚举时漏掉了一些情况,导致答案不全。比如找因数,只找了1到n/2,漏掉了n本身!
解决方法:列举几个小例子验证,确保不漏!
陷阱3:效率太低!
有些题目数据范围很大,暴力枚举会超时!
解决方法:能优化的地方要优化,比如找因数只需要枚举到 √n。

💻 标准模板

enumeration_demo.cpp
#include <iostream>
using namespace std;

int main() {
    // ========== 例题1:找出1-100中所有能被3整除的数 ==========
    cout << "1-100中能被3整除的数:" << endl;
    for (int i = 1; i <= 100; i++) {  // 枚举范围:1到100
        if (i % 3 == 0) {              // 判断条件:能被3整除
            cout << i << " ";
        }
    }
    cout << endl << endl;
    
    // ========== 例题2:找出100-999中所有的水仙花数 ==========
    // 水仙花数:各位数字的立方和等于它本身,如 153 = 1³+5³+3³
    cout << "水仙花数:" << endl;
    for (int num = 100; num <= 999; num++) {  // 枚举所有三位数
        int temp = num;
        int ge = temp % 10;        // 分离个位
        temp /= 10;
        int shi = temp % 10;      // 分离十位
        temp /= 10;
        int bai = temp;           // 分离百位
        
        // 判断是否是水仙花数
        if (ge*ge*ge + shi*shi*shi + bai*bai*bai == num) {
            cout << num << " ";
        }
    }
    cout << endl << endl;
    
    // ========== 例题3:百钱买百鸡问题 ==========
    // 公鸡5元一只,母鸡3元一只,小鸡1元三只
    // 100元买100只鸡,问公鸡、母鸡、小鸡各多少只?
    cout << "百钱买百鸡方案:" << endl;
    for (int x = 0; x <= 20; x++) {      // 公鸡最多20只(100/5)
        for (int y = 0; y <= 33; y++) {  // 母鸡最多33只(100/3)
            int z = 100 - x - y;         // 小鸡数量
            // 判断:钱数等于100,且小鸡数量是3的倍数
            if (z % 3 == 0 && 5*x + 3*y + z/3 == 100) {
                cout << "公鸡:" << x << ",母鸡:" << y << ",小鸡:" << z << endl;
            }
        }
    }
    cout << endl;
    
    // ========== 例题4:模拟题 - 计算阶乘 ==========
    // 输入n,计算n! = 1×2×3×...×n
    int n;
    cout << "请输入n:";
    cin >> n;
    
    long long factorial = 1;  // 用long long防止溢出
    for (int i = 1; i <= n; i++) {
        factorial *= i;  // 累乘
    }
    cout << n << "! = " << factorial << endl;
    
    return 0;
}

贪心与递推

小高重点

🧠 核心逻辑

贪心算法:每一步都选"当前最优",希望最终得到全局最优!

就像找零钱:要找37元,先用最大的20元,再找17元;用10元,再找7元;用5元,再找2元;用2元,完成!

贪心的关键:
1. 确定贪心策略(每一步怎么选最优?)
2. 证明贪心正确性(这样选真的能得到最优解吗?)
注意:不是所有问题都能用贪心!

递推:从已知推出未知,利用前面的结果计算后面的结果!

就像斐波那契数列:第3项 = 第1项 + 第2项,第4项 = 第2项 + 第3项...

斐波那契数列:f(n) = f(n-1) + f(n-2)

💣 避坑指南

陷阱1:贪心策略错误!
有些问题贪心得不到最优解!比如硬币面值是1、3、4,要找6元:
贪心:4+1+1 = 3枚
最优:3+3 = 2枚
解决方法:做题时多举例验证,不确定就换个方法!
陷阱2:递推初始值搞错!
斐波那契数列:f(1)=1, f(2)=1,如果初始值写错,后面全错!
解决方法:先确定边界条件(最开始的几项),再写递推公式!
陷阱3:数组越界!
递推时访问 f[n-1] 和 f[n-2],如果 n=1,就会访问 f[0] 和 f[-1]!
解决方法:先处理边界情况(n=1, n=2),再进行递推!

💻 标准模板

greedy_dp_demo.cpp
#include <iostream>
using namespace std;

int main() {
    // ========== 贪心例题:找零钱问题 ==========
    // 有面值100、50、20、10、5、1的纸币,找零n元,最少几张?
    int money;
    cout << "请输入要找的金额:";
    cin >> money;
    
    int count = 0;
    int values[] = {100, 50, 20, 10, 5, 1};
    
    cout << "找零方案:" << endl;
    for (int i = 0; i < 6; i++) {
        int num = money / values[i];  // 能用几张这个面值的
        if (num > 0) {
            cout << values[i] << "元:" << num << "张" << endl;
            count += num;
            money %= values[i];  // 剩余金额
        }
    }
    cout << "总共需要" << count << "张纸币" << endl << endl;
    
    // ========== 递推例题1:斐波那契数列 ==========
    // f(1)=1, f(2)=1, f(n)=f(n-1)+f(n-2)
    int n;
    cout << "请输入n:";
    cin >> n;
    
    long long fib[100];  // 存储斐波那契数列
    fib[1] = 1;  // 边界条件
    fib[2] = 1;  // 边界条件
    
    for (int i = 3; i <= n; i++) {
        fib[i] = fib[i-1] + fib[i-2];  // 递推公式
    }
    cout << "斐波那契数列第" << n << "项:" << fib[n] << endl << endl;
    
    // ========== 递推例题2:爬楼梯问题 ==========
    // 每次可以爬1级或2级台阶,问爬到第n级有多少种方法?
    // 思路:到第n级,可以从n-1级爬1级,或从n-2级爬2级
    // 所以:f(n) = f(n-1) + f(n-2)
    
    int stairs;
    cout << "请输入台阶数:";
    cin >> stairs;
    
    long long dp[100];
    dp[1] = 1;  // 1级台阶:1种方法
    dp[2] = 2;  // 2级台阶:2种方法(1+1 或 2)
    
    for (int i = 3; i <= stairs; i++) {
        dp[i] = dp[i-1] + dp[i-2];  // 状态转移方程
    }
    cout << "爬到第" << stairs << "级有" << dp[stairs] << "种方法" << endl << endl;
    
    // ========== 递推例题3:数字三角形(简化版) ==========
    // 从顶部走到底部,每次只能走左下或右下,求最大路径和
    int triangle[5][5] = {
        {7, 0, 0, 0, 0},
        {3, 8, 0, 0, 0},
        {8, 1, 0, 0, 0},
        {2, 7, 4, 4, 0},
        {4, 5, 2, 6, 5}
    };
    
    // dp[i][j] 表示从顶部走到(i,j)的最大和
    int dp2[5][5] = {0};
    dp2[0][0] = triangle[0][0];
    
    for (int i = 1; i < 5; i++) {
        for (int j = 0; j <= i; j++) {
            if (j == 0) {
                dp2[i][j] = dp2[i-1][j] + triangle[i][j];  // 只能从左上来
            } else if (j == i) {
                dp2[i][j] = dp2[i-1][j-1] + triangle[i][j];  // 只能从右上来
            } else {
                dp2[i][j] = max(dp2[i-1][j-1], dp2[i-1][j]) + triangle[i][j];
            }
        }
    }
    
    // 找最后一行的最大值
    int maxSum = 0;
    for (int j = 0; j < 5; j++) {
        if (dp2[4][j] > maxSum) {
            maxSum = dp2[4][j];
        }
    }
    cout << "最大路径和:" << maxSum << endl;
    
    return 0;
}

📊 模块四:排序算法大全 仅限小高组

排序是算法的基础中的基础!掌握这四种排序,考试不慌!

冒泡排序

小高专属

🧠 核心逻辑

冒泡排序就像"气泡上浮",大的数字会一点点"浮"到后面!

  • 从左到右,两两比较相邻的数字
  • 如果左边的比右边的大,就交换它们
  • 一轮下来,最大的数字就"冒泡"到了最右边
  • 重复这个过程,直到所有数字排好序
冒泡排序口诀:
外层循环控制轮数,内层循环控制比较
相邻比较看大小,大的往后换位置
n个数排n-1轮,每轮少比一个数

💣 避坑指南

陷阱1:循环边界写错!
外层 i 从 0 到 n-1,内层 j 从 0 到 n-1-i,不是 n-i!
记住:每轮结束后,最后 i 个已经排好了,不需要再比较!
陷阱2:交换写错!
写成 a = b; b = a; 这样两个变量都变成 b 了!
解决方法:需要一个临时变量 temp 来保存中间值!

💻 标准模板

bubble_sort.cpp
#include <iostream>
using namespace std;

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    cout << "排序前:";
    for (int i = 0; i < n; i++) cout << arr[i] << " ";
    cout << endl;
    
    // ========== 冒泡排序核心代码 ==========
    for (int i = 0; i < n - 1; i++) {       // 外层:n-1轮
        for (int j = 0; j < n - 1 - i; j++) { // 内层:每轮比较n-1-i次
            if (arr[j] > arr[j + 1]) {     // 相邻比较
                // 交换
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
    
    cout << "排序后:";
    for (int i = 0; i < n; i++) cout << arr[i] << " ";
    cout << endl;
    
    return 0;
}
✨ 冒泡排序可视化动画
速度:

点击"播放动画"开始演示冒泡排序过程

选择排序

小高专属

🧠 核心逻辑

选择排序就像"选美比赛",每轮选出最小的放到前面!

  • 第1轮:在所有数中找最小的,放到第1个位置
  • 第2轮:在剩下的数中找最小的,放到第2个位置
  • 以此类推,直到所有数排好
选择排序 vs 冒泡排序:
• 冒泡:不断交换,大的往后冒
• 选择:找最小值,只交换一次
选择排序交换次数更少!

💻 标准模板

selection_sort.cpp
#include <iostream>
using namespace std;

int main() {
    int arr[] = {64, 25, 12, 22, 11};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    // ========== 选择排序核心代码 ==========
    for (int i = 0; i < n - 1; i++) {     // 外层:n-1轮
        int minIdx = i;                   // 假设当前最小值在位置i
        
        // 在i后面的元素中找最小值
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIdx]) {
                minIdx = j;               // 更新最小值位置
            }
        }
        
        // 把最小值交换到位置i
        if (minIdx != i) {
            int temp = arr[i];
            arr[i] = arr[minIdx];
            arr[minIdx] = temp;
        }
    }
    
    // 输出结果
    for (int i = 0; i < n; i++) cout << arr[i] << " ";
    
    return 0;
}

插入排序

小高专属

🧠 核心逻辑

插入排序就像"整理扑克牌",每次拿一张牌,插到正确的位置!

  • 把第一个数看作已排好的部分
  • 取出下一个数,从后往前比较,找到合适的位置插入
  • 重复这个过程,直到所有数都插入完毕
插入排序特点:
• 对于基本有序的数据,效率很高
• 稳定排序(相等的元素不会交换位置)
• 适合小规模数据排序

💻 标准模板

insertion_sort.cpp
#include <iostream>
using namespace std;

int main() {
    int arr[] = {12, 11, 13, 5, 6};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    // ========== 插入排序核心代码 ==========
    for (int i = 1; i < n; i++) {        // 从第2个元素开始
        int key = arr[i];                // 当前要插入的元素
        int j = i - 1;                   // 从前一个位置开始比较
        
        // 把比key大的元素往后移
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];        // 往后移
            j--;
        }
        
        // 插入key到正确位置
        arr[j + 1] = key;
    }
    
    // 输出结果
    for (int i = 0; i < n; i++) cout << arr[i] << " ";
    
    return 0;
}

计数排序(桶排序)

小高专属

🧠 核心逻辑

计数排序就像"投票计数",统计每个数字出现了几次!

  • 创建若干个"桶",每个桶对应一个数字
  • 遍历数组,把每个数字放到对应的桶里
  • 最后按顺序把桶里的数字倒出来
计数排序特点:
• 速度超快!时间复杂度 O(n+k)
• 但只能排整数,且范围不能太大
• 适合数据范围小的情况(比如分数0-100)

💣 避坑指南

陷阱:数据范围太大!
如果数据范围是 0 到 1000000,就需要开 1000001 个桶,内存会爆!
解决方法:数据范围大时,用其他排序算法!

💻 标准模板

counting_sort.cpp
#include <iostream>
#include <cstring>  // 用于memset
using namespace std;

int main() {
    int arr[] = {4, 2, 2, 8, 3, 3, 1};
    int n = sizeof(arr) / sizeof(arr[0]);
    
    // ========== 计数排序核心代码 ==========
    // 步骤1:找最大值,确定桶的数量
    int maxVal = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] > maxVal) maxVal = arr[i];
    }
    
    // 步骤2:创建桶并初始化为0
    int bucket[100] = {0};  // 假设数据范围不超过100
    
    // 步骤3:统计每个数字出现的次数
    for (int i = 0; i < n; i++) {
        bucket[arr[i]]++;
    }
    
    // 步骤4:按顺序输出(从小到大)
    cout << "排序后:";
    for (int i = 0; i <= maxVal; i++) {
        // 每个数字输出它出现的次数
        for (int j = 0; j < bucket[i]; j++) {
            cout << i << " ";
        }
    }
    cout << endl;
    
    // 如果要从大到小输出,反向遍历即可
    cout << "从大到小:";
    for (int i = maxVal; i >= 0; i--) {
        for (int j = 0; j < bucket[i]; j++) {
            cout << i << " ";
        }
    }
    cout << endl;
    
    return 0;
}
排序算法 时间复杂度 空间复杂度 稳定性 适用场景
冒泡排序 O(n²) O(1) 稳定 小规模数据,教学演示
选择排序 O(n²) O(1) 不稳定 小规模数据,交换次数少
插入排序 O(n²) O(1) 稳定 基本有序的数据,小规模
计数排序 O(n+k) O(k) 稳定 数据范围小的整数

🔢 模块五:初等数论核心 小低/小高通用

数论是数学与编程的完美结合!掌握这些,解决数学问题如虎添翼!

素数判断

通用

🧠 核心逻辑

素数(质数):只能被1和它本身整除的大于1的正整数。

比如:2, 3, 5, 7, 11, 13, 17, 19, 23...

判断方法:看 n 能不能被 2 到 n-1 之间的数整除。如果能,就不是素数!

优化技巧:
不需要试到 n-1,只需要试到 √n 就够了!
因为如果 n = a × b,那么 a 和 b 中至少有一个 ≤ √n!
i * i <= n 来判断,避免开根号的精度问题!

💣 避坑指南

陷阱1:忘记处理特殊情况!
0 和 1 不是素数!2 是最小的素数!
解决方法:先判断 n <= 1 返回 false,再判断 n == 2 返回 true。
陷阱2:i * i 溢出!
当 n 很大时,i * i 可能超过 int 范围!
解决方法:用 long long,或者写成 i <= n / i。

💻 标准模板

prime_check.cpp
#include <iostream>
using namespace std;

// 判断是否为素数的函数
bool isPrime(int n) {
    // 特殊情况处理
    if (n <= 1) return false;  // 0和1不是素数
    if (n == 2) return true;   // 2是素数
    if (n % 2 == 0) return false;  // 偶数不是素数
    
    // 只需要检查到√n
    for (int i = 3; i * i <= n; i += 2) {  // 只检查奇数
        if (n % i == 0) {
            return false;  // 能被整除,不是素数
        }
    }
    return true;  // 是素数
}

int main() {
    int n;
    cout << "请输入一个正整数:";
    cin >> n;
    
    if (isPrime(n)) {
        cout << n << " 是素数" << endl;
    } else {
        cout << n << " 不是素数" << endl;
    }
    
    // 输出1-100的所有素数
    cout << endl << "1-100的素数:" << endl;
    for (int i = 1; i <= 100; i++) {
        if (isPrime(i)) {
            cout << i << " ";
        }
    }
    cout << endl;
    
    return 0;
}

辗转相除法(求最大公约数)

小高专属

🧠 核心逻辑

最大公约数(GCD):两个数都能整除的最大的那个数。

比如:12 和 18 的最大公约数是 6(因为 12÷6=2,18÷6=3)

辗转相除法原理

  • gcd(a, b) = gcd(b, a % b)
  • 不断用除数和余数替换原来的两个数
  • 直到余数为0,此时的除数就是最大公约数
举例:求 gcd(48, 18)
48 ÷ 18 = 2 余 12 → gcd(18, 12)
18 ÷ 12 = 1 余 6 → gcd(12, 6)
12 ÷ 6 = 2 余 0 → 余数为0,答案是 6

💣 避坑指南

陷阱:顺序搞错!
gcd(a, b) 中,a 可以比 b 小,算法会自动调整!
比如 gcd(18, 48):18 ÷ 48 = 0 余 18 → gcd(48, 18),自动换过来了!

💻 标准模板

gcd.cpp
#include <iostream>
using namespace std;

// 方法1:while循环版本
int gcd(int a, int b) {
    while (b != 0) {
        int temp = a % b;  // 计算余数
        a = b;              // b变成新的a
        b = temp;           // 余数变成新的b
    }
    return a;  // 当b=0时,a就是最大公约数
}

// 方法2:递归版本(更简洁)
int gcdRecursive(int a, int b) {
    if (b == 0) return a;
    return gcdRecursive(b, a % b);
}

// 求最小公倍数 LCM
// 公式:lcm(a, b) = a * b / gcd(a, b)
int lcm(int a, int b) {
    return a / gcd(a, b) * b;  // 先除后乘,防止溢出
}

int main() {
    int a, b;
    cout << "请输入两个正整数:";
    cin >> a >> b;
    
    cout << "最大公约数:" << gcd(a, b) << endl;
    cout << "最小公倍数:" << lcm(a, b) << endl;
    
    // 验证:gcd × lcm = a × b
    cout << "验证:" << gcd(a, b) << " × " << lcm(a, b);
    cout << " = " << (long long)gcd(a, b) * lcm(a, b);
    cout << "," << a << " × " << b << " = " << (long long)a * b << endl;
    
    return 0;
}

进制转换

小高专属

🧠 核心逻辑

十进制转二进制:"除基取余,逆序排列"

  • 把十进制数不断除以2,记录每次的余数
  • 直到商为0
  • 把所有余数倒过来写,就是二进制!
举例:13 转二进制
13 ÷ 2 = 6 余 1
6 ÷ 2 = 3 余 0
3 ÷ 2 = 1 余 1
1 ÷ 2 = 0 余 1
逆序排列:1101,所以 13 = 1101(二进制)

💣 避坑指南

陷阱1:余数顺序搞反!
最先得到的余数是最低位(最右边),最后得到的是最高位(最左边)!
解决方法:用数组存余数,然后倒序输出!
陷阱2:特殊情况处理!
如果 n = 0,结果是 0,不是空!
解决方法:单独处理 n = 0 的情况。

💻 标准模板

base_convert.cpp
#include <iostream>
using namespace std;

int main() {
    int n;
    cout << "请输入一个十进制数:";
    cin >> n;
    
    // ========== 十进制转二进制 ==========
    int binary[32];  // 存储二进制各位(int最多32位)
    int count = 0;   // 记录位数
    
    int temp = n;
    
    // 特殊情况:0
    if (temp == 0) {
        binary[count++] = 0;
    } else {
        // 除基取余
        while (temp > 0) {
            binary[count++] = temp % 2;  // 取余数
            temp /= 2;                     // 除以2
        }
    }
    
    // 逆序输出
    cout << n << " 的二进制:";
    for (int i = count - 1; i >= 0; i--) {
        cout << binary[i];
    }
    cout << endl;
    
    // ========== 十进制转任意进制(2-16) ==========
    int base;
    cout << endl << "请输入目标进制(2-16):";
    cin >> base;
    
    char digits[] = "0123456789ABCDEF";  // 进制字符表
    char result[32];
    count = 0;
    temp = n;
    
    if (temp == 0) {
        result[count++] = '0';
    } else {
        while (temp > 0) {
            result[count++] = digits[temp % base];
            temp /= base;
        }
    }
    
    cout << n << " 的 " << base << " 进制:";
    for (int i = count - 1; i >= 0; i--) {
        cout << result[i];
    }
    cout << endl;
    
    // ========== 二进制转十进制 ==========
    string binStr;
    cout << endl << "请输入一个二进制数:";
    cin >> binStr;
    
    int decimal = 0;
    for (int i = 0; i < binStr.size(); i++) {
        decimal = decimal * 2 + (binStr[i] - '0');
    }
    cout << binStr << " 的十进制:" << decimal << endl;
    
    return 0;
}