2026年7月

排序(Round 1)

-1 检查浏览器渲染数学公式情况

因为笔者发布文章当天,本博客仍然存在 Latex 可能无法渲染的情况,下面是一个 Latex 示范:

$x$

  • 如果正常显示为衡水体 x,则代表读者的浏览器已经成功渲染了 Latex
  • 如果显示为 带有美元符号的 x 即 $x$(已除去换行符),则代表读者的浏览器没有成功渲染 Latex,读者可能需要刷新重试,如果刷新多次仍然无法渲染,请尝试换一个浏览器/网络环境

0 框架

在本文中,我将重点介绍 排序,感谢 深入浅出程序设计竞赛(基础篇) 提供重要支持,本文有一部分的内容是基于本书修改

排序 是在 OI 算法中重要的一部分,虽然大部分场景下都可以用 sort 解决(限于 bits/stdc++.h),但是掌握具体的内容还是必不可少的,相对于初赛而言

$$ \begin{aligned} &\textbf{排序(Round 1)} \begin{cases} &\text{计数排序}\\ &\text{选择排序、冒泡排序、插入排序}\\ &\text{快速排序}\\ &\text{排序算法的应用}\\ &\text{汇总}\\ \end{cases} \end{aligned} $$

1 计数排序

【题目样本 1.1】P1271【模板】排序

题目描述

https://www.luogu.com.cn/problem/P1271

学校正在选举学生会成员,有 $n(n \leq 999)$ 名候选人,每名候选人编号分别从 $1$ 到 $n$, 现在收集到了 $m(m \leq 200000)$ 张选票,每张选票都写了一个候选人编号

现在想把这些堆积如山的选票按照投票数字从小到大排序。输入 $n$ 和 $m$ 以及 $m$ 个选票上的数字,求出排序后的选票编号。例如,当有 $5$ 个候选人,$10$ 张选票分别是 $2、5、2、2、5、2、2、2、1、2$ 时,需要输出 1222222255

分析

这题本质有很多方法,但是由于是 计数排序,因此:

在投票区放上 $n$ 个投票箱,投票人支持谁就把票投入对应的投票箱中,投票后只须统计每个投票箱有几张选票,直接按照候选人编号取出来就可以完成排序操作

也不需要排序了

参考代码

#include <bits/stdc++.h>
using namespace std;
int n,m,i,j,x,f[100005];
int main(){
    cin>>n>>m;
    for(i=1;i<=m;i++) cin>>x,f[x]++;
    for(i=1;i<=n;i++) for(j=1;j<=f[i];j++) cout<<i<<" ";
}

这里倡导数组从 $1$ 开始存

只需要开一个大小不小于 $n$ 的数组作为票箱,依次读入选票,然后将选票号数加到对应的票箱中,最后按照每个票箱的数量输出候选人编号即可

在这个过程中,甚至不需要存储下每一张选票

在完成输入并计算时,$f[]$ 状态如下

$f$12345
17002

计数排序

这种排序方法被称为 计数排序

读入选票并统计的时间复杂度是 $O(m)$,输出选票的时间复杂度是 $O(m+n)$(第一层循环做 $n$ 次,$f[i]$ 至多为 $m$),空间复杂度是 $O(n)$(因为 $f[]$)

所以计数排序只能用于排序编号范围不是很大的数字,如果需要排序的数字要到 $10^9$,别说运行时间了,内存都无法存得下这么大的范围的数组

此外,如果希望将一些浮点数或者字符串进行排序,就没办法去建造合适的 投票箱,所以就不能使用这种算法了(把下标存成 $\text{浮点数} \times 10^\text{浮点数位数}$ 也是一个不错的选择)

也有改进方法,即 桶排序&基数排序,思路类似,但是复杂

无论是上段和计数,这些排序都是基于分类,并非比较

2 选择排序、冒泡排序、插入排序

【题目样本 2.1】数列排序

题目描述

输入 $n(n \leq 1000)$ 个数字 $a_i(a_i \leq 10^9)$, 将其从小到大排序后输出

分析

设有 $5$ 张纸牌,他们是 $4,1,9,5,1$,打牌时如何排序?

解法 1:选择排序

分析

求最大值:max()

求最小值:min()

求一序列最值:for(int i=1;i<n;i++) ma=max(ma,a[i]);,a[] 为序列,$n$ 为数量,$ma$ 存放最大值

核心思想:打擂台

如何联想到本题?

[collapse status="false" title="答案"]
从第一张牌到最后一张牌中找到最小的一张,放在最前面的位置;然后从第二张牌到最后一张牌中继续找到最小的一张,放到第二位……如此反复,就可以得到从小到大的序列
[/collapse]

这里有一张不错的图,来自 深入浅出,因此贴在这

若遇到大小相等的牌,continue

参考代码片段
for (int i = 0; i < n - 1; i++) {
    for (int j = i + 1; j < n; j++)
        if (a[j] < a[i]) {
            int p = a[i];
            a[i] = a[j];
            a[j] = p;
        }
}

解法 2:冒泡排序

分析

如果尝试把最大的一项放在后面,虽然可以倒过来使用选择排序,但是可以试试别的办法

这里有一张图,来自 豆包,它打了一个类似于文章的比方,讲述了 选择排序 与 冒泡排序 的区别,前情提要是 现有扑克牌:9、5,要排成5、9

首先比较第 $1$ 张牌和第 $2$ 张牌,如果后面的牌比前面的牌小,那么就交换位置;然后比较第 $2$ 张牌和第 $3$ 张牌,同样保证第 $3$ 张牌要大于第 $2$ 张牌……依此类推, 直到比较最后一张牌和倒数第二张牌,这样就能保证最后面的一张牌是最大的一张了

继续进行新的一轮,利用上面一样的交换,但是需要注意的是,由于最后一张牌已经是手牌中最大的一张了,所以只需要比较到倒数第二张就行了

这一轮后结束后倒数第二张牌就是第二大的牌(当然,也有可能是并列最大)。经过 $n-1$ 轮排序后,就得到了有序的手牌

下图同样来自 深入浅出,用于理解

参考代码片段
for (int i = 0; i < n - 1; i++) {
    for (int j = 0; j < n - i - 1; j++)
        if (a[j] > a[j + 1]) {
            int p = a[j];
            a[j] = a[j + 1];
            a[j + 1] = p;
        }
}

[scode type="green"]
int p=a[j];a[j]=a[j+1];a[j+1]=p; 推荐用 swap(a[j],a[j+1]); 来取代,用于交换
[/scode]

解法 3:插入排序

分析

一种很形象的比喻

你在玩纸牌游戏的时候,从牌堆里面抽牌,抽到一张牌,放到你的手牌里,你是怎么保证手牌内的牌是有序的呢?

如果你能通过 Cloudflare 的人机验证,你应该会看看抓到牌的大小,再看看手牌,定位到合适的位置,插入

上面描述的过程,即 插入排序

  1. 把手牌分为有序的和无序的两部分
  2. 最开始有序的就只有一张牌,也就是第一张
  3. 要把接下来的 无序区 中的一张牌(称为 待插牌 )插入到 有序区 中,就是从有序区的末尾开始往前比较,如果待插牌比正在比较的牌小,那么就把有序区的正在比较的牌往后面放一格,然后继续往前面进行比较,直到待插牌遇到不大于自己的牌或者成为第一个为止
  4. 这时,待插牌就可以填入留出来的缺口中。反复将无序区中的待插牌插入到有序区中,直到所有的牌都在有序区

同样的,这里有一张非常生动的图,供读者查阅

下面这是来自 深入浅出 的 插入排序 实现代码,进行了注释的补充

for(int i = 1; i < n; i++) {
    int now = a[i], j;          // 记录待插入元素,稍候会放进去
    for(j = i - 1; j >= 0; j--) // 一个很经典的模板:元素后移
        if(a[j] > now)
            a[j + 1] = a[j];    // 大于now的元素后移
        else
            break;
    a[j + 1] = now;             // 插入到正确位置
}

选择排序

为了加强读者的记忆,这里重新梳理一遍

解法 1 这种算法被称为 选择排序

从代码中可以看出有两重循环,读者可以自己计算一下已知 $n$ 的情况下一共需要比较的次数

选择排序的算法时间复杂度是 $O(n^2)$;由于只需要一个数组存下这些数字即可,空间复杂度是 $O(n)$

[collapse status="false" title="答案"]

$$ \frac{n(n-1)}{2} $$

[/collapse]

冒泡排序

解法 2 这种算法被称为 冒泡排序,在一轮中,最大的那张牌会一点一点地从左移动到右边,像冒泡泡一样

读者也可以计算一下排序的过程中需要进行几次比较

