同冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。
不同的是,冒泡排序在每一轮只把一个元素冒泡到数列的一端,而快速排序在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边,比它小的元素移动到数列的另一边,从而把数列拆解成了两个部分。
这种方法叫分治法。
快排的主要思想就是分治,每次把大于基准和小于基准的数分在两边,每部分在下次又分为两个部分,直到不可再分。
这种方法平均情况下需要进行 logn 轮,因此快速排序算法的平均时间复杂度是 O(nlogn)。但是有时选基准会选到区间内的最大或最小值,这样每次就只是确定了基准的位置,没有发挥分治法的优势,这种极端条件下,会进行 n 轮,所以快排最坏的时间复杂度为 O(n²) 。
确定好基数后就是把数分为大于基数和小于基数的两部分了,分数有两种方法:
1. 挖坑法
2. 指针交换法
挖坑法
挖坑法顾名思义就是挖坑,挖完坑找数填坑。
我们举个栗子来理解他,规定一个数组:
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 14 | 7 | 16 | 5 | 3 | 22 | 8 | 10 |
指针 | i | j |
首先确定一个基准元素 key = a[0] = 14,此时 a[0] 这个位置就相当于一个坑。初始时左右各一个指针,i = 0,j = 7,i 将要指向大于基准的数的位置,j 将要指向小于基准的位置,因为要保证基准数左边全部是小于基准的数,基准右边全部大于基准,所以 j 从最右边开始左移,直到指向小于基准的数,放到左边的坑里,此时就出现了一个新坑(j 指向的小于基准的数的位置),i 再从左开始找到大于基准的数,填到右边的坑里,这时 i 指向的大于基准的数的位置就是一个新坑,重复上述步骤,直到没有可以改变的位置。
下面来模拟过程(红色位置为坑,蓝色位置为找到的要填的数的位置):
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 14 | 7 | 16 | 5 | 3 | 22 | 8 | 10 |
指针 | i | j |
从右开始找到位置 7 为小于基准的数的位置,把 a[7] 这个数填到 0 这个坑里,结果:
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 10 | 7 | 16 | 5 | 3 | 22 | 8 | 10 |
指针 | i | j |
此时 7 就是一个新坑,再从左开始找比基准数大的值
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 10 | 7 | 16 | 5 | 3 | 22 | 8 | 10 |
指针 | i | j |
找到了 2 这个位置,再把 a[2] 填到 7 这个坑里,结果:
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 10 | 7 | 16 | 5 | 3 | 22 | 8 | 16 |
指针 | i | j |
2 为新坑,再从右开始找比基准小的数填到左边
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 10 | 7 | 8 | 5 | 3 | 22 | 8 | 16 |
指针 | i | j |
将 a[6] 填入 2 中,6 为坑,继续从左边找
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 10 | 7 | 8 | 5 | 3 | 22 | 22 | 16 |
指针 | i | j |
找到 5 的位置,将 a[5] 填到坑6 里,5 为新坑,j 继续向左移动
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 10 | 7 | 8 | 5 | 3 | 22 | 22 | 16 |
指针 | i , j |
此时 i, j 重合,5 位置为坑,把之前的基准 key 填入坑 5,这样一次循环就结束了,结果:
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 10 | 7 | 8 | 5 | 3 | 14 | 22 | 16 |
指针 | i , j |
主要代码
void quick_sort(int a[], int l, int r)
{
if(l<r)
{
int i = l, j = r, key = a[l];
while(i<j)
{
while(i<j && a[j]>=key) j--;
if(i<j) a[i++] = a[j];
while(i<j && a[i]<=key) i++;
if(i<j) a[j--] = a[i];
}
a[i] = key;
quick_sort(a,l,i-1);
quick_sort(a,i+1,r);
}
}
运行过程
指针交换法
挖坑法是找到一个放一个,而指针交换法是同时找到两个,交换他们的位置
同样的,来举栗子:
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 14 | 7 | 16 | 5 | 3 | 22 | 8 | 10 |
指针 | i | j |
没错还是这个数组,还是这个基准 key = a[0] = 14,还是这个指针 i = 0,i = 7,不同的是,这次小 i 和小 j 一同奔跑(为了能快点见面也是不容易),i 还是向右找到大于基准的数,j 还是向左找到小于基准的数
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 14 | 7 | 16 | 5 | 3 | 22 | 8 | 10 |
指针 | i | j |
找到后交换两个指针指向的数字
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 14 | 7 | 10 | 5 | 3 | 22 | 8 | 16 |
指针 | i | j |
交换后继续奔跑
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 14 | 7 | 10 | 5 | 3 | 22 | 8 | 16 |
指针 | i | j |
继续交换
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 14 | 7 | 10 | 5 | 3 | 8 | 22 | 16 |
指针 | i | j |
交换后会发现,挨着了,怎么办,这时要先让小 j 跑,跑到小 i 的位置,为什么呢,先来看一下小 j 先跑的结果
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 14 | 7 | 10 | 5 | 3 | 8 | 22 | 16 |
指针 | i, j |
别忘了还有个基准等着分配,此时就要和基准交换了
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 8 | 7 | 10 | 5 | 3 | 14 | 22 | 16 |
指针 | i, j |
有木有感觉很完美,这就是小 j 先跑的结果,咱们再返回去看小 i 先跑的结果
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 14 | 7 | 10 | 5 | 3 | 8 | 22 | 16 |
指针 | i, j |
然后进行交换
序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
数字 | 22 | 7 | 10 | 5 | 3 | 8 | 14 | 16 |
指针 | i, j |
还记不记得快排的思想是什么,基准左边的数都要小于基准,现在 22 在 14 左边就出错了,所以每次移动,要先让小 j 动
主要代码
void quick_sort(int a[], int l, int r)
{
if(l > r) return;
int i = l, j = r, key = a[l];
while(i!=j)
{
while(i<j && a[j]>=key) j--;
while(i<j && a[i]<=key) i++;
if(i<j)
{
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
a[l] = a[i];
a[i] = key;
quick_sort(a,l,i-1);
quick_sort(a,i+1,r);
}