hnust 1815: 算法10-6~10-8:快速排序
题目描述
快速排序是对起泡排序的一种改进。它的基本思想是,通过一趟排序将待排序的记录分割成两个独立的部分,其中一部分记录的关键字均比另一部分的关键字小,在分成两个部分之后则可以分别对这两个部分继续进行排序,从而使整个序列有序。
快速排序的算法可以描述如下:
在本题中,读入一串整数,将其使用以上描述的快速排序的方法从小到大排序,并输出。
输入
输入的第一行包含1个正整数n,表示共有n个整数需要参与排序。其中n不超过100000。
第二行包含n个用空格隔开的正整数,表示n个需要排序的整数。
输出
只有1行,包含n个整数,表示从小到大排序完毕的所有整数。
请在每个整数后输出一个空格,并请注意行尾输出换行。
样例输入 Copy
10
2 8 4 6 1 10 7 3 5 9
样例输出 Copy
1 2 3 4 5 6 7 8 9 10
提示
在本题中,需要按照题目描述中的算法完成快速排序的算法。
快速排序是一种十分常用的排序算法,其平均时间复杂度为O(knlnn),其中n为待排序序列中记录的个数,k为常数。大量的实际应用证明,在所有同数量级的此类排序算法中,快速排序的常数因子k是最小的,因此,就平均时间而言,快速排序是目前被认为最好的一种内部排序方法。
而在C语言的常用编译器中,qsort函数是一个非常常用的快速排序函数。
解题过程
快速排序是一种高效的排序算法,使用分治法(Divide and Conquer)策略来把一个序列分为较小的两个子序列,然后递归地排序两个子序列。
题干代码解析
图片中提供的代码是快速排序算法(Quick Sort)的实现,它由两个主要部分组成:分区函数 Partition
和快速排序函数 QSort
。以下是对代码的详细解析:
Partition
函数:
- 函数作用:对顺序表的一个子序列进行分区操作,使枢轴元素(pivot)最终位于它最终排序的位置,并使所有比它小的元素位于它左边,所有比它大的元素位于它右边。
- 参数:
SqList &L
:引用传递的顺序表。int low
:子序列的起始索引。int high
:子序列的结束索引。
- 过程:
- 选择
L.r[low]
作为枢轴记录pivotkey
。 - 使用两个指针
low
和high
,从两端交替向中间扫描,直到low
和high
相遇。 - 当
L.r[high].key
小于或等于pivotkey
时,将high
指针向左移动。 - 当
L.r[low].key
大于pivotkey
时,将low
指针向右移动,并交换L.r[low]
和L.r[high]
。 - 当
low
和high
相遇时,将枢轴记录移动到它最终的位置L.r[low]
。 - 返回枢轴记录的位置。
- 选择
QSort
函数:
- 函数作用:递归地对顺序表的子序列进行快速排序。
- 参数:
SqList &L
:引用传递的顺序表。int low
:子序列的起始索引。int high
:子序列的结束索引。
- 过程:
- 如果
low
小于high
,说明子序列长度大于1,需要排序。 - 调用
Partition
函数对子序列进行分区,得到枢轴位置pivotloc
。 - 对枢轴左边的子序列
L.r[low...pivotloc-1]
递归调用QSort
进行排序。 - 对枢轴右边的子序列
L.r[pivotloc+1...high]
递归调用QSort
进行排序。
- 如果
QuickSort
函数:
- 函数作用:快速排序算法的入口函数,对整个顺序表进行排序。
- 参数:
SqList &L
:引用传递的顺序表。
- 过程:
- 调用
QSort
函数,传入顺序表L
,以及起始索引1和结束索引L.length
。
- 调用
代码逻辑分析:
- 快速排序是一种分治算法,它通过递归地将数据分为较小的子问题来解决。
- 算法的关键在于分区操作,它通过选取一个枢轴元素,将数据分为两部分,使得左边的所有元素都不大于枢轴,右边的所有元素都不小于枢轴。
- 通过递归地对子序列进行快速排序,最终达到整个序列有序的目的。
注意事项:
- 快速排序的性能在最坏情况下是 O(n^2),但平均情况下是 O(n log n),这取决于枢轴的选择。
- 快速排序是不稳定的排序算法,因为相同元素的顺序可能会在分区过程中改变。
- 快速排序通常需要随机化处理来避免最坏情况的发生,特别是在输入数据已经有序或接近有序的情况下。
以下是对小编的高中模板递归方法快速排序的详细讲解
-
函数定义:
quick_sort(int q[], int l, int r)
:这是快速排序的递归函数,接收一个整数数组q
和两个整数l
和r
作为参数,分别表示要排序的数组部分的起始和结束索引。
-
基本情况:
- 如果
l
大于等于r
,则表示当前要排序的数组部分已经是一个元素或为空,不需要排序,直接返回。
- 如果
-
选择基准元素:
- 使用
(l + r >> 1)
计算中间位置的索引,并将该位置的元素q[l + r >> 1]
作为基准元素x
。这里使用了位运算符>> 1
来实现整数除以2。
- 使用
-
初始化指针:
i
初始化为l - 1
,j
初始化为r + 1
,这两个指针用于从数组的两端开始遍历。
-
分区操作:
- 使用两个
while
循环进行分区:- 第一个
while
循环中,i
从左向右遍历,直到找到第一个不小于x
的元素。 - 第二个
while
循环中,j
从右向左遍历,直到找到第一个不大于x
的元素。
- 第一个
- 当
i
小于j
时,使用swap
函数交换q[i]
和q[j]
,将较大的元素移动到数组右侧,较小的元素移动到左侧。
- 使用两个
-
递归排序:
- 当完成一次分区操作后,对基准元素左边和右边的子数组分别进行快速排序。
-
函数调用:
quick_sort(q, l, j)
对左侧子数组进行排序。quick_sort(q, j + 1, r)
对右侧子数组进行排序。
-
swap
函数:- 代码中没有给出
swap
函数的实现,但它应该是一个交换两个整数的函数。
- 代码中没有给出
-
算法性能:
- 快速排序的平均时间复杂度为 O(n log n),在大多数情况下表现良好。但在最坏情况下(例如,数组已经排序或所有元素相等),时间复杂度会退化为 O(n^2)。
-
稳定性:
- 快速排序是不稳定的排序算法,因为相同的元素可能在分区过程中改变它们原来的顺序。
快速排序是一种非常实用的排序算法,由于其高效性,它在实际应用中非常广泛。然而,对于大型数据集或需要稳定性的场景,可能需要考虑其他排序算法,如归并排序。
AC代码
这里小编手懒了,就直接用了高中背过的算法模板,上面有讲解
#include <iostream>
using namespace std;const int N = 1e6 + 10;
int n,q[N];void quick_sort(int q[], int l, int r)
{if(l>=r) return;int x=q[l+r>>1], i=l-1, j=r+1;while(i<j){do i++; while(q[i]<x);do j--; while(q[j]>x);if(i<j) swap(q[i],q[j]);}quick_sort(q,l,j), quick_sort(q,j+1,r);
}int main()
{scanf("%d", &n);for(int i=0; i<n; i++)scanf("%d", &q[i]);quick_sort(q,0,n-1);for(int i=0; i<n; i++)printf("%d ", q[i]);return 0;
}