冒泡排序算法的时间复杂度也是 $O(n^2)$,空间复杂度也是 $O(n)$

冒泡排序相比于选择排序并没有实质性的改进

有一种冒泡排序的改良版叫作希尔排序,不是相邻位置的两个数字比较,而是比较距离更远的两个数字,虽然能略微优化一些复杂度,但依然不实用

猴子排序同样也是一种奇怪的办法

插入排序

这种算法叫作 插入排序

如果序列本来已经从小到大排序,那么每次无序区的待插牌都不会插进有序区中,而是紧贴在有序区后使有序区一直扩大,那么只需要 $n-1$ 次比较就可以完成算法流程

然而,如果序列是从大往小排序,那么每次插入都会将有序区中的所有牌往后面挪动一次,这样速度就非常慢了

讲时间复杂度一般会考虑最悲观的情况,所以插入排序的算法复杂度还是 $O(n^2)$,和选择排序、冒泡排序的复杂度一致,它们空间复杂度也是一样的

不过,如果能保证序列 基本有序 , 那么使用插入排序的效率可能会不错

不推荐在 Contest 中使用这些方法,耗时过久

笔者认为,学习这些是为了掌握原理,在第一轮比赛中获得较好的成绩,在历年的 OI 比赛中,有相当一部分的套题涉及了这个内容

3 快速排序

【题目样本 3.1】P1177 快速排序

题目描述

https://www.luogu.com.cn/problem/P1177

输入 $n(n \lt 10^5)$ 个数字 $a_i(a_i \lt 10^9)$,将其从小到大排序后输出

分析

这个数据量就很大了,插入排序、选择排序、冒泡排序都无法完成。这次将介绍在 OI 中最普遍使用的排序算法 快速排序,简称快排,顾名思义

快速排序说起来其实也挺简单的,但是需要读者能够理解递归

[collapse status="false" title="递归"]
递归就是函数调用自己。

你的快速排序就是递归:

void Qsort(int x, int y) {
    if (x >= y) return;          // 终止条件
    // ... 分区操作 ...
    Qsort(x, i);                 // 自己调用自己,排左边
    Qsort(i+2, y);               // 自己调用自己,排右边
}

执行过程:先把数组分成左右两部分,然后分别对两部分再调用同样的函数继续分,直到每部分只剩一个元素。

每次调用会占用栈内存,递归太深会栈溢出。你的代码最坏情况(已有序数据)递归深度达 $200000$,风险很大。

上述内容来自 Deepseek V4 Pro,请注意辨别
[/collapse]

算法的大致过程是对于一个无序序列,找到一个 哨兵数 ,将序列中所有比哨兵数小的数字都在哨兵数的左边,所有比哨兵数大的数字都在哨兵数的右边;然后分别对哨兵数左边和右边再使用同样的方法找到新的哨兵数,并再次进行分类,直到集合不可分割为止

怎么选择哨兵呢?随便选,可以是第一个,可以是中间那个,也可以在序列中随机选择

选择好哨兵,然后从序列左端开始寻找第一个比哨兵大的数字,从右边选择第一个比哨兵小的数字,然后交换这两个数;接着继续从左边找到比哨兵大的数字,右边比哨兵小的数字并交换……直到将序列分为两组,左边序列都不大于哨兵,右边序列都不小于哨兵,就可以分别对左边和右边进行排序了

下面同样还有一张生动的图,建议配合代码食用

参考代码

有多种办法可以实现,这是一种实现办法,也基于书本内容进行了适当的补充更多的办法请前往 排序的应用 这一章节

void qsort(int a[], int l, int r) { // a[] 传递数组
    int i = l, j = r, flag = a[(l + r) / 2], tmp;  // flag是基准值(哨兵)
    do {
        while (a[i] < flag) i++;   // 左指针右移,跳过比基准小的
        while (a[j] > flag) j--;   // 右指针左移,跳过比基准大的
        if (i <= j) {              // 找到一对需要交换的数
            tmp = a[i]; a[i] = a[j]; a[j] = tmp;
            i++; j--;              // 继续移动指针
        }
    } while (i <= j);              // 两指针未交错就继续
    if (l < j) qsort(a, l, j);     // 递归排左半部分
    if (i < r) qsort(a, i, r);     // 递归排右半部分
}

快速排序

虽然在极端情况下(比如数列已经有序,且每次选择哨兵都会分出长度为 $1$ 的小段),快速排序的时间复杂度是 $O(n^2)$,但是如果随机选择哨兵,则很难出现这种情况

实际上,随机化快速排序的算法复杂度是 $O(n \log n)$,空间复杂度是 $O(n)$,而且不需要额外的辅助空间,所以是一种最为实用的排序算法

快速排序利用了 分治 思想,后面可能会出一篇 排序 这一专题的补充篇,介绍了更多的非基础排序

额外的,二分查找法 也利用了 分治思想,这个方法也可能会在未来单独出一篇专题进行讲解

【题目样本 3.2】 P1923 求第 $k$ 小的数

题目描述

https://www.luogu.com.cn/problem/P1923

输入 $n(n \lt 10^6 \text{ 且 } n \text{ 为奇数})$ 个数字 $a_i(a_i \lt 10^9)$,输出这些数字的第 $k$ 小的数,最小的数是第 $0$ 小

分析

如上 URL 中的 题目描述,如果你是真的要训练 快排,请 不要 使用 nth_element STL,当然我估计你也不知道

虽然可以直接排序,然后取数组中的第 $k$ 个作为答案进行输出,但是直接排序一遍的复杂度是 $O(n \log n)$,不适合这道头目,我们可以借鉴 快排 的 分治思想 把时间复杂度降至 $O(n)$

[collapse status="false" title="可以降低时间复杂度的原因"]

核心原因在于每次只需要递归处理一边,而不是两边

更多的:

  1. 快速排序的分治过程
    每次选一个基准(pivot),将数组分成三部分:

    • 左边:所有元素 ≤ pivot
    • 中间:pivot 本身
    • 右边:所有元素 > pivot

    然后递归地对左右两边分别排序,所以总复杂度是 (O(n \log n))。

  2. 求第 k 小数的优化
    在快速选择(Quick Select)中,不需要对两边都排序,只需要判断第 k 小的数在哪一边:

    • 如果 (k) 小于左边元素个数,就只在左边递归查找
    • 如果 (k) 等于左边元素个数 + 1,那么 pivot 就是答案
    • 否则,只在右边递归查找,并且调整 k 的值
  3. 复杂度分析

    • 每次递归处理的数组大小大约是上一次的一半(平均情况)
    • 总比较次数:

      $$ n + \frac{n}{2} + \frac{n}{4} + \cdots = 2n $$

    • 因此平均时间复杂度是 (O(n))

上述内容来源 Deepseek V4 Pro 修改而得
[/collapse]

和快速排序的方法一样,任意取一个哨兵,将序列分为两部分,左边部分的所有数字都不大于右边的数字

  • 如果 $k$ 在左边的范围,则递归求解左边的部分
  • 如果在右边,则递归求解右边的部分
  • 但是也有可能两边都不属于(比如说 $j=4$ 而 $i=6$ , 但刚好 $k=5$),那么直接就可以给出答案

参考代码

这个代码是 深入浅出 提供的,由 Deepseek V4 Pro 和 笔者 改写而得

int ans = 0, k; // 记录答案,以及求第 k 小
void findkth(int a[], int l, int r) { // 引入数组的地址
    if(l == r) {
        ans = a[l]; // 区间长度为 1 时,记录答案
        return;
    }

    int i = l, j = r, flag = a[(l + r) / 2], tmp;
    do {
        while (a[i] < flag) i++; // 从左找比哨兵大的数
        while (a[j] > flag) j--; // 从右找比哨兵小的数
        if (i <= j) { // 交换
            tmp = a[i]; a[i] = a[j]; a[j] = tmp;
            i++; j--;
        }
    } while (i <= j);

    if (k <= j) findkth(a, l, j); // 第 k 小的数字在左区间
    else if (i <= k) findkth(a, i, r); // 第 k 小的数字在右区间
    else findkth(a, j + 1, i - 1); // 第 k 小的数字既不在左区间,也不在右区间
}

尽管 此参考代码片段(快速选择) 相对于 快排(模板代码)类似,但是每次处理的区间长度期望下都会被减半,复杂度是线性的

快速排序 - 快速选择

这个概念是在书中没有的,是由 Deepseek V4 Pro 提出

它基于 快速排序 的分治思想,但并不是完整的排序算法,而是一种 选择算法 ——专门用来查找数组中第 $k$ 小(或第 $k$ 大)的元素

下面是一张表格,阐释了区别

