概念
快速排序和归并排序是两种常用的高效排序算法,它们都采用分治策略,但在细节上有所不同:
快速排序:
原理:通过一个称为“基准”的元素将数组分为两个子数组,使得左边的元素都不大于基准,右边的元素都不小于基准,然后递归地在两个子数组上应用相同的过程。
优点:平均情况下非常快,时间复杂度为O(n log n)。
注意:最坏情况下(已排序或反向排序的数据)时间复杂度退化为O(n^2)。
归并排序:
原理:将数组分为两半,对每一半递归地应用归并排序,然后将排序好的两半合并成一个有序数组。
优点:性能稳定,时间复杂度始终为O(n log n),适合大数据量的排序。
注意:需要额外的内存空间来存储临时数组。
通过这节课,学生应能够理解这两种排序算法的工作原理,并在实际编程中正确选择并实现它们。
1、排身高-3107
快速排序是一种高效的排序算法,它采用分治法的思想,通过一个轴点(pivot)将数组分为两部分,然后独立对这两部分进行排序。具体过程包括选择一个轴点,通常是数组的第一个或最后一个元素,然后重新排列数组,使得轴点左边的所有元素都不大于轴点,轴点右边的所有元素都不小于轴点,最后递归地在轴点两边的子数组上重复这个过程。
题目分析:
我们需要使用快速排序算法来对一组小动物的身高进行排序。
输入描述解析:
- 第一行是一个整数
n
,代表有n
个身高需要排序。 - 第二行包含
n
个整数,每个整数代表一个小动物的身高。
输出描述解析:
- 输出是一行,包含升序排列的身高,数字之间用一个空格隔开。
代码解析:
void qsort(int left,int right){
int x=a[left],l=left,r=right;
if(l>=r) return;
while(l<r){
while(l<r && a[r]>=x) r--;
a[l]=a[r];
while(l<r && a[l]<=x) l++;
a[r]=a[l];
}
a[r]=x;
qsort(left,r-1);
qsort(r+1,right);
}
qsort
函数是快速排序的核心。函数接收两个参数left
和right
,表示要排序的数组段的起始和终止位置。x
是基准值,这里取的是每个数组段的第一个元素a[left]
。l
和r
是两个指针,分别从数组的两端开始向中间移动,交换元素以确保基准值左边的元素都不大于它,右边的元素都不小于它。- 当
l >= r
时,递归结束。 - 在每次循环中,右指针
r
向左移动,直到找到一个小于基准值的元素。然后将该元素复制到左指针l
的位置。 - 左指针
l
向右移动,直到找到一个大于基准值的元素。然后将该元素复制到右指针r
的位置。 - 当左右指针相遇时,将基准值复制到该位置,此时基准值的位置固定。
- 递归调用
qsort
函数,对基准值左边和右边的子数组进行排序。
在 main
函数中,我们首先读取 n
的值,然后读取 n
个身高值存入数组 a
。接着,我们调用 qsort
函数对整个数组进行排序,并最终输出排序后的数组。
性能考量:
快速排序的平均时间复杂度为 (O(n \log n)),在大多数情况下非常高效。对于小规模的数据(如本题中的 n<1000),快速排序通常表现良好。
示例输出解释:
对于样例输入:
6
5 6 2 9 8 1
排序后的输出为:
1 2 5 6 8 9
每个身高值都按照从低到高的顺序排列,且每个数值之间有一个空格。
#include<iostream>
using namespace std;
int a[10001];//存储序列
void qsort(int left,int right){
//自定义快速排序函数
int x=a[left],l=left,r=right;//x:取第一个数字为基准值 l:最左边数字下标 r:最右边数字下标
if(l>=r) return ;//递归结束条件
while(l<r){
//移动元素
while(l<r && a[r]>=x) r--;
a[l]=a[r];
while(l<r && a[l]<=x) l++;
a[r]=a[l];
}
a[r]=x;//固定基准值
qsort(left,r-1);//基准值左边排序
qsort(r+1,right);//基准值右边排序
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
qsort(1,n);//调用快排函数
for(int i=1;i<=n;i++) cout<<a[i]<<" ";
return 0;
}
2、兔子排行榜-2938
题目要求我们对兔子王国的小兔子们按总成绩进行排序,然后输出前k名的小兔子的名字和成绩。由于成绩都是整数,并且如果成绩相同,则按照输入的顺序输出。这里可以使用归并排序算法来实现。
题目分析:
归并排序是一个有效的排序算法,特别适合于大量数据的排序,其基本思想是将数组分成两半,递归地对它们进行归并排序,然后将结果合并起来。在合并过程中,如果两个子数组的头部元素相比,我们总是选择较大的元素先放入辅助数组中,这样可以确保排序的稳定性,即相同成绩的兔子会保持它们原始的顺序。
代码解析:
在代码中定义了一个结构体 node
来存储每个兔子的名字和成绩。man
数组用于存储所有兔子的信息,而 tmp
数组用于在归并过程中临时存储数据。
struct node{
string name;
double score;
}man[100010],tmp[100010];
merge
函数是归并排序中的合并步骤,它将两个已排序的子数组合并为一个已排序的数组:
void merge(long long p,long long q){
// ...
while(first<=mid&&second<=q){
if(man[first].score>=man[second].score){
// ...
}else{
// ...
}
}
// ...
for(int i=0;i<q-p+1;i++){
man[p+i].score=tmp[i].score;
man[p+i].name=tmp[i].name;
}
}
partition
函数是归并排序的拆分步骤,它递归地将数组分成越来越小的部分,直到每个部分只有一个元素,然后开始合并它们:
void partition(long long p,long long q){
// ...
if(mid>p) partition(p,mid);
if(mid+1<q) partition(mid+1,q);
merge(p,q);
}
在 main
函数中,首先读取兔子的数量 n
和要输出的前 k
名的数量,然后读取每个兔子的名字和成绩,存入 man
数组。调用 partition
函数对整个数组进行排序,最后输出前 k
名兔子的名字和成绩。
性能考量:
归并排序的时间复杂度为 ( O(n \log n) ),这对于题目中的数据范围(1万到10万)是足够的。代码使用了递归,对于较大的数据量,递归的深度也是可以接受的。
示例输出解释:
对于样例输入:
3 2
xiaotong 100
xiaocheng 93
xiaomei 99
我们需要对这三个兔子按成绩进行排序,并输出前2名。排序后的数组应该是:
xiaotong 100
xiaomei 99
xiaocheng 93
因此,对于这个输入,程序应该输出:
xiaotong 100
xiaomei 99
这代表成绩最高的两个兔子及其成绩。注意,即使归并排序是稳定的,但这里代码中的 score
字段是 double
类型的,理应是 int
,因为成绩是整数,这是代码中的一个小错误。不过,这不会影响排序的结果,因为在这个情境中,成绩是唯一的。
#include<bits/stdc++.h>
using namespace std;
struct node{
string name;
double score;
}man[100010],tmp[100010];
void merge(long long p,long long q){
//拆分子函数传值的下标
//mid中间值,first左区间,second右区间
long long mid=(p+q)/2,t=-1,first=p,second=mid+1;
while(first<=mid&&second<=q){
//左右区间都有值
if(man[first].score>=man[second].score){
//左区间按序跟右区间比较
tmp[++t].score=man[first].score;//小于等于右区间的数进入辅助数组
tmp[t].name=man[first++].name;
}else{
tmp[++t].score=man[second].score;//否则右区间的数进入辅助数组
tmp[t