排序概念
排序(Sorting)是计算机程序设计中的一种重要操作,其功能是对一个数据元素集合或序列重新排列成一个按数据元素某个项值有序的序列。
十种常见排序算法可以分类两大类别:比较类排序和非比较类排序。

常见的快速排序、归并排序、堆排序以及冒泡排序等都属于比较类排序算法。比较类排序是通过比较来决定元素间的相对次序,由于其时间复杂度不能突破 O(n log n) ,因此也称为非线性时间比较类排序。在冒泡排序之类的排序中,问题规模为 n ,又因为需要比较 n 次,所以平均时间复杂度为O(n²)。在归并排序快速排序之类的排序中,问题规模通过分治法消减为 log n 次,所以时间复杂度平均为 O(n log n) 。
比较类排序的优势是,适用于各种规模的数据,也不在乎数据的分布,都能进行排序。可以说,比较排序适用于一切需要排序的情况。
然而计数排序、基数排序、桶排序则属于非比较类排序算法。非比较排序不通过比较来决定元素间的相对次序,而是通过确定每个元素之前,应该有多少个元素来排序。由于它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序。非比较排序只要确定每个元素之前的已有的元素个数即可,所有一次遍历即可解决。算法时间复杂度为 O(n) 。
非比较排序时间复杂度比较低,但由于非比较排序需要占用空间来确定唯一位置。所以对数据规模和数据分布有一定的要求。
排序的稳定性
一个排序算法,若可使排序前后关键字相同的项相对顺序不变,则称该排序算法是稳定的排序算法。
在编写合理的情况下,简单排序算法是稳定的;快速排序、堆排序是不稳定的。在CSP中,往往排序是没有附带其他项目的,也就不要求排序稳定。快速排序、堆排序仍然是最佳选择。可是有没有时间复杂度为O(nlogn)的稳定的排序算法呢?有的。归并排序基于分治思想:把要排序的数组平分两半,对两部分分别排序(递归地)后再合并起来。合并时,将一个数组按顺序插入另一个数组中,需要开辟一个暂存数组。利用空间优化,可只用开辟一个与原数组等大的数组。归并排序的优缺点都很明显。无论情形如何,它的比较次数、赋值次数都稳定在n log n,没有最差情况,运行时间与快速排序、堆排序相当。而且,它是稳定的排序算法。但是,它的内存占用会达到快速排序、堆排序的两倍,所以要合理选用排序算法。
下面是一些主排序算法的优缺点比较:竞赛时使用容易造成内存超出限制。
十大排序算法介绍
①选择排序
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零。选择排序是不稳定的排序方法。
选择排序的思想就是:从当前数中选出一个最大或者最小的值排在最前面,然后从剩下的数中选出剩下数的最值排在已经排序的数的后面,算法时间复杂度O (n²) 。
代码:
//选择排序
void swap(int* a, int* b)
{
int tem = *a;
*a = *b;
*b = tem;
}
void SelectSort(int* arr, int n)
{
//保存参与单趟排序的第一个数和最后一个数的下标
int begin = 0, end = n - 1;
while (begin < end)
{
//保存最大值的下标
int maxi = begin;
//保存最小值的下标
int mini = begin;
//找出最大值和最小值的下标
for (int i = begin; i <= end; ++i)
{
if (arr[i] < arr[mini])
{
mini = i;
}
if (arr[i] > arr[maxi])
{
maxi = i;
}
}
//最小值放在序列开头
swap(&arr[mini], &arr[begin]);
//防止最大的数在begin位置被换走
if (begin == maxi)
{
maxi = mini;
}
//最大值放在序列结尾
swap(&arr[maxi], &arr[end]);
++begin;
--end;
}
}
②冒泡排序
冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。
它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中的二氧化碳气泡最终会上浮到顶端一样,故名“冒泡排序”。
冒泡排序的思想就是:比如说要升序排列,那么就依次相邻两个数比较大小,然后把大的数放在后面,依次类推。冒泡排序时间复杂度O(n²)。


代码:
//冒泡排序
void BubbleSort(int* arr, int n)
{
int end = n;
while (end)
{
int flag = 0;
for (int i = 1; i < end; ++i)
{
if (arr[i - 1] > arr[i])
{
int tem = arr[i];
arr[i] = arr[i - 1];
arr[i - 1] = tem;
flag = 1;
}
}
if (flag == 0)
{
break;
}
--end;
}
}
③插入排序
插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动
插入排序思想就是:依次遍历数组,假设前面已经有一部分排过序的,当前待排数字跟前面的数字依次比较大小,将其插在对应顺序位置,算法时间复杂度O(n²),在实际工作中这种排序算法不可取。