快速排序快速选择
目的将整个数组排好序只找出第 $k$ 小的元素
递归方向同时递归左右两边只递归包含答案的那一边
时间复杂度(O(n \log n))平均 (O(n))
是否完全排序是否

所以,这种方法叫做 快速选择,也可以理解为"快速排序的简化版"或"基于快排分治的选择算法"

[collapse status="false" title="函数图像(可能有误)"]

这个部分由 Deepseek V4 Pro 与 豆包 提供,它们分别为这个部分 提供函数生成代码 和 提供函数图,在这里表示对他们最诚挚的感谢

但是目前看起来它们携手合作画出来的图有误,作为初二学生的笔者,无法验证这张图的真伪,所以这个问题抛给读者,抱歉

[/collapse]

4 排序的应用

这里提供了一些 排序 的拓展用法,在第二轮比赛时可能有用

【题目样本 4.1】P1059 [NOIP 2006 普及组] 明明的随机数

题目描述

https://www.luogu.com.cn/problem/P1059

给出 $N(N \lt 10^2)$ 个 $1 \sim 10^3$ 的数字,输出去重后剩余数字的个数以及去重排序后的序列

解法 1:计数排序

在拿到新的选票时发现投票箱中已经有这个选票了,就扔掉这张选票,保持每个票箱中最多只有一张票

最后将有票的票箱编号输出即可

这里要求先输出不同数字的个数,可以先枚举一遍票箱记录,输出剩余数字的个数,然后再枚举一遍票箱,输出序列

也可以创建一个新的数组,在枚举票箱的时候将有票的编号复制进新的数组中

解法 2:STL

当然也可以直接对这些数据进行排序,然后从小到大每个数字只输出一个

考虑数据规模,可以使用上面提到的所有排序方式

不过,这次介绍排序的 STL, 这样就不需要去亲自实现排序算法了

关于 STL,后面可能会出一个专门的专题,STL = Standard Template Library(标准模板库)

有了这个就不用自己手搓了

排序 STL 1:sort
分析

std::sort 被定义于 algorithm 头文件中,因此,你在使用前需要先导入这个头文件,为了方便笔者,笔者会尽可能地在后文省略较多的不影响理解的内容

sort 的基本用法如下

// 形式1:默认升序排序
sort(起始迭代器, 结束迭代器);

// 形式2:自定义比较规则
sort(起始迭代器, 结束迭代器, 比较函数/仿函数/Lambda); // 比较函数即 cmp 用的较多

一个例子,sort(a,a+n,cmp),对 $a[]$ 数组从 $a[0]$ 到 $a[n-1]$ 进行排序,cmp 是指自定义排序函数,如果是将数组 $a[]$ 从小到大排序,那么这一项可以省略

参考代码
#include <iostream>
#include <algorithm>
using namespace std;
int const MAXN = 1010;
int a[MAXN], ans[MAXN], n, cnt = 0, tmp = -1;
int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    sort(a, a + n);
    for (int i = 0; i < n; i++) {
    if (a[i] != tmp) ans[cnt++] = a[i]; /* 当前的和上一个不同才复制到新数组 */
    tmp = a[i];
    }
    cout << cnt << endl;
    for (int i = 0; i < cnt; i++) cout << ans[i] << ' ';
    return 0;
}
sort

这里,sort 的时间复杂度是 $O(n \log n)$

如果要对数组 $a[1]$ 到 $a[n]$ 排序,就要用 sort(a+1, a+n+1)

那么,如果要求从大到小排序,只需要定义一个名字是 cmp 的自定义比较函数即可,这个函数的输入是两个元素,如果第一个在第二个之前,则返回 true, 否则返回 false。代码如下:

bool cmp(int q,int h){ // q 前 h 后
    return q>h;
}
排序 STL 2:unique
分析

std::unqiue 同样被定义于 algorithm 头文件中

unique(a,a+n):对 $a[]$ 数组从 $a[0]$ 到 $a[n-1]$ 进行去重,要求 $a[]$ 数组已经有序,返回去重后最后一个元素对应的指针

如果使用 unique, 就可以不用 ans 新数组了,可以直接获得去重后的数组和最终元素个数

参考代码
sort(a,a+n);
cnt=unique(a,a+n)-a; // 剩下的数总数
cout<<cnt<<"\n";
for(int i=0;i<cnt;i++) cout<<a[i]<<" ";

【题目样本 4.2】P1093 [NOIP 2007 普及组] 奖学金

题目描述

https://www.luogu.com.cn/problem/P1093

有 $n$($n \le 300$)名学生的语文、数学、英语成绩,这些学生的学号依次是从 $1$ 到 $n$。需要对这些学生进行排序。如果总分相同,则语文分数高者名次靠前;如果语文成绩还相同,学号小者靠前。输出排名前 $5$ 的学生学号和总分

解法 1:STL

分析

定义一个 struct 结构体,命名为 student 存储学生各项信息,使用 sort 附带 cmp 比较函数进行排序,最后输出排序结果,下面是排序优先级

  1. 总分,高者优先
  2. 语文,高者优先
  3. 学好,小者优先
参考代码
#include <algorithm>
#include <iostream>
using namespace std;

int const MAXN = 310;
int n;

struct student {
    int id, chinese, total;
} a[MAXN];

int cmp(student a, student b) {
    if (a.total != b.total) return a.total > b.total;      // 总分先定胜负
    if (a.chinese != b.chinese) return a.chinese > b.chinese; // 然后比语文
    return a.id < b.id;                                    // 最后比学号
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        int math, english;
        cin >> a[i].chinese >> math >> english;
        a[i].total = a[i].chinese + math + english;
        a[i].id = i + 1;
    }

    sort(a, a + n, cmp);

    for (int i = 0; i < 5; i++) {
        cout << a[i].id << " " << a[i].total << endl;
    }

    return 0;
}

解法 2:选择排序

分析

时间复杂度 $O(kn)$,此题 $k=5$,即 $O(n)$

解法 3:插入排序

分析

维护排名前 $5$ 数组作为比较对象(手牌)

时间复杂度 $O(n)$,运行效率不高

【题目样本 4.3】P1781 宇宙总统

题目描述

https://www.luogu.com.cn/problem/P1781

共有 ( n )(( n \le 20 ))个非凡拔尖的人竞选总统,现在票数已经统计完毕,请算出谁能够当上总统。第一行输出候选人编号,第二行输出选票数量。票数可能很大,最大会有 100 位

解法 1:计数排序

分析

由于本题只需要求出最大的那一个数字,所以用“打擂台”法

但是数字很大,没办法直接存下来(long long 也存不下)

但是需要高精度,不考虑单开一个话题

解法 2:结构体

分析

存进字符串。不过不能直接对这些字符串进行大小比较,因为字符串比较是比较字典序(第一位小的在前面,如果相同则比较第二位,以此类推),例如 $10000$ 小于 $1200$ 小于 $200$

得手搓 cmp

参考代码

struct node {
    string x;    // 票数
    int num;     // 候选人编号
} s[MAXN];

bool cmp(node a, node b) {
    if (a.x.length() != b.x.length()) {
        return a.x.length() > b.x.length(); // a 比 b 位数多时 a 在前面
    }
    return a.x > b.x; // 位数相同,但 a 字典序排列比 b 大
}

5 汇总

练习

笔者为读者提供了一些洛谷上没有的题目,供练习

C++ 常用排序算法一览表

额外的,这里有一张特别大的表格,供读者查阅

排序算法平均时间复杂度最好时间复杂度最坏时间复杂度空间复杂度最坏空间复杂度稳定性特点
冒泡排序$O(n^2)$$O(n)$$O(n^2)$$O(1)$$O(1)$✅ 稳定相邻元素两两比较,大数后移;优化版可提前结束
选择排序$O(n^2)$$O(n^2)$$O(n^2)$$O(1)$$O(1)$❌ 不稳定每轮选最小放到前面;交换次数最少(n次)
插入排序$O(n^2)$$O(n)$$O(n^2)$$O(1)$$O(1)$✅ 稳定将元素插入已排序区;数据量小或基本有序时极快
希尔排序$O(n \log n) \sim O(n^2)$$O(n \log n)$$O(n^2)$$O(1)$$O(1)$❌ 不稳定分组插入排序;增量序列决定效率
归并排序$O(n \log n)$$O(n \log n)$$O(n \log n)$$O(n)$$O(n)$✅ 稳定分治+合并;适合链表和外部排序
快速排序$O(n \log n)$$O(n \log n)$$O(n^2)$$O(\log n)$$O(n)$❌ 不稳定分治+分区;实际最快,但最坏退化为 $O(n^2)$
堆排序$O(n \log n)$$O(n \log n)$$O(n \log n)$$O(1)$$O(1)$❌ 不稳定基于堆数据结构;适合求 Top K
计数排序$O(n + k)$$O(n + k)$$O(n + k)$$O(k)$$O(k)$✅ 稳定非比较排序;适用于整数且范围 $k$ 不大
桶排序$O(n + k)$$O(n + k)$$O(n^2)$$O(n + k)$$O(n + k)$✅ 稳定分桶+桶内排序;数据分布均匀时极快
基数排序$O(d \times (n + k))$$O(d \times (n + k))$$O(d \times (n + k))$$O(n + k)$$O(n + k)$✅ 稳定按位排序(LSD/MSD);适合整数或字符串

