题解:洛谷 P1177 排序
【题目来源】
洛谷:P1177 【模板】排序 - 洛谷(luogu.com.cn)
【题目描述】
将读入的 \(N\) 个数从小到大排序后输出。
【输入】
第一行为一个正整数 \(N\)。
第二行包含 \(N\) 个空格隔开的正整数 \(a_i\),为你需要进行排序的数。
【输出】
将给定的 \(N\) 个数从小到大输出,数之间空格隔开,行末换行且无空格。
【输入样例】
5
4 2 4 5 1
【输出样例】
1 2 4 4 5
【核心思想】
-
问题分析:给定 \(N\) 个正整数,要求将其从小到大排序并输出。这是一个最基础的排序问题,适合作为快速排序、堆排序或归并排序等经典算法的模板题。由于数据规模通常较大(\(N\) 可能达到 \(10^5\)),需要使用 \(O(N \log N)\) 的排序算法。
-
算法选择:
- 快速排序:采用分治策略,选取基准值,将小于基准的放左边,大于基准的放右边,递归排序两边。代码中选取中间或随机元素作为基准,避免最坏情况。
- 堆排序(备选):使用小根堆,将所有元素插入堆,然后依次弹出堆顶即可得到升序序列。
- 其他排序:归并排序、
std::sort等也可通过,但作为模板题,手写快排或堆排是学习重点。
-
关键步骤(以快速排序为例):
- 读入:读取 \(N\) 和数组 \(a[0..N-1]\)。
- 分区函数:选择基准
x(如中间位置),双指针i和j从两端向中间扫描,交换逆序对,直到i >= j。 - 递归排序:对左半部分
[l, j]和右半部分[j+1, r]分别递归排序。 - 输出:按顺序输出数组。
-
时间/空间复杂度:
- 时间复杂度:平均 \(O(N \log N)\),最坏 \(O(N^2)\)(但通过随机基准可避免)。
- 空间复杂度:\(O(\log N)\)(递归栈空间)。
-
排序算法的选择:
- 快速排序:在平均情况下效率最高,是常用的排序算法。
- 堆排序:适合需要稳定输出最小值的场景,但本题只需最终有序,快排更简洁。
- 适用场景:排序是算法竞赛中最基础的工具,掌握快速排序和堆排序是必备技能。
【算法标签】
普及- #快速排序
【代码详解】
#include <bits/stdc++.h> // 包含标准库中的所有头文件
using namespace std; // 使用标准命名空间
const int N = 100020; // 定义常量N,表示数组的最大长度
int a[N]; // 定义一个全局数组a,用于存储待排序的数据
// 快速排序函数
void quick_sort(int a[], int l, int r) {
if (l >= r) return; // 如果左边界大于等于右边界,说明区间已经有序,直接返回
int i = l - 1, j = r + 1; // 初始化两个指针i和j,分别指向区间外的位置
int x = a[l + (r - l) / 2]; // 选取区间中间位置的元素作为基准值(pivot)
// 分区操作
while (i < j) {
do i++; while (a[i] < x); // 从左向右找到第一个大于等于基准值的元素
do j--; while (a[j] > x); // 从右向左找到第一个小于等于基准值的元素
if (i < j) swap(a[i], a[j]); // 如果i<j,交换这两个元素
}
// 递归排序左半部分和右半部分
quick_sort(a, l, j); // 排序左半部分 [l, j]
quick_sort(a, j + 1, r); // 排序右半部分 [j+1, r]
}
int main() {
int n;
cin >> n; // 输入数组的长度n
for (int i = 0; i < n; i++) cin >> a[i]; // 输入数组的n个元素
quick_sort(a, 0, n - 1); // 调用快速排序函数对数组进行排序
for (int i = 0; i < n; i++) cout << a[i] << " "; // 输出排序后的数组
return 0; // 程序正常结束
}
#include <iostream>
using namespace std;
int n, a[100005];
void quicksort(int l, int r){
if(l == r) return; // 只有一个元素,直接返回
int i = l - 1, j = r + 1; // 初始化左右指针
int x = a[(l + r) / 2]; // 选择中间元素作为基准
while(i < j){
do i++; while(a[i] < x); // 向右找第一个>=x的元素
do j--; while(a[j] > x); // 向左找第一个<=x的元素
if(i < j) swap(a[i], a[j]); // 交换两个元素
}
quicksort(l, j); // 递归排序左半部分
quicksort(j + 1, r); // 递归排序右半部分
}
int main(){
cin >> n;
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
quicksort(0, n - 1);
for(int i = 0; i < n; i++) printf("%d ", a[i]);
return 0;
}
// 使用acwing的模板二刷
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n;
int q[N];
void quick_sort(int q[], int l, int r)
{
if (l >= r) return; // 区间只有一个元素或为空,直接返回
// 随机化选择基准值
int rnd_idx = rand() % (r - l + 1) + l;
swap(q[l], q[rnd_idx]);
int x = q[l]; // 基准值
int i = l - 1, j = r + 1; // 初始化左右指针
while (i < j)
{
do i++; while (q[i] < x); // 从左向右找第一个≥x的元素
do j--; while (q[j] > x); // 从右向左找第一个≤x的元素
if (i < j) swap(q[i], q[j]); // 交换两个元素
}
// 递归排序左右两部分
quick_sort(q, l, j);
quick_sort(q, j + 1, r);
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> q[i];
}
quick_sort(q, 1, n);
for (int i = 1; i <= n; i++)
{
cout << q[i] << " ";
}
cout << endl;
return 0;
}
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int a[100010], cnt; // 堆数组, 当前堆大小
// 上浮操作:从节点u开始向上调整
void up(int u)
{
if (u / 2 && a[u / 2] > a[u]) // 如果有父节点且父节点更大
{
swap(a[u], a[u / 2]); // 交换
up(u / 2); // 继续向上调整
}
}
// 下沉操作:从节点u开始向下调整
void down(int u)
{
int v = u; // 记录最小值的索引
if (u * 2 <= cnt && a[u * 2] < a[v]) // 左子节点存在且更小
{
v = u * 2;
}
if (u * 2 + 1 <= cnt && a[u * 2 + 1] < a[v]) // 右子节点存在且更小
{
v = u * 2 + 1;
}
if (u != v) // 如果u不是最小值
{
swap(a[u], a[v]); // 交换
down(v); // 继续向下调整
}
}
// 压入元素:在堆尾插入元素
void push(int x)
{
a[++cnt] = x; // 放到堆尾
up(cnt); // 上浮调整
}
// 弹出堆顶:删除堆顶元素
void pop()
{
a[1] = a[cnt--]; // 用堆尾元素覆盖堆顶
down(1); // 下沉调整
}
int main()
{
int n;
scanf("%d", &n); // 输入元素个数
for (int i = 1, x; i <= n; i++) // 输入每个元素
{
scanf("%d", &x);
push(x); // 建堆:从堆尾插入每个数,上浮调整
}
for (int i = 1; i <= n; i++) // 堆排序:每次取出堆顶
{
printf("%d ", a[1]); // 输出当前最小值
pop(); // 删除堆顶
}
return 0;
}
【运行结果】
5
4 2 4 5 1
1 2 4 4 5
浙公网安备 33010602011771号