代码:
void InsertSort(int* arr, int n)
{
for (int i = 0; i < n - 1; ++i)
{
//记录有序序列最后一个元素的下标
int end = i;
//待插入的元素
int tem = arr[end + 1];
//单趟排
while (end >= 0)
{
//比插入的数大就向后移
if (tem < arr[end])
{
arr[end + 1] = arr[end];
end--;
}
//比插入的数小,跳出循环
else
{
break;
}
}
//tem放到比插入的数小的数的后面
arr[end + 1] = tem;
//代码执行到此位置有两种情况:
//1.待插入元素找到应插入位置(break跳出循环到此)
//2.待插入元素比当前有序序列中的所有元素都小(while循环结束后到此)
}
}
④希尔排序
希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因 D.L.Shell 于 1959 年提出而得名。
希尔排序是把记录按下表的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至 1 时,整个文件恰被分成一组,算法便终止。
希尔排序的思想就是:希尔排序是对插入排序的改进,可以把当前数据分组,每个分组都有一定间隔,比如说原来数组的小标范围是0,1,2,3,4,5,6,7,8,9.我们把它们分成两组,0-4一组,5-9 一组,这样的话,间隔就是5,我们令0,5,;1,6;2,7;3,8;4,9,它们两两比较大小。然 后再减小间隔范围,或者说增多分组个数,比如此时,间隔为2,那么比较下标为024,6,8 的大小,然后比较下标为1,3,5,7,9的大小。到最后间隔变为1,就变成了完全的插入排序了。希尔排序的算法时间复杂度O(n²)

代码:
//希尔排序
void ShellSort(int* arr, int n)
{
int gap = n;
while (gap>1)
{
//每次对gap折半操作
gap = gap / 2;
//单趟排序
for (int i = 0; i < n - gap; ++i)
{
int end = i;
int tem = arr[end + gap];
while (end >= 0)
{
if (tem < arr[end])
{
arr[end + gap] = arr[end];
end -= gap;
}
else
{
break;
}
}
arr[end + gap] = tem;
}
}
}
⑤快速排序
快速排序(Quicksort),计算机科学词汇,适用领域Pascal,C++等语言,是对冒泡排序算法的一种改进。
快速排序利用分治的思想,在数组中设置一个游标,从数组的两端来遍历数组,将元素和游标相比,意图达到两组数据一边游标小,一边比游标大。那么此时的游标就处于两组数组的中间。然后依次对前后两组数据排序,此时当然可以利用递归了。时间平均复杂度是O(n*log n),排序算法貌似是公认最实用最好的排序算法,在大多数情况下都能解决问题,提高效率,当然也有糟糕的时候,此时的算法时间复杂度达到0(n²)

代码:
//快速排序 hoare版本(左右指针法)
void QuickSort(int* arr, int begin, int end)
{
//只有一个数或区间不存在
if (begin >= end)
return;
int left = begin;
int right = end;
//选左边为key
int keyi = begin;
while (begin < end)
{
//右边选小 等号防止和key值相等 防止顺序begin和end越界
while (arr[end] >= arr[keyi] && begin < end)
{
--end;
}
//左边选大
while (arr[begin] <= arr[keyi] && begin < end)
{
++begin;
}
//小的换到右边,大的换到左边
swap(&arr[begin], &arr[end]);
}
swap(&arr[keyi], &arr[end]);
keyi = end;
//[left,keyi-1]keyi[keyi+1,right]
QuickSort(arr, left, keyi - 1);
QuickSort(arr,keyi + 1,right);
}
//快速排序法 挖坑法
void QuickSort1(int* arr, int begin, int end)
{
if (begin >= end)
return;
int left = begin,right = end;
int key = arr[begin];
while (begin < end)
{
//找小
while (arr[end] >= key && begin < end)
{
--end;
}
//小的放到左边的坑里
arr[begin] = arr[end];
//找大
while (arr[begin] <= key && begin < end)
{
++begin;
}
//大的放到右边的坑里
arr[end] = arr[begin];
}
arr[begin] = key;
int keyi = begin;
//[left,keyi-1]keyi[keyi+1,right]
QuickSort1(arr, left, keyi - 1);
QuickSort1(arr, keyi + 1, right);
}
//单趟排
int PartSort(int* arr, int begin, int end)
{
int key = arr[begin];
while (begin < end)
{
while (key <= arr[end] && begin < end)
{
--end;
}
arr[begin] = arr[end];
while (key >= arr[begin] && begin < end)
{
++begin;
}
arr[end] = arr[begin];
}
arr[begin] = key;
int meeti = begin;
return meeti;
}
void QuickSortNoR(int* arr, int begin, int end)
{
stack<int> st;
//先入右边
st.push(end);
//再入左边
st.push(begin);
while (!st.empty())
{
//左区间
int left = st.top();
st.pop();
//右区间
int right = st.top();
st.pop();
//中间数
int mid = PartSort(arr, left, right);
//当左区间>=mid-1则证明左区间已经排好序了
if (left < mid - 1)
{
st.push(mid - 1);
st.push(left);
}
//当mid+1>=右区间则证明右区间已经排好序
if (right > mid + 1)
{
st.push(right);
st.push(mid + 1);
}
}
}
//快速排序法 前后指针版本
void QuickSort2(int* arr, int begin, int end)
{
if (begin >= end)
return;
int cur = begin, prev = begin - 1;
int keyi = end;
while (cur != keyi)
{
if (arr[cur] < arr[keyi] && ++prev != cur)
{
swap(&arr[cur], &arr[prev]);
}
++cur;
}
swap(&arr[++prev],&arr[keyi]);
keyi = prev;
//[begin,keyi -1]keyi[keyi+1,end]
QuickSort2(arr, begin, keyi - 1);
QuickSort2(arr, keyi + 1, end);
}
⑥归并排序
归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
归并排序的思想就是把数组分成两组,前后两组分别排序,两组排完序后再把两组合并起来,当然可以利用递归的思想来实现归并排序,算法时间复杂度是O(n*log n)