[collapse status="false" title="C++ 常用排序算法一览表(无特点)"]

排序算法平均时间复杂度最好时间复杂度最坏时间复杂度空间复杂度最坏空间复杂度稳定性
冒泡排序$O(n^2)$$O(n)$$O(n^2)$$O(1)$$O(1)$✅ 稳定
选择排序$O(n^2)$$O(n^2)$$O(n^2)$$O(1)$$O(1)$❌ 不稳定
插入排序$O(n^2)$$O(n)$$O(n^2)$$O(1)$$O(1)$✅ 稳定
希尔排序$O(n \log n) \sim O(n^2)$$O(n \log n)$$O(n^2)$$O(1)$$O(1)$❌ 不稳定
归并排序$O(n \log n)$$O(n \log n)$$O(n \log n)$$O(n)$$O(n)$✅ 稳定
快速排序$O(n \log n)$$O(n \log n)$$O(n^2)$$O(\log n)$$O(n)$❌ 不稳定
堆排序$O(n \log n)$$O(n \log n)$$O(n \log n)$$O(1)$$O(1)$❌ 不稳定
计数排序$O(n + k)$$O(n + k)$$O(n + k)$$O(k)$$O(k)$✅ 稳定
桶排序$O(n + k)$$O(n + k)$$O(n^2)$$O(n + k)$$O(n + k)$✅ 稳定
基数排序$O(d \times (n + k))$$O(d \times (n + k))$$O(d \times (n + k))$$O(n + k)$$O(n + k)$✅ 稳定

[/collapse]

C++ STL 中的排序一览表

同时,这里还有一张关于 STL 的表

函数时间复杂度空间复杂度最坏空间复杂度稳定性特点
std::sort$O(n \log n)$$O(\log n)$$O(\log n)$❌ 不稳定混合排序(快排+堆排+插入),最常用
std::stable_sort$O(n \log^2 n) \sim O(n \log n)$$O(n)$$O(n)$✅ 稳定保证相等元素顺序不变
std::partial_sort$O(n \log k)$$O(1)$$O(1)$❌ 不稳定取前 $k$ 个最小/最大
std::nth_element$O(n)$ 平均$O(1)$$O(1)$❌ 不稳定求第 $k$ 小/大,不完全排序

关于学会说话【第二弹】

题

今天我被我的父母屌了,但是原因耐人寻味:你说话太直、不会来事、别人不喜欢你,仅此而已

这应该是很普遍的现象了,在绝大部分 $80\text{后} \sim 90\text{后}$ 的思想里,会说话=让所有人舒服、让所有人满意、让所有人喜欢

但冷静下来我想说一句大实话,也是这篇第二弹的核心:这根本不是会说话,这是教人讨好、教人卑微、教人丢掉自我

你以为你好了,说难听点,即拍马屁,孟子说得好:“此之谓失其本心。”——《鱼我所欲也》【《《孟子・告子上》】

我觉得我不是很玩梗吧,我反而觉得我很喜欢讲大白话,说话的意义,不是令他人开心

会说法=高情商?

这是 $80\text{后} \sim 90\text{后}$ 的观点之一,我认为是错误的

他们认为,一个人会不会说话,标准极其简单,即有没有人不高兴、有没有人吐槽、有没有人不喜欢你,这样的判断类似于郑振铎《猫》

[collapse status="false" title="郑振铎《猫》"]
我家养了好几次猫,结局总是失踪或死亡。三妹是最喜欢猫的,她常在课后回家时,逗着猫玩。有一次,从隔壁要了一只新生的猫来。花白的毛,很活泼,如带着泥土的白雪球似的,常在廊前太阳光里滚来滚去。三妹常常取了一条红带,或一根绳子,在它面前来回地拖摇着,它便扑过来抢,又扑过去抢。我坐在藤椅上看着他们,可以微笑着消耗过一二小时的光阴,那时太阳光暖暖地照着,心上感着生命的新鲜与快乐。后来这只猫不知怎地忽然消瘦了,也不肯吃东西,光泽的毛也污涩了,终日躺在厅上的椅下,不肯出来。三妹想着种种方法逗它,它都不理会。我们都很替它忧郁。三妹特地买了一个很小很小的铜铃,用红绫带穿了,挂在它颈下,但只显得不相称,它只是毫无生意的,懒惰的,郁闷地躺着。有一天中午,我从编译所回来,三妹很难过地说道:“哥哥,小猫死了!”

我心里也感着一缕的酸辛,可怜这两月来相伴的小侣!当时只得安慰着三妹道:“不要紧,我再向别处要一只来给你。”

隔了几天,二妹从虹口舅舅家里回来。她道,舅舅那里有三四只小猫,很有趣,正要送给人家。三妹便怂恿着她去拿一只来。礼拜天,母亲回来了,却带了一只浑身黄色的小猫同来。立刻三妹一部分的注意,又被这只黄色小猫吸引去了。这只小猫较第一只更有趣,更活泼。它在园中乱跑,又会爬树,有时蝴蝶安详地飞过时,它也会扑过去捉。它似乎太活泼了,一点也不怕生人,有时由树上跃到墙上,又跑到街上,在那里晒太阳。我们都很为它提心吊胆,一天都要“小猫呢?小猫呢?”地查问好几次。每次总要寻找了一回,方才寻到。三妹常指它笑着骂道:“你这小猫呀,要被乞丐捉去后才不会乱跑呢!”我回家吃中饭,总看见它坐在铁门外边,一见我进门,便飞也似地跑进去了。饭后的娱乐,是看它在爬树。隐身在阳光隐约里的绿叶中,好像在等待着要捉捕什么似的。把它抱了下来。一放手,又极快地爬上去了。过了二三个月,它会捉鼠了。有一次,居然捉到一只很肥大的鼠,自此,夜间便不再听见讨厌的吱吱的声了。

某一日清晨,我起床来,披了衣下楼,没有看见小猫,在小园里找了一遍,也不见。心里便有些亡失的预警。

“三妹,小猫呢?”

她慌忙地跑下楼来,答道:“我刚才也寻了一遍,没有看见。”

家里的人都忙乱的在寻找,但终于不见。

李嫂道:“我一早起来开门,还见它在厅上。烧饭时,才不见了它。”

大家都不高兴,好像亡失了一个亲爱的同伴,连向来不大喜欢它的张婶也说:“可惜,可惜,这样好的一只小猫。”

我心里还有一线希望,以为它偶然跑到远处去,也许会认得归途的。

过了一二个星期,它始终没有回来。大家都以为它一定是被人家捉去,或是死了。三妹很不高兴的,咕噜着道:“他们看见了,为什么不出来阻止?他们明晓得它是我家的!”

我也怅然地,愤恨地,在诅骂着那个不知名的夺去我们所爱的东西的人。

自此,我家好久不养猫。

冬天的早晨,门口蜷伏着一只很可怜的小猫,毛色是花白,但并不好看,又很瘦。它伏着不去。我们如不取来留养,至少也要为冬寒与饥饿所杀。张婶把它拾了进来,每天给它饭吃。但大家都不大喜欢它,它不活泼,也不像别的小猫之喜欢顽游,好像是具着天生的忧郁性似的,连三妹那样爱猫的,对于它也不加注意。如此的,过了几个月,它在我家仍是一只若有若无的动物。它渐渐的肥胖了,但仍不活泼。大家在廊前晒太阳闲谈着时,它也常来蜷伏在母亲或三妹的足下。三妹有时也逗着它玩,但并没有对于前几只小猫那样感兴趣。有一天,它因夜里冷,钻到火炉底下去,毛被烧脱了好几块,更觉得难看了。

春天来了,它成了一只壮猫了,却仍不改它的忧郁性,也不去捉鼠,终日懒惰的伏着,吃得胖胖的。

这时,妻买了一对黄色的芙蓉鸟来,挂在廊前,叫得很好听。妻常常叮嘱着张婶换水,加鸟粮,洗刷笼子。那只花白猫对于这一对黄鸟,似乎也特别注意,常常跳在桌上,对鸟笼凝望着。

