【图解】快速排序—超级容易理解的做法

打算开始写算法专栏了,第一篇文章写什么呢?就快速排序吧!

1.快排介绍

快速排序的思想就是分治的思想,分治思想在算法中是非常常用也是非常重要的!一个问题,可以把它一分为二,然后再分别处理,依次类推,这就是分治的思想。

那么快速排序是怎么一回事呢?我来告诉你:

快速排序指的是,我们从一组数中任意选定一个数字(我们称这个数字叫基准值),然后经过一轮排序后使得小于基准值的数全部在基准值的左边;大于基准值的数全部在基准值的右边。这样我们从大的模块上来说就有序了,左边部分的最大值小于右边部分的最小值,然后再分别对左边部分快排,右边部分快排,直到有序

2.举例说明

看完这段话是不是不太好理解呢?没关系,我来举个简单的例子:

有5个数字,分别是 3 1 2 4 5,利用快速排序将其变成升序序列

那么,首先我们来选取一个基准值,基准值是任意的,你可以选第一个数作为基准值,也可以选最后一个,也可以选择中间的数,都可以!看你心情喽,但是如果你使用我接下来这种做法,那么为了确保万无一失,我建议你选取中间的数!那怎么选取中间的数呢?我们利用数组长度除以2 是不是就得到中间数的下标了呢

//定义 l 为数组起始, r 为数组结尾 , 数组为 a[], 基准值为 x
	int l = 0, r = a.size() - 1;
	x = a[(l + r) / 2];

这样,基准值 x 就得到了,那么这里你也可以这样写

//我们知道,在 位运算中,右移一位就是除以2的意思,故可以使用右移
	int l = 0 , r = a.size() - 1;
	x = a[l + r >> 1];

现在,基准值找到了,在序列3 1 2 4 5中,通过l + r >> 1得到下标为2,故基准值x = a[2],即 数字2

2.1 执行第一轮

此时,我们利用双指针 i, j 分别代表左边指针和右边指针,分别从数组起始端和末尾段分别向中间移动,如果左边遇到大于2的数停止左边指针移动;如果右边遇到小于2的数,停止右边指针移动;此时如果i < j那么将两个数交换swap(a[i], a[j])i指针和j指针继续移动, 直到 i > j,停止移动,此时第一轮结束。具体如下:

【图解】快速排序---超级容易理解的做法_数据结构
第一次比较,3 > 2所以3应该是在右半边的,此时 i 指针停止后移;j指针所指向的 5 > 2j指针前移

【图解】快速排序---超级容易理解的做法_快速排序_02
再次比较,j指针所指向的 4 > 2, j指针继续前移

【图解】快速排序---超级容易理解的做法_排序算法_03
此时,j 指针所指向的 2 ≯ 2,故 j 指针停止移动。判断 i< j ?如果符合,交换a[i], a[j]。如下
【图解】快速排序---超级容易理解的做法_算法_04
交换完成后,判断 I < j ?符合条件,继续移动
【图解】快速排序---超级容易理解的做法_数据结构_05
i指针指向的 1 < 2, 故 i指针继续移动
【图解】快速排序---超级容易理解的做法_图解快排_06
此时,i 指针指向的 3 ≮ 2i指针停止移动;j 指针向前移动
【图解】快速排序---超级容易理解的做法_数据结构_07
j指针指向的1 ≯ 2,j指针停止移动;判断i< j ?不符合条件,第一轮结束

2.2 递归左半边

接下来开始分别递归左边和右边,左边的起始为 l = 0, r = j; 右边的起始为 l = j + 1, r = r

【图解】快速排序---超级容易理解的做法_快速排序_08
对左边进行快排,选择基准值 x = l + r >> 1, 即(0 + 1 ) / 2 = 0, 所以a[0] = 2
【图解】快速排序---超级容易理解的做法_快速排序_09

i指针指向的 2 ≮ 2,故 i指针停止移动;j指针指向的1 ≯ 2,故,j 指针停止移动;此时i< j, 交换a[i], a[j]
【图解】快速排序---超级容易理解的做法_图解快排_10
继续移动 i, j。此时,发现i和j指向的值均不满足条件,且j > i,不符合交换和循环条件,结束

2.3 递归右半边

同理:右半边也是如此,故总的排序结果为
【图解】快速排序---超级容易理解的做法_快速排序_11

3.快排性质

	1. 快排的平均时间复杂度:O(nlogn) 
	2. 空间复杂度:O(logn) 
	3. 稳定性:由于此排序无法保证相等元素的相对位置不变,故它不是稳定的排序

4. 完整代码

#include <iostream>
using namespace std;

const int N = 100010;
int q[N];
int n;

//快速排序
void quick_sort(int q[], int l, int r)
{
    if (l >= r) return;
    int i = l - 1, j = r + 1, x = q[l + 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()
{
    cin >> 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;
}