代码:
/*
题目:归并排序
划分成很小的组,然后两两归并
*/
#include<iostream>
using namespace std;
void Merge(int[], int, int[], int, int, int) //归并函数的声明【把归并函数提到该函数前面,则不用声明】
//归并排序
//参数:
// numbers[]:原数组
// length:数组元素的个数(数组长度)
// temp[]:辅助数组
// begin:数组开头的下标
// end:数组结尾的下标
void MergeSort(int numbers[], int length, int temp[], int begin, int end)
{
//1. 同样判断传入的参数是否有效
if (numbers == nullptr || length <= 0 || begin < 0 || end >= length)
throw new exception("Invalid input.");
//2. 作为递归的结束条件,开始下标和结束下标相等时,说明子序列中只有一个元素,看作有序的
if (end - begin == 0)
return;
//3. 定义中间变量,将数组分半【如果有7个元素,下标0-6,则middle=3,数组分为长度为4和3的两段】
int middle = ((end - begin) / 2 ) + begin;
//4. 递归,先递归左半边,再递归右半边,将左右子序列不断分为长度为1的子序列才停止递归
MergeSort(numbers, length, temp, begin, middle);
MergeSort(numbers, length, temp, middle + 1, end);
//5. 再慢慢归并
Merge(numbers, length, temp, begin, end, middle);
}
//归并函数
//参数:
// numbers[]:原数组
// length:数组元素的个数(数组长度)
// temp[]:辅助数组
// begin:数组开头的下标
// end:数组结尾的下标
// middle:数组中间的下标
void Merge(int numbers[], int length, int temp[], int begin, int end, int middle)
{
//1. 判断是否有不符合要求的参数传入,有则抛出错误
if (numbers == nullptr || length <= 0 || begin < 0 || end >= length)
throw new exception("Invalid input.");
//2. 将原序列从中分开
int leftIndex = begin; //左边序列的开始(左边序列的结尾是middle)
int rightIndex = middle + 1;//右边序列的开始(右边序列的结尾是end)
int tempIndex = begin; //辅助数组的下标
//3. 当左右子序列尚未到头时,循环
while (leftIndex <= middle && rightIndex <= end)
{
//4. 两两对比判断,谁大谁就放入辅助数组,同时指针后移
if (numbers[leftIndex] < numbers[rightIndex])
temp[tempIndex] = numbers[leftIndex++];
else
temp[tempIndex] = numbers[rightIndex++];
//5. 辅助数组下标++
++tempIndex;
}
//6. 当左边或右边子序列尚未到头时,直接放入辅助数组
while (leftIndex <= middle)
temp[tempIndex++] = numbers[leftIndex++];
while (rightIndex <= end)
temp[tempIndex++] = numbers[rightIndex++];
//7. 再将辅助数组中已经有序的元素覆盖掉原数组中无序的元素,使原数组变成部分有序
for (int i = begin; i <= end; ++i)
numbers[i] = temp[i];
}
//简单测试
int main(int arvc, char* argv[])
{
const int length = 9;
int nums[length] = { 18, 7, 23, 3, 9, 32, 10 , 99, 0};
int *temp = new int[length];
MergeSort(nums, length, temp, 0, 8);
for (int i = 0; i < length; i++)
cout << nums[i] << " ";
delete[] temp;
temp = nullptr;
cout << endl;
return 0;
}
⑦堆排序
堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
首先说明什么是堆,堆其实就一个二叉树,其中要满足父节点大于或者小于子节点。把数组中元素按照堆来遍历,修正堆后,那么最大或者最小的元素就在根节点上了。我们把根节点移除,按照相同的方法再次得到最值,依次类推,直到剩下一个元素位置。算法时间复杂度是O(n*log n)