妻道:“张婶,留心猫,它会吃鸟呢。”

张婶便跑来把猫捉了去,隔一会,它又跳上桌子对鸟笼凝望着了。

一天,我下楼时,听见张婶在叫道:“鸟死了一只,一条腿被咬去了,笼板上都是血。是什么东西把它咬死的?”

我匆匆跑下去看,果然一只鸟是死了,羽毛松散着,好像它曾与它的敌人挣扎了许久。

我很愤怒,叫道:“一定是猫,一定是猫!”于是立刻便去找它。

妻听见了,也匆匆地跑下来,看了死鸟,很难过,便道:“不是这猫咬死的还有谁?它常常对鸟笼望着,我早就叫张婶要小心了。张婶!你为什么不小心?”

张婶默默无言,不能有什么话来辩护。

于是猫的罪状证实了。大家都去找这可厌的猫,想给它以一顿惩戒。找了半天,却没找到。我以为它真是“畏罪潜逃”了。

三妹在楼上叫道:“猫在这里了。”

它躺在露台板上晒太阳,态度很安详,嘴里好像还在吃着什么。我想,它一定是在吃着这可怜的鸟的腿了,一时怒气冲天,拿起楼门旁倚着的一根木棒,追过去打了一下。它很悲楚地叫了一声,便逃到屋瓦上了。

我心里还愤愤的,以为惩戒得还没有快意。

隔了几天,李嫂在楼下叫道:“猫,猫!又来吃鸟了!”同时我看见一只黑猫飞快地逃过露台,嘴里衔着一只黄鸟。我开始觉得我是错了!

我心里十分地难过,真的,我的良心受伤了,我没有判断明白,便妄下断语,冤枉了一只不能说话辩诉的动物。想到它的无抵抗的逃避,益使我感到我的暴怒,我的虐待,都是针,刺我良心的针!

我很想补救我的过失,但它是不能说话的,我将怎样地对它表白我的误解呢?

两个月后,我们的猫忽然死在邻家的屋脊上。我对于它的亡失,比以前的两只猫的亡失,更难过得多。

我永无改正我的过失的机会了!

自此,我家永不养猫。
[/collapse]

虽然感觉直接给读者丢下这么长的文章也挺奇怪的,想看就看,不看拉倒——卤煮刘

说白了,只要有人对我有意见、只要场面不够和谐、只要我没有顺着别人的话说,就是我不会说话、就是我情商低、就是我不懂事

但是这个逻辑本身就是感觉很荒谬,漏洞百出

下面用标签来生动的表面

[scode type="green"]A说老板漂亮,你说漂亮[/scode]

[scode type="red"]A说老板漂亮,你说不漂亮[/scode]

老板本来就不漂亮,你说漂亮,骗了他人,你骗了自己了吗,骗下去,类似于汉斯・克里斯蒂安・安徒生(丹麦)《皇帝的新装》

[collapse status="false" title="汉斯・克里斯蒂安・安徒生(丹麦)《皇帝的新装》"]
许多年前,有一个皇帝,为了穿得漂亮,不惜把所有的钱都花掉。他既不关心他的军队,也不喜欢去看戏,也不喜欢乘着马车去游公园——除非是为了去炫耀一下他的新衣服。他每一天每一点钟都要换一套衣服。人们提到他,总是说:“皇上在更衣室里。”

有一天,他的京城来了两个骗子,自称是织工,说能织出人间最美丽的布。这种布不仅色彩和图案都分外美观,而且缝出来的衣服还有一种奇怪的特性:任何不称职的或者愚蠢得不可救药的人,都看不见这衣服。

“那真是理想的衣服!”皇帝心里想,“我穿了这样的衣服,就可以看出在我的王国里哪些人不称职;我就可以辨别出哪些是聪明人,哪些是傻子。是的,我要叫他们马上为我织出这样的布来!”于是他付了许多钱给这两个骗子,好让他们马上开始工作。

他们摆出两架织布机,装作是在工作的样子,可是他们的织布机上连一点东西的影子也没有。他们急迫地请求发给他们一些最细的生丝和最好的金子。他们把这些东西都装进自己的腰包,只在那两架空织布机上忙忙碌碌,一直搞到深夜。

“我倒很想知道衣料究竟织得怎样了。”皇帝想。不过,想起凡是愚蠢或不称职的人就看不见这布,心里的确感到不大自然。他相信自己是无须害怕的,但仍然觉得先派一个人去看看工作的进展情形比较妥当。全城的人都听说这织品有一种多么神奇的力量,所以大家也都渴望借这个机会测验一下:他们的邻人究竟有多么笨,或者有多么傻。

“我要派我诚实的老大臣到织工那儿去。”皇帝想,“他最能看出这布料是什么样子,因为他很有理智,就称职这点说,谁也不及他。”

这位善良的老大臣来到那两个骗子的屋子里,看见他们正在空织布机上忙碌地工作。

“愿上帝可怜我吧!”老大臣想,他把眼睛睁得特别大,“我什么东西也没有看见!”但是他没敢把这句话说出口来。

那两个骗子请他走近一点,同时指着那两架空织布机问他花纹是不是很美丽,色彩是不是很漂亮。可怜的老大臣眼睛越睁越大,仍然看不见什么东西,因为的确没有东西。

“我的老天爷!”他想,“难道我是愚蠢的吗?我从来没有怀疑过自己。这一点决不能让任何人知道。难道我是不称职的?不成!我决不能让人知道我看不见布料。”

“哎,您一点意见也没有吗?”一个正在织布的骗子说。

“哎呀,美极了!真是美极了!”老大臣一边说,一边从他的眼镜里仔细地看,“多么美的花纹!多么美的色彩!是的,我将要呈报皇上,我对这布料非常满意。”

“嗯,我们听了非常高兴。”两个织工齐声说。于是他们就把色彩和稀有的花纹描述了一番,还加上些名词。老大臣注意地听着,以便回到皇上那儿可以照样背出来。事实上他也就这样做了。

这两个骗子又要了更多的金子,更多的生丝,说是为了织布的需要。他们把这些东西全装进了腰包。

过了不久,皇帝又派了另外一位诚实的官员去看工作进行的情况。这位官员的运气并不比头一位大臣好:他看了又看,但是那两架空织布机上什么也没有,他什么东西也看不出来。

“你看这段布美不美?”两个骗子问。他们指着,描述着一些美丽的花纹——事实上它们并不存在。

“我并不愚蠢呀!”这位官员想,“这大概是我不配有现在这样好的官职吧。这也真够滑稽,但是我决不能让人看出来。”他就把他完全没看见的布称赞了一番,同时保证说,他对这些美丽的色彩和巧妙的花纹感到很满意。“是的,那真是太美了!”他对皇帝说。

城里所有的人都在谈论着这美丽的布料。

皇帝很想亲自去看一次。他选了一群特别圈定的随员——其中包括已经去看过的那两位诚实的大臣。他就到那两个狡猾的骗子那里。这两个家伙正在以全副精神织布,但是一根丝的影子也看不见。

“您看这布华丽不华丽?”那两位诚实的官员说,“陛下请看:多么美的花纹!多么美的色彩!”他们指着那架空织布机,他们相信别人一定看得见布料。

“这是怎么一回事呢?”皇帝心里想,“我什么也没有看见!这可骇人听闻了。难道我是一个愚蠢的人吗?难道我不够资格当皇帝吗?这可是最可怕的事情。”

“哎呀,真是美极了!”皇帝说,“我十分满意!”

于是他点头表示满意。他装作很仔细地看着织机的样子,因为他不愿意说出他什么也没有看见。跟他来的全体随员也仔细地看了又看,可是他们也没有看出更多的东西。不过,他们也照着皇帝的话说:“啊,真是美极了!”他们建议皇帝用这种新奇的、美丽的布料做成衣服,穿上这衣服亲自去参加快要举行的游行大典。“真美丽!真精致!真是好极了!”每人都随声附和着。每人都有说不出的快乐。皇帝赐给骗子每人一个爵士的头衔和一枚可以挂在纽扣洞上的勋章;并且还封他们为“御聘织师”。

第二天早晨游行大典就要举行了。在头天晚上,这两个骗子整夜不睡,点起十六支蜡烛。你可以看到他们是在赶夜工,要完成皇帝的新衣。他们装作把布料从织机上取下来。他们用两把大剪刀在空中裁了一阵子,同时又用没有穿线的针缝了一通。最后,他们齐声说:“请看!新衣服缝好了!”

