1. 快速排序介绍
冒泡排序虽然好,可是太浪费时间了,计数排序虽快,但是太浪费空间了。那有没有两者兼得的方法呢?当然有,那就是快速排序。
快速排序是基于分治的一种排序算法。其核心就是找基准数(参照数)。将小于基准数的数放到基准数左边,大于基准数的数放到基准数右边,再递归分别处理左右两个区间。
按照上面的方式,让我们来排序数组int a[10]={6,1,2,7,9,3,4,5,10,8};
吧。
为了方便,我们就将第一个数
6
作为基准数吧。
接下来,需要将这个序列中所有比基准数大的数放在6的右边,
比基准数小的放在6的左边,类似下面这种排列
3 1 2 5 46
9 7 10 8
方法其实很简单,分别从初始序列6 1 2 7 9 3 4 5 10 8
两段开始"探测",
先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换它们。
这里可以用两个变量i
和j
分别指向序列最左边和最右边。
我们为这两个变量起一个好听点的名字"哨兵i"和"哨兵j"。
刚开始的时候,让哨兵i指向序列的最左边即(i=1),指向数字6
让哨兵j指向序列的最右边即(j=10),指向数字8
i j
6
1 2 7 9 3 4 5 108
因为此处设置的基准数是最左边的数,所以首先哨兵j
出动,这一点非常重要
哨兵j一步一步地向左挪动(即j--
),直到找到一个小于6的数停下来,
接下来哨兵i再一步一步地向右挪动(即i++
),直到找到一个大于6的数停下来,
最后哨兵j停在了数字5
面前,哨兵i停在了数字7
面前
i j
6 1 27
9 3 45
10 8
7和5交换。
i j
6 1 25
9 3 47
10 8
到此第一次交换结束,接下来哨兵j
继续向左挪动,。
他发现了4
(比基准数6要小,满足要求)之后停了下来。
哨兵i
也继续向右移动。
他发现了9
(比基准数6要大,满足要求)之后停了下来。
此时再次进行交换。
i j
6 1 2 59
34
7 10 8。
9和4交换
i j
6 1 2 54
39
7 10 8
第二次交换结束,探测继续,哨兵j
继续向左挪动。
他发现了3
(比基准数6要小,满足要求)之后又停了下来。
哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了。
哨兵i和哨兵j都走到3面前,说明此时"探测"结束,我们将基准数6和3进行交换,
i
j
6
1 2 5 43
9 7 10 8
6和3进行交换
i
j
3
1 2 5 46
9 7 10 8
到此第一轮"探测"真正结束此时以基准数6
为分界点,6左边的数都小于等于6, 6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j
的使命就是寻找小于基准数的数,而哨兵i
的使命就是要找大于基准数的数,直到i和j碰头为止。
现在基准数6已归位,他正好处在序列的第6位。
此时我们已经将原来的序列以6为分界点,拆分成了两个序列
3 1 2 5 4
和9 7 10 8
接下来还需要分别处理这两个序列,因为6左边和右边的序列目前都还是很混乱的。
不过不要紧,接下来只需要模拟刚才的方法分别处理至两个序列即可。请看下面这个霸气侧漏的表。
1 2 3 4 5 6 7 8 9 10
_____________________________
6 1 2 7 9 3 4 5 10 8 以6
为基准数
3 1 2 5 4 6 9 7 10 8 基准数6
已归位
3 1 2 5 4 | 以3
为基准数
2 1 3 5 4 | 基准数3
已归位
2 1 | | 以2
为基准数
1 2 | | 基准数2
已归位
1 | | | 以1
为基准数,因为只有一位,所以基准数1
已归位
| | | 5 4 | 以5
为基准数
| | | 4 5 | 基准数5
已归位
| | | 4 | | 以4
为基准数,因为只有一位,所以基准数4
已归位
| | | | | | 9 7 10 8 以9
为基准数
| | | | | | 8 7 9 10 基准数9
已归位
| | | | | | 8 7 | 10 以8
为基准数
| | | | | | 7 8 | 基准数8
已归位
| | | | | | 7 | | 以7
为基准数,因为只有一位,所以基准数7已归位
| | | | | | | | | 10 以10
为基准数,因为只有一位,所以基准数10已归位
| | | | | | | | | | 已全部归位
1 2 3 4 5 6 7 8 9 10
代码示例如下:
void quicksort(int a[], int left, int right) {//使用递归函数实现快排
int i, j, t, temp;
if (left > right)return;//递归边界
temp = a[left];//temp中存的就是基准数
i = left;//赋初值
j = right;
while (i != j) {//最后i会等于j
//顺序很重要,先从右往左找
while (a[j] >= temp && i < j)
j--;
//再从左往右找
while (a[i] <= temp && i < j)
i++;
//交换两个数在数组中的位置
if (i < j) {//当哨兵i和哨兵j没有相遇时
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
//最终将基准数归位
a[left] = a[i];
a[i] = temp;
quicksort(a, left, i - 1);//继续处理左边的,这里是一个递归的过程
quicksort(a, i + 1, right);//继续处理右边的,这里是一个递归的过程
}
快速排序不是稳定的,时间复杂度是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),简称 O ( n l o g n ) O(nlog\space n) O(nlog n)。最坏的情况下,如果选取的基准始终是最小值或最大值,导致分区不平衡,快速排序的时间复杂度会退化到 O ( n 2 ) O(n^2) O(n2)。但是这种情况在实际应用中出现的很少。
2.【模板】排序
题目描述
将读入的 N N N 个数从小到大排序后输出。
输入格式
第一行为一个正整数 N N N。
第二行包含 N N N 个空格隔开的正整数 a i a_i ai,为你需要进行排序的数。
输出格式
将给定的 N N N 个数从小到大输出,数之间空格隔开,行末换行且无空格。
输入输出样例
输入 #1
5
4 2 4 5 1
输出 #1
1 2 4 4 5
提示
对于 20 % 20\% 20% 的数据,有 1 ≤ N ≤ 1 0 3 1 \leq N \leq 10^3 1≤N≤103;
对于 100 % 100\% 100% 的数据,有 1 ≤ N ≤ 1 0 5 1 \leq N \leq 10^5 1≤N≤105, 1 ≤ a i ≤ 1 0 9 1 \le a_i \le 10^9 1≤ai≤109。
2.1.题意解析
由于这道题的数据量很大,所以
O
(
n
2
)
O(n^2)
O(n2)的算法均不能过。使用上面的代码也会超时(当数据量较大时,每多一条语句都可能会多运行几毫秒时间)。
所以这里对快速排序进行优化。
首先,将基准数选为a[(left+right)/2]
,这样能避免最后的交换。也可以先从左开始找了。
然后,每一次哨兵i
和哨兵j
交换完之后,再比较当前位置的数是没有意义的。所以要i++;j--;
。
最后,因为上面的改动,退出循环的条件要变成i>j
。画图想一想,为什么?
改进后的快速排序如下:
void qsort(int a[], int left, int right) {//使用递归函数实现快排
int tmp = a[(left + right) / 2], i = left, j = right;//从中间设基准数
do {
while (a[i] < tmp)//哨兵i向左找>=基准数的数
i++;
while (a[j] > tmp)//哨兵j向左找<=基准数的数
j--;
if (i <= j)
{
swap(a[i], a[j]);
i++; j--;//没必要在下一轮再比较这两个位置的数了
}
} while (i <= j);
if (left < j)qsort(a, left, j);//哨兵j没有超过左区间,在左区间排序
if (i < right)qsort(a, i, right);//哨兵i没有超过右区间,在右区间排序
}
当然,这个快速排序模版也不是绝对通用的,要根据具体情况编写代码。
2.2.AC代码
#include<bits/stdc++.h>
using namespace std;
void qsort(int a[],int left,int right)//使用递归函数实现快排
{
int tmp=a[(left+right)/2],i=left,j=right;//从中间设基准数
do
{
while(a[i]<tmp)//哨兵i向左找>=基准数的数
i++;
while(a[j]>tmp)//哨兵j向左找<=基准数的数
j--;
if(i<=j)
{
swap(a[i],a[j]);
i++;j--;//没必要在下一轮再比较这两个位置的数了
}
}while(i<=j);
if(left<j)qsort(a,left,j);//哨兵j没有超过左区间,在左区间排序
if(i<right)qsort(a,i,right);//哨兵i没有超过右区间,在右区间排序
}
int main()
{
int a[100010],n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
qsort(a,1,n);//调用快速排序函数
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
return 0;
}
这道题用sort也能通过,就不给出代码了。
喜欢就订阅此专辑吧!
【蓝胖子编程教育简介】
蓝胖子编程教育,是一家面向青少年的编程教育平台。平台为全国青少年提供最专业的编程教育服务,包括提供最新最详细的编程相关资讯、最专业的竞赛指导、最合理的课程规划等。本平台利用趣味性和互动性强的教学方式,旨在激发孩子们对编程的兴趣,培养他们的逻辑思维能力和创造力,让孩子们在轻松愉快的氛围中掌握编程知识,为未来科技人才的培养奠定坚实基础。
欢迎扫码关注蓝胖子编程教育