快速排序介绍和【题解】——【模板】排序

1. 快速排序介绍

    冒泡排序虽然好,可是太浪费时间了,计数排序虽快,但是太浪费空间了。那有没有两者兼得的方法呢?当然有,那就是快速排序。

    快速排序是基于分治的一种排序算法。其核心就是找基准数(参照数)。将小于基准数的数放到基准数左边,大于基准数的数放到基准数右边,再递归分别处理左右两个区间。

在这里插入图片描述
按照上面的方式,让我们来排序数组int a[10]={6,1,2,7,9,3,4,5,10,8};吧。

为了方便,我们就将第一个数6作为基准数吧。
接下来,需要将这个序列中所有比基准数大的数放在6的右边,
比基准数小的放在6的左边,类似下面这种排列
3 1 2 5 4 6 9 7 10 8
方法其实很简单,分别从初始序列6 1 2 7 9 3 4 5 10 8两段开始"探测",
先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换它们。
这里可以用两个变量ij分别指向序列最左边和最右边。
我们为这两个变量起一个好听点的名字"哨兵i"和"哨兵j"。
刚开始的时候,让哨兵i指向序列的最左边即(i=1),指向数字6
让哨兵j指向序列的最右边即(j=10),指向数字8
i                                j
6 1 2 7 9 3 4 5 10 8
因为此处设置的基准数是最左边的数,所以首先哨兵j出动,这一点非常重要
哨兵j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来,
接下来哨兵i再一步一步地向右挪动(即i++),直到找到一个大于6的数停下来,
最后哨兵j停在了数字5面前,哨兵i停在了数字7面前
           i             j
6 1 2 7 9 3 4 5 10 8
7和5交换。
           i             j
6 1 2 5 9 3 4 7 10 8
到此第一次交换结束,接下来哨兵j继续向左挪动,。
他发现了4(比基准数6要小,满足要求)之后停了下来。
哨兵i也继续向右移动。
他发现了9(比基准数6要大,满足要求)之后停了下来。
此时再次进行交换。
             i        j
6 1 2 5 9 3 4 7 10 8。
9和4交换
             i        j
6 1 2 5 4 3 9 7 10 8
第二次交换结束,探测继续,哨兵j继续向左挪动。
他发现了3(比基准数6要小,满足要求)之后又停了下来。
哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了。
哨兵i和哨兵j都走到3面前,说明此时"探测"结束,我们将基准数6和3进行交换,
                   i
                   j
6 1 2 5 4 3 9 7 10 8
6和3进行交换
                   i
                   j
3 1 2 5 4 6 9 7 10 8
到此第一轮"探测"真正结束此时以基准数6为分界点,6左边的数都小于等于6, 6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是寻找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。
现在基准数6已归位,他正好处在序列的第6位。
此时我们已经将原来的序列以6为分界点,拆分成了两个序列
3 1 2 5 49 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 1N103

对于 100 % 100\% 100% 的数据,有 1 ≤ N ≤ 1 0 5 1 \leq N \leq 10^5 1N105 1 ≤ a i ≤ 1 0 9 1 \le a_i \le 10^9 1ai109

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也能通过,就不给出代码了。

喜欢就订阅此专辑吧!

【蓝胖子编程教育简介】
蓝胖子编程教育,是一家面向青少年的编程教育平台。平台为全国青少年提供最专业的编程教育服务,包括提供最新最详细的编程相关资讯、最专业的竞赛指导、最合理的课程规划等。本平台利用趣味性和互动性强的教学方式,旨在激发孩子们对编程的兴趣,培养他们的逻辑思维能力和创造力,让孩子们在轻松愉快的氛围中掌握编程知识,为未来科技人才的培养奠定坚实基础。

欢迎扫码关注蓝胖子编程教育
在这里插入图片描述

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
快速排序是一种常用的排序算法,它的基本思想是通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,然后再按此方法对这两部分记录分别进行快速排序,以达到整个序列有序的目的。 以下是JAVA中快速排序的实现代码: ```java public class QuickSort { public static void sort(int[] arr, int start, int end) { if (start < end) { int pivot = partition(arr, start, end); sort(arr, start, pivot - 1); sort(arr, pivot + 1, end); } } private static int partition(int[] arr, int start, int end) { int pivot = arr[start]; while (start < end) { while (start < end && arr[end] >= pivot) { end--; } arr[start] = arr[end]; while (start < end && arr[start] <= pivot) { start++; } arr[end] = arr[start]; } arr[start] = pivot; return start; } public static void main(String[] args) { int[] arr = {5, 6, 8, 1, 3, 2, 9, 4, 7}; sort(arr, 0, arr.length - 1); for (int i : arr) { System.out.print(i + " "); } } } ``` 在快速排序中,需要选择一个元素作为枢轴(pivot),这里选择第一个元素作为枢轴。然后将序列中的元素分为两部分,一部分比枢轴小,一部分比枢轴大。然后再对这两部分分别进行快速排序,最终得到有序序列。 在上述代码中,partition方法是关键,它实现了分割序列的功能。首先将枢轴存储在pivot变量中,然后从序列的两端开始遍历,找到第一个比枢轴小的元素和第一个比枢轴大的元素,然后将它们交换位置。重复这个过程直到start和end重合,最后将枢轴放到这个位置上。 sort方法是递归实现的,每次选择一个枢轴,然后将序列分为两部分,分别对这两部分进行快速排序,直到序列有序为止。 最后在main方法中,我们对一个无序序列进行快速排序,并输出有序序列。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值