皇帝带着他的一群最高贵的骑士们亲自到来了。这两个骗子每人举起一只手,好像他们拿着一件什么东西似的。他们说:“请看吧,这是裤子,这是袍子!这是外衣!”“这衣服轻柔得像蜘蛛网一样,穿的人会觉得好像身上没有什么东西似的,这也正是这衣服的奇妙之处。”

“一点也不错。”所有的骑士都说。可是他们什么也看不见,因为什么东西也没有。

“现在请皇上脱下衣服,”两个骗子说,“好让我们在这个大镜子面前为您换上新衣。”

皇帝把他所有的衣服都脱下来。两个骗子装作一件一件地把他们刚才缝好的新衣服交给他。他们在他的腰周围弄了一阵子,好像是为他系上一件什么东西似的;这就是后裙。皇上在镜子面前转了转身子,扭了扭腰肢。

“上帝,这衣服多么合身啊!裁得多么好看啊!”大家都说,“多么美的花纹!多么美的色彩!这真是一套贵重的衣服!”

“对!我已经穿好了,”皇帝说,“这衣服合我的身吗?”于是他又在镜子面前把身子转动了一下,因为他要使大家觉得他在认真地观看他美丽的新装。

那些托后裙的内臣都把手在地上东摸西摸,好像他们正在拾取衣裙似的。他们开步走,手中托着空气——他们不敢让人瞧出他们实在什么东西也没有看见。

这样,皇帝就在那个富丽的华盖下游行起来了。站在街上和窗子里的人都说:“乖乖!皇上的新装真是漂亮!他上衣下面的后裙是多么美丽!这件衣服真合他的身材!”谁也不愿意让人知道自己什么也看不见,因为这样就会显出自己不称职,或是太愚蠢。皇帝所有的衣服从来没有获得过这样的称赞。

“可是他什么衣服也没有穿啊!”一个小孩子最后叫了出来。

“上帝哟,你听这个天真的声音!”爸爸说。于是大家把这孩子讲的话私下里低声地传播开来。

“他并没有穿什么衣服!有一个小孩子说他并没有穿什么衣服啊!”

“他实在没有穿什么衣服啊!”最后所有的老百姓都说。皇帝有点儿发抖,因为他似乎觉得老百姓们所讲的话是真的。不过他自己心里却这样想:“我必须把这游行大典举行完毕。”因此他摆出一副更骄傲的神气。他的内臣们跟在他后面走,手中托着一条并不存在的后裙。
[/collapse]

他们一辈子都没搞懂:让别人喜欢,是无底线迁就的结果,不是好好说话的标准

[collapse status="false" title="迁就(GenAI)"]

  1. 退让、包容,顺着别人的意愿,委屈自己满足对方(最常用)
    指明明自己不舒服、不认同,却为了和睦,放下自身想法去迎合他人。
    例句:家长总让我事事迁就别人,可一味迁就只会丢掉自己的底线。
  2. 勉强凑合、将就
    物品、条件达不到理想标准,勉强使用。
    例句:这间房子太小,只能暂时迁就着住。

近义词、反义词

  • 近义词:将就、包容、迎合、忍让
  • 反义词:抗拒、固执、较真、强硬
    [/collapse]

可笑吗?

如果你说话面面俱到、不敢反驳、不敢拒绝、永远顺着别人,确实没人会讨厌你,但你也永远活得憋屈、永远没有立场、永远被人拿捏

你为了高情商,人家说什么,你就说什么,无论对错,误入歧途

这样的会说话,本质就是牺牲自我,转为顺从,说难听点,这不是能力,而是软弱

“说话要让人喜欢” 是毒鸡汤

解释标题

[collapse status="false" title="毒鸡汤(GenAI)"]
毒鸡汤
定义
表面听起来有道理、温柔治愈,内核逻辑扭曲、误导人、损害自身立场与心态的句子、观点。看似劝人变好,实则压抑自我、一味妥协,只会带来内耗。

核心特征

  1. 片面化,只讲忍让、讨好,不提底线与自我;
  2. 颠倒因果,把委屈自己当成成熟、懂事;
  3. 弱化矛盾,要求单方面妥协,忽视互相尊重。

近义词
心灵毒汤、畸形情商论、讨好型说教
[/collapse]

你不可能被所有人接受

家长总幻想一种完美状态:说话圆滑、不得罪人、人人夸赞

你觉得,现实中,有这种状态吗?

[scode type="red"]你讲原则,想占便宜的人不喜欢你[/scode]

[scode type="red"]你讲道理,蛮不讲理的人不喜欢你[/scode]

[scode type="red"]你有主见,随波逐流的人不喜欢你[/scode]

[scode type="red"]你敢拒绝,自私自利的人更不喜欢你[/scode]

何曾不是社会现状?

社会从来不是完美的,只是你没遇到罢了

长辈总是说自己看过星星,摘过月亮,为什么会下定这种结论?

鲁迅《朝花夕拾》 给出了完美的答案

但凡你有底线、有思想、不讨好,就一定会有人不喜欢你

如果你为了别人的喜欢去改变你自己说法的方式,好似正方形变圆,你的边界呢?你的态度呢?你的独立呢?你只会因别人的改变而改变,你就是一个植物

追求被喜欢与拥有底气不可同时而立

[collapse status="false" title="《韩非子・难一》自相矛盾"]
楚人有鬻盾与矛者,誉之曰:“吾盾之坚,物莫能陷也。” 又誉其矛曰:“吾矛之利,于物无不陷也。” 或曰:“以子之矛陷子之盾,何如?” 其人弗能应也。夫不可陷之盾与无不陷之矛,不可同世而立。
[/collapse]

你说话唯唯诺诺,只为征求别人的喜欢,这年头 AI 短剧还少吗,惹不起大人物,就改变自己,你明明没错,却为了他人而习惯性的道歉,长辈一句句的丢了你的面子,你的根源一直都是从小被灌输的封建思想:别得罪人,要让别人喜欢

于是:

  • 遇到不公不敢说
  • 遇到不合理要求不敢拒绝
  • 被误解不敢辩解
  • 有不同想法不敢表达

你就是被思想禁锢了,我们生活在现代,已经不是近代中国了,何时能改变这种封建思想呢?

你以为你在学好好说话,其实你在习惯性委屈自己换取别人的善意,这不是失去自我的一种表现吗?

你从小觉得,你是一个独立的个体,可是在职场的环境下,你还是一个个体吗?小时没有零花钱,成人时你为了讨好他人而失去自我的本性

你,为了这些,值得吗?

仅仅是为了钱?

你能活出自己吗?

和朋友交往也差不多,有时确实为了对方打钱而不要脸

不对,有点扯了

但现实最讽刺的是:越想让人喜欢,越容易被人轻视你的迁就会变成理所当然,你的退让会变成软弱可欺

分清主次

让人喜欢是你的目的吗?

长辈把顺序完全搞反了:他们以为刻意讨好、刻意圆滑、刻意照顾所有人情绪,就能收获好人缘。

真实顺序是:你说话得体、有分寸、有立场、有逻辑、待人真诚,别人自然会尊重你、认可你,甚至喜欢你

我们亲爱的道法教师提到了“交友要讲原则”,你,讲原则了吗?你被剥削了!哈哈哈哈哈哈!

一旦你把“让人喜欢”当成说话的目标,你的表达就会变形、你的态度就会卑微、你的底线就会爆炸

怕你妈

人家明明是错的,你就大胆指出来,你就对着人家的鼻子说回去,怕什么

做真正的自己

首先,我不得不承认我国文不好,但是我不承认我不会说话

诚实、真实、实在

诚实

我可以礼貌待人、尊重他人、好好沟通,但我不会为了让你开心,就违背自己的想法、认可错误的观点、接受不合理的要求,尽管你是特朗普

真实

说话的第一意义,是让自己被理解、让诉求被表达、让边界被看见,不是维护表面的和谐

有些场面看似尴尬、有些人听着不舒服,不是我不会说话,是真话本来就不好听,原则本来就不讨喜

善用反问,但有时不能用反问

实在

好好说话是我的教养,不是我取悦别人的工具

我待人温和、有理有据、分寸得当,这是我做人的体面。至于你喜不喜欢、满不满意、舒不舒服,那是你的情绪,不是我的责任,谁他妈管你怎么样

因人而异

你被 PUA 了???

莫得关系

很多时候,不是你不会说话,是你不肯讨好、不肯妥协、不肯虚伪,所以显得“不合群”

以前的年代,有点战争,他们为了和平,这样做

然而,现在是什么年代了

玫瑰不用长高,恋者自会弯腰

你可以好,但是你不能讨好;你可以说话,但是你不能说好话

NOIP 信息学:搜索及其优化 完整知识点文档

https://www.tropical-fish.cn/usr/uploads/2026/07/3625185257.pptx