代码:
#include<iostream>
using namespace std;
void sw(int &a,int &b) {
int temp = a;
a = b;
b = temp;
}
// arr[]为完全二叉树层序遍历得到的数组
// n为完全二叉树的节点,即数组长度
// i为待维护的节点
void heapify(int arr[], int n, int i) { //把这个二叉树先堆化
//递归出口
if (i >= n) return;
int largest = i;
int lson = i * 2 + 1;
int rson = i * 2 + 2;
if (lson < n && arr[largest] < arr[lson]) { //和左孩子数值比较,找到最大节点,赋值下标
largest = lson;
}
if (rson < n && arr[largest] < arr[rson]) { //和右孩子数值比较,找到最大节点,赋值下标
largest = rson;
}
if (largest != i) { //如果现在的最大值下标和之前的不一样,那么交换二者的数值
//sw(arr[largest], arr[i]);
swap(arr[largest], arr[i]);
heapify(arr, n, largest); //进行一个递归,因为在上一层的节点交换完之后,无法保证下边父节点大于孩子节点
}
}
void heap_sort(int arr[], int n) {
//建堆
int lastNode = n - 1; //从后往前建堆
int parent = (lastNode - 1) / 2;
for (int i = parent; i >= 0; i--) {
heapify(arr, n, i);
}
//堆排序
for (int i = n - 1; i >= 0; i--) {
sw(arr[i], arr[0]);
heapify(arr, i, 0);
}
}
int main() {
int arr[5] = { 5,4,3,2,1 };
heap_sort(arr, sizeof(arr) / sizeof(arr[0]));
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
cout << arr[i] << endl;
}
return 0;
}
⑧基数排序
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O(n log (r) m),其中r为所采取的基数,而m为堆数,在某些时候,基数排序法的效率高于其它的稳定性排序法。
基数排序和后面要说的计数排序,桶排序都是非比较排序,因此他们能够突破比较排序的时 间上限O(n*log n),达到时间复杂度为O(n)的程度,但是这几种排序都有限制性条件,不能 看到他们的时间复杂付貌似比别的低一个等级就觉得他们是最好的排序算法了。三者的思想 类似,都是用到了桶的概念。基数排序针对的是非负整数,将所有的数字依次比较个位,十 位,百位,直到最高位,每一次比较都能得到一个排序,由于越往高位,这个位上数字影响 权重越大,所以能够起到对前面顺序的修正。