一、前言:搜索算法定位

  1. 算法解题优先级:优先数学推导、递推、贪心、DP;无高效数学解法时,使用搜索。
  2. 搜索特点:通用性强、适用范围广;纯暴力搜索效率极低,必须配合各类优化才能通过大数据。
  3. 基础搜索分类:穷举、深度优先搜索(DFS)、广度优先搜索(BFS)。

二、核心基础概念

2.1 状态

  • 定义:搜索的最小单元,决定搜索效率、思维难度、代码复杂度。
  • 设计要点:靠刷题积累经验,状态定义优劣直接决定代码能否过时限。

2.2 搜索树

所有搜索过程均可抽象为一棵搜索树:

  • DFS:纵向优先遍历整棵树,适合求全部可行解、构造方案;缺点是深度过大易栈溢出。
  • BFS:逐层横向遍历,天然适合求最短/最小代价最优解;自带分层,第一次到达目标即为最优。

三、基础搜索算法实现

3.1 深度优先搜索 DFS(回溯)

通用模板

void dfs(int dep, [其他状态参数])
{
    // 边界:到达目标状态
    if(当前为目标状态){
        记录答案/输出/计数;
        return;
    }
    // 枚举所有可拓展状态
    for(枚举所有下一步可能){
        保存现场(修改全局/局部状态);
        if(该拓展合法) dfs(dep + 1, 新参数);
        恢复现场(回溯,还原状态);
    }
}

特点

  1. 使用系统栈/手动栈,递归实现;
  2. 不自动保证最优,需手动记录最优值;
  3. 深度过大时栈溢出(如POJ3278裸DFS会爆栈)。

3.2 广度优先搜索 BFS(队列)

通用模板

初始状态入队;
while(队列不为空){
    取出队首元素,队首出队;
    if(当前是目标状态){输出答案; return;}
    // 拓展所有子状态
    for(所有拓展方向){
        if(状态未访问 && 拓展合法){
            标记已访问(判重);
            新状态入队;
        }
    }
}

特点

  1. 借助队列实现分层遍历;
  2. 第一次抵达终点一定是最小步数/最小代价,无需额外比较最优;
  3. 必须判重,否则重复入队无限循环。

四、经典入门例题:POJ3278 抓住那头牛

题目简述

农夫在数轴N,牛在K,三种移动方式,每次耗时1分钟:

  1. $X \to X-1$
  2. $X \to X+1$
  3. $X \to 2X$
    求到达牛位置的最少时间。样例输入5 17,输出4。

算法选择分析

  1. 裸DFS:搜索树深度极大,系统栈溢出,且无法保证最优;
  2. BFS最优:分层遍历,首次走到K即为最小步数,是本题标准解法。

五、NOIP真题案例详解

5.1 NOIP2017提高组Day2 T1 奶酪(连通性DFS)

题意

无限大奶酪,高度$h$,内部有等半径球形空洞;空洞相交/相切可互通;空洞接触下表面$z=0$可进入,接触上表面$z=h$可逃出。判断老鼠能否从底部走到顶部。

核心思路

  1. 模型转化:无向图连通性,每个空洞是节点;
  2. 连通条件:两球心距离 $\le 2r$;

    • 距离公式避免sqrt防止精度丢失:平方比较
      $$(x_1-x_2)^2+(y_1-y_2)^2+(z_1-z_2)^2 \le (2r)^2$$
    • 全部变量使用long long,防止大数溢出;
  3. 起点集合:$z \le r$ 的空洞(接触下表面);
  4. 终止条件:搜到任意满足 $z \ge h-r$ 的空洞(接触上表面);

核心代码框架

void dfs(int id){
    vis[id] = true;
    // 判断是否连通上表面
    if(hole[id].z + r >= h) flag = true;
    for(int i = 1; i <= n; i++){
        if(!vis[i] && 两球连通(id,i)) dfs(i);
    }
}

5.2 NOIP2014提高组Day2 T2 寻找道路(反向DFS+BFS最短路)

题意

有向图,边权均为1,求起点s到终点t的最短路径,约束:路径上每个点的所有出边指向的点,都能间接到达终点t。

分步解法

  1. 建两张图:正向图(原图)、反向图(所有边反转);
  2. 反向图DFS:从终点t出发,标记所有能到达t的点vis1[];
  3. 筛选合法点:一个点u合法当且仅当u所有出边指向的v都满足vis1[v]=true;
  4. 正向图BFS:仅走合法点,求s到t最短路;

数据注意

$n\le10^4,m\le2\times10^5$,稀疏图用vector邻接表;输入量大,禁用cin,改用scanf防超时。

5.3 NOIP2017普及组T3 棋盘(带状态记忆化DFS)

题意

$m\times m$棋盘,格子红/黄/无色;四向行走,异色花费1金币;可花2金币临时将无色格子染色,魔法不可连续释放。求左上角到右下角最小花费。

状态设计(三维记忆化)

f[x][y][can]:坐标$(x,y)$,can=0/1代表当前能否使用魔法,存储到达该状态最小金币;

状态转移

  1. 下一格有颜色:根据颜色是否相同累加金币,魔法重置为可用;
  2. 下一格无色:仅当魔法可用时,花费2金币,魔法置为不可用;

优化梯度

  • 裸DFS:40分;
  • 最优性剪枝:60分;
  • 记忆化剪枝(记录每个状态最小代价):100分。

5.4 NOIP2009提高组T4 靶形数独(DFS+搜索顺序优化)

题意

9×9数独,每个格子有分值,数字×格子分值总和最大化;无解输出-1。

优化梯度

  1. 暴力DFS顺序填格:40分;
  2. 预处理空白格、行/列/九宫格数字判重:75分;
  3. 最优优化(100分):搜索顺序优化
    每次优先选择可填数字最少的空白格填充,大幅剪枝无效分支;

    • 若某格无合法数字:可行性剪枝,直接回溯;
    • 若只剩1种数字:直接填充,减少循环。

六、DFS核心优化:剪枝

6.1 可行性剪枝

当前状态已经不满足题目约束,后续无论怎么走都无解,直接回溯。
例:数独格子无可用数字、数轴坐标超出0~100000范围。

6.2 最优性剪枝

全局记录当前已知最优解;若当前已消耗代价 ≥ 最优解,后续路径只会更差,直接返回。
例:抓牛DFS记录当前最小步数,当前步数超过最优则剪枝。

6.3 记忆化剪枝(全局最优剪枝)

记录每个状态到达时的最小代价;再次走到同一状态时,若当前花费 ≥ 记录值,直接剪枝;否则更新记录。
适用:棋盘、最短代价类搜索问题。

6.4 搜索顺序优化

人为调整拓展节点的先后顺序,优先搜索分支少、更容易出解的路径,提前触发剪枝。
代表例题:靶形数独(优先填可选数字最少的格子)。

七、高阶搜索优化算法

7.1 迭代加深搜索 IDDFS

  1. 思路:限制DFS最大深度,从小到大逐层放宽深度限制;
  2. 优势:兼具DFS空间小、BFS保证最优的特点;解决深搜栈溢出问题;
  3. 适用:求最小步数、深度不确定的最优解问题。

7.2 双向BFS

  1. 思路:起点BFS、终点BFS同时向外拓展,两边相遇即得到最优解;
  2. 优势:搜索范围指数级缩小,大幅降低时间复杂度;
  3. 适用:起点终点明确、状态空间巨大的最短路问题(抓牛、迷宫最短路径)。

7.3 BFS配套优化:哈希判重

状态复杂(字符串、多维数组)无法开数组标记访问时,用哈希表存储已走过状态,避免重复入队。

八、搜索算法选择总结

算法适用场景核心优势短板
DFS求全部方案、连通性、构造解代码简单、空间小无法保证最优、深度大易栈溢出
BFS最短步数、最小代价最优解天然最优、分层遍历队列占用空间大
DFS+剪枝最优值、代价最小类搜索灵活,多重剪枝提速需要手动维护最优答案
IDDFS深度未知、需要最优、怕栈溢出兼顾DFS与BFS优点重复浅层搜索,轻微冗余
双向BFS超大状态空间最短路搜索量指数级减少代码逻辑更复杂

九、易错点汇总

  1. 距离比较时禁用sqrt,平方后用long long避免精度、溢出问题;
  2. 大数据输入使用scanf/printf,cin关闭同步或直接淘汰;
  3. DFS回溯必须恢复现场,否则状态污染;
  4. BFS必须判重,否则死循环;
  5. 连通性问题分清有向图/无向图,按需建反向图;
  6. 记忆化数组初始化无穷大,到达同一状态代价更小时才更新;
  7. 递归DFS注意递归深度上限,深度极大改用迭代加深或BFS。

十、配套上机习题

  1. 奶酪:基础连通DFS
  2. 飞越原野:BFS+记忆判重
  3. 寻找道路:反向DFS+正向BFS综合
  4. 棋盘:DFS+记忆化最优剪枝
  5. 靶形数独:DFS+搜索顺序优化
  6. 小木棍:DFS多重可行性/最优性剪枝

玫瑰不必长高

    小区花园一角有几丛玫瑰静静盛放,与附近奋力向天生长、争高比大的杨树不同,也不同于墙边需要攀附他物方能向上的藤蔓,故而它自自然然地立于泥土之中,在晨光初照、清风徐来时,很自然、很从容地舒展花瓣。因此其姿态简单天真,所作之事亦最本分、最快乐。

    从近处细看,可知其枝干细润而有分明、挺利的尖刺,更妙的是尖刺越分明硬朗,枝头所托之花便红得越鲜亮,清甜的香味也因此自然流露、扩散得更远。阳光穿过层层叠叠的绿叶,碎金似的光斑直接洒在花瓣上,故而那红色温润莹润,简直似被阳光轻轻吻过,遂有一层柔和圆润的光晕。因此,这小小的带刺之物,实为角落中不动声色的绝好风姿。

    路过的人对尖刺的反应有十分清楚、自然的差别:有人见了尖刺便绕道而行,有人停步弯腰,避开刺尖,轻托花枝,欲仔细嗅一嗅其芬芳。而玫瑰本身对此全无反应:它从没有因为自己不如杨树高大而低头丧气,也从来没有因为有人惧怕尖刺而收起锋芒,只是坦然开放,神情从容,仿佛早已明悟:真正为它驻足的人,爱的绝不是它的高度,而是它静静绽放时所释放的香气。

    看到这株玫瑰,我不由自主地联想到一个问题:玫瑰有刺,但并没有因此失去美,同样,人有棱角、有个性,不也恰恰是珍贵、独特之处吗?而现实中人人都生活在“要更优秀”、“要更懂事”、“要更符合别人的期待”的种种声音之中,因此不少人在主动修剪自己,磨平棱角,改变模样,去换取一声喝彩、一份认可。但毋庸讳言,在连绵不断的改变、迎合之中,最本真的那个鲜活的、带着露水清气的自我已经悄然消逝。

    “自己”是否也同褪色的花瓣一样,正在逐渐黯淡、凋零,因而失去了原有的光彩和芬芳呢?

    玫瑰不必长高,爱花的人自然会为它弯腰,由此也自然地引出人生的很有意境的道理:人生最重要的不是去拼命踮脚摘取不属于自己的高度,而是守住内心那份属于自己的“香气”,即你的热爱、你的真诚、你本来的样子。因此,当你安然接纳自己,在自己的土地上踏实扎根、妥帖生长,终有某一天,有人会穿过茫茫人海,只为寻你而来,为你驻足,为你低眉,为你流连。真正的价值,从来不需要声嘶力竭去证明。你若盛开,清风与懂得,自会前来。

关于 FMCRAFT 更新 使钱换积分 制度的说明

起初

自古以来,FMCRAFT 就有 1 人民币 = 50 积分 的说法
但是我考虑到,这样贫富差距过大
所以我决定,更新制度
在此之后,所有关于【积分换钱】的制度除本制度无效

新制度

计算方式

概述:$1$ 人民币 = $50 \times (1-x)$ 积分

$x$ 为个人 skill 技能值
你可以通过 /sk 查看个人技能值,管理员也可以通过 sk profile skills 玩家名 查看玩家个人技能值
skill 分为以下几个技能:

  • Agility - 敏捷
  • Alchemy - 炼金
  • Archery - 射箭
  • Defense - 防御
  • Enchanting - 附魔
  • Excavation - 挖掘
  • Farming - 农耕
  • Fighting - 战斗
  • Fishing - 钓鱼
  • Foraging - 采集
  • Mining - 采矿

我们定义一个值 $t$,为您上述各个等级中最高的一个等级
我们列出以下表,来表示您的 $t$ 所对应的 $x$

$\text{ID}$$t$$x$
110.01
220.02
330.03
440.04
550.05
660.06
770.07
880.08
990.09
10100.10
11110.11
12120.12
13130.13
14140.14
15150.15
16160.16
17170.17
18180.18
19190.19
20200.20
21210.21
22220.22
23230.23
24240.24
25250.25
26260.26
27270.27
28280.28
29290.29
30300.30
31310.31
32320.32
33330.33
34340.34
35350.35
36360.36
37370.37
38380.38
39390.39
40400.40
41410.41
42420.42
43430.43
44440.44
45450.45
46460.46
47470.47
48480.48
49490.49
50500.50
51510.51
52520.52
53530.53
54540.54
55550.55
56560.56
57570.57
58580.58
59590.59
60600.60
61610.61
62620.62
63630.63
64640.64
65650.65
66660.66
67670.67
68680.68
69690.69
70700.70
71710.71
72720.72
73730.73
74740.74
75750.75
76760.76
77770.77
78780.78
79790.79
80800.80
81810.81
82820.82
83830.83
84840.84
85850.85
86860.86
87870.87
88880.88
89890.89
90900.90
91910.91
92920.92
93930.93
94940.94
95950.95
96960.96
97970.97
98980.98
99990.99
1001001.00

简单地说,$t$ 与 $x$ 成正比,比值为 $100$
特别的,如果计算出的积分为浮点数,需要向上取整

打一个生动的比方,您捐赠了 10 元,您的最高经验等级为 $16$,所以 $t=16$,您的 $x=0.16$,因此您可得 $420$ 积分

领取方式

您需要先前往 https://www.tropical-fish.cn/281.html 下方的 【捐赠】,用您的【微信】或者【支付宝】扫描其中的二维码,备注为您的游戏名
随后,您可前往 https://user.fmcraft.top/ 完成登录,完成邮箱绑定,发起工单,类型为【FMCRAFT-其他】,名称为 【钱换积分-{游戏名}-{捐赠日期}】,内容需要包含您支付的日期和您的游戏名,在我们处理后会发放您的奖励并标记工单为完成状态

FMCRAFT MC Server 2026 年 上半年 更新公告

各位 FMCRAFT 的帅哥美女:

为优化服务器运行环境、提升全体玩家游戏体验,保障服务器稳定、安全、高质量运行,可爱的服主已于2026年上半年完成服务器全方位升级与优化调整。本次更新包含机房迁移、网络优化、版本升级、安全防护、bug修复及账号合规调整等多项核心内容,具体更新详情公告如下:

一、服务器机房与网络架构升级

  1. 完成主服务器机房迁移工作,现已正式搬迁至安徽芜湖机房,有效优化区域网络延迟,提升服务器运行稳定性。
  2. 独立拆分登录凭据服务器,将其迁移至中国杭州优质机房,优化玩家登录响应速度,减少登录卡顿、失败等问题。
  3. 优化内网网络部署,针对服务器部分内网线路启用SakuraFRP内网穿透服务,进一步优化网络传输链路,改善联机网络环境。

二、游戏版本升级

本次已正式将服务器游戏版本升级至 Minecraft 1.21.11,同步适配新版本核心玩法、内容特性及兼容性优化,玩家可体验新版本全新游戏内容。

三、安全防护体系升级

为严厉打击外挂、违规作弊行为,维护公平公正的游戏环境,服务器正式接入Matrix反作弊系统,全方位监测游戏违规行为,强力拦截各类作弊操作,守护正常玩家游戏体验。

四、问题修复与体验优化

针对前期服务器运行过程中出现的各类已知bug、卡顿故障、功能异常等问题进行全面排查与修复,同时优化服务器运行机制、资源调度及玩家交互体验,大幅提升游戏流畅度与服务器稳定性。

五、账号合规整治调整

依据服务器用户公约及游戏违规规则,完成新一轮账号合规核查处理:对多次违规、破坏服务器游戏环境、扰乱社区秩序的违规账号进行永久/限时封禁;同时对前期误封、违规情节已肃清的合规玩家账号予以解封,恢复正常游戏权限。

本次更新旨在为各位玩家提供更稳定、安全、优质的游戏服务,后续我们将持续跟进服务器运行状态,定期优化更新、修复问题。欢迎各位玩家正常上线体验新版本内容,自觉遵守服务器规则,共同维护良好的游戏社区环境。

六、其他

预祝各位生活愉快。

该退服的赶紧退。

https://f.wps.cn/g/K0Hr27v5/【WPS表单】邀你填写「FMCRAFT 2026 年 上半年 综合调查问卷」

我 - TropicalFish

该篇文章由 AI 生成