代码:
#include <iostream>
#include <vector>
using namespace std;
int MaxBit(vector<int> input) //求出数组中最大数的位数
{
int max_num = input[0]; //默认最大数为第一个数字
for (int i = 0; i < input.size(); i++) //找出数组中的最大数
{
if (input[i] > max_num)
{
max_num = input[i];
}
}
int p = 0;
while (max_num > 0)
{
p++;
max_num /= 10; //每次除以10取整,即可去掉最低位
}
return p;
}
int GetNum(int num, int d) //取出所给数字的第d位数字
{
int p = 1;
while (d - 1 > 0)
{
p *= 10;
d--;
}
return num / p % 10;
}
vector<int> RadixSort(vector<int> input, int length) //基数排序
{
vector<int> bucket(length); //创建临时存放排序过程中的数据
vector<int> count(10); //创建按位计数的技术容器,即记录排序中按个位、十位...各个数的位置的个数
for (int d = 1; d <= MaxBit(input); d++) {
// 计数器清0
for (int i = 0; i < 10; i++) {
count[i] = 0;
}
// 统计各个桶中的个数
for (int i = 0; i < length; i++) {
count[GetNum(input[i], d)]++;
}
for (int i = 1; i < 10; i++) { //得到每个数应该放入bucket中的位置
count[i] += count[i - 1];
}
for (int i = length - 1; i >= 0; i--) { //采用倒序进行排序是为了不打乱已经排好的顺序
int k = GetNum(input[i], d);
bucket[count[k] - 1] = input[i];
count[k]--;
}
for (int j = 0; j < length; j++) // 临时数组复制到 input 中
{
input[j] = bucket[j];
}
}
return input;
}
int main()
{
int arr[] = { 50, 123, 543, 187, 49, 30, 0, 2, 11, 100 };
vector<int> test(arr, arr + sizeof(arr) / sizeof(arr[0]));
cout << "排序前:";
for (int i = 0; i < test.size(); i++) {
cout << test[i] << " ";
}
cout << endl;
vector<int> result = test;
result = RadixSort(result, result.size());
cout << "排序后:";
for (int i = 0; i < result.size(); i++) {
cout << result[i] << " ";
}
cout << endl;
system("pause");
}
⑨计数排序
计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的时间复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(n*log(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(n*log(n)), 如归并排序,堆排序)
计数排序的思想也很简单,就是针对所有的整数。取一个从最小值到最大值的间隔,然后遍 历数组,把当前元素放在下标为(当前元素值-最小值)的位置。

代码:
#include <stdlib.h>
void countingSort(int *ini_arr, int *sorted_arr, int n, int maxValue)
{
int *count_arr = (int *)malloc(sizeof(int) * maxValue);
int i, j, k;
for (k = 0; k <= maxValue; k++)
count_arr[k] = 0;
for (i = 0; i < n; i++)
count_arr[ini_arr[i]]++;
for (k = 1; k <= maxValue; k++)
count_arr[k] += count_arr[k - 1];
for (j = n; j > 0; j--)
sorted_arr[--count_arr[ini_arr[j - 1]]] = ini_arr[j - 1];
free(count_arr);
}
⑩桶排序
桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的数组内的数值是均匀分配的时候,桶排序使用线性时间(O(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。
桶排序的思想就是先把数据分成一个个分组,这一个个分 组就是一个个桶,当然这些桶是有顺序的,要不然桶排序作为非比较排序算法,连唯一的顺序都没有并且也不比较还排什么序啊。对于首次分桶后的数据可以采用递归或者其他的排序 算法实现对每个桶内数据排序。最后按照桶号将所有数据依次连接就是排完顺序的数据了。
排序的稳定性:
举例:有一张成绩表,记录着许多学生的成绩,要将他们按成绩排序,但成绩相同者的相对顺序不能改变。换句话说,ABCDE 五人,A、C、D 成绩相同,显然排序完之后会排在一起,现在的要求是:他们排在一起的顺序也必须是ACD,不能是 ADC、CAD...。这样的实际问题涉及到排序的稳定性。

代码:
#include<iostream>
#include<vector>
using namespace std;
void bucketSort(vector<int>&vec, int n)
{
int max = vec[0];
for (int i = 0; i < n; i++)
{
if (vec[i] > max)
{
max = vec[i];
}
}
//申请max+1个桶
//int *bucket = new int[max + 1];
//给每个桶赋初值为0;
//memset(bucket, 0, (max + 1) * sizeof(int));
vector<int>bucket(max + 1, 0);
//遍历原数组,把相应的数放到相应的桶里
for (int i = 0; i < n; i++)
{
bucket[vec[i]]++;
}
int index = 0;
//从桶里把数取出来, i代表的数值对应桶下标, bucket[i]代表的是个数
for (int i = 0; i < bucket.size(); i++)
{
while (bucket[i] > 0)
{
vec[index++] = i;
bucket[i]--;
}
}
}
int main()
{
vector<int>vec = { 2,3,5,8,9,7,4,6,1 };
bucketSort(vec, vec.size());
for (auto it : vec)
{
cout << it << " ";
}
return 0;
}
最后(求了看看吧)
首先,我的这个十大经典排序算法是 “ 加班加点 ” 赶出来的(这可是有整整12291字呀!!!),有可能会有些许差错,若有错误请私信我并指出,谢谢!!!
其次,我希望大家能去温习一下子这个十大经典排序算法,因为在考试中,不管是编程(做题排序时用到),还是笔试(如:2023年的大湾区笔试中就用到了,当时可是有一小题与整整一大题都考了呢,因为本作者是亲身经历者!想要了解的可以看我的csp备考------2023 大湾区信息学),都是会有这一项知识点的!!!
最后的最后:
制作不易,点个赞吧!!!
制作不易,点个赞吧!!!
制作不易,点个赞吧!!!

5万+

被折叠的 条评论
为什么被折叠?



