上一章中我们介绍了冒泡排序,但是其时间复杂度达到了O(N^2),速度较慢,在提交一些题时,可能会超时。接下来介绍一种既不浪费空间又可以快一点的排序——快速排序。
思想:分治,二分,递归。
思路大概:
例如:我们现在要用快排对一组数据:6 1 2 7 9 3 4 5 10 8 排序。首先我们先找一个基准数,也就是参照物,一般来说,我们以数组第一个数为基准数,对于这组数据也就是 6 ,然后将这组数据中大于基准数的放在其右边,小于基准数的放在其左边,类似:3 1 2 5 4 6 9 7 10 8。
这被成为分区。现在得到了:
- 一个由所有小于基准数的数字组成的子数组
- 基准数
- 一个由所有大于基准数的数字组成的子数组
这里我们只是进行了分区,得到了两个无序的子数组,所以需要重复以上步骤,直到有序为止,也就是递归。
具体实现:(还是以上面的数据为例)
- 选择基准值,一般而言我们以数据第一个为基准数。
- 分区。如何将大于基准值和小于基准值的数字放在基准数两边,是快排的关键。对于上述例子 6 1 2 7 9 3 4 5 10 8 ,我们选定了 6 为基准值,而它在第一位,那我们首先选定两个“哨兵i和j”,刚开始时,让 i 和 j 分别指向数据的最左边和最右边,即 i=1,指向数字 6 ;j=10,指向数字 8 ,然后让 j 从右到左找一个比 6 小的数,再让 i 左往右找一个比 6 大的数,交换他们。因为基准数是最左边的数,所以 j 先找,j 一步步挪动(即 j--),直到找到比 6 小的数 5 ,然后 i 开始行动(即 i++),直到找到一个大于 6 的数 7 ,交换它们的值,但 i 和 j 不变。即: i=3 和 j=8 分别指向了 7 和 5,交换得: 6 1 2 5 9 3 4 7 10 8。到此,第一次交换结束。然后重复之前的步骤(ps:仍是 j 先出发),直到 i 和 j 碰头为止。得到:3 1 2 5 4 6 9 7 10 8。到此第一轮“探测”结束。
- 递归。将第一次“探测”的左边“3 1 2 5 4”和“9 7 10 8”在进行1,2, 3操作,直到有序。
图解帮助理解:
C语言代码:
#include <stdio.h>
int a[101], n;
void quicksort(int left, int right)
{
int i, j, t, temp;
if (left > right) //递归终止条件
return;
temp = a[left]; //temp中存的就是基准数
i = left;
j = right;
while (i != j)
{
//先从右往左找
while (a[j] >= temp && i < j)
{
j--;
}
//在从左往右找
while (a[i] <= temp && i < j)
{
i++;
}
//交换
if (i < j)
{
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
//将基准数归位
a[left] = a[i];
a[i] = temp;
quicksort(left, i-1); //递归处理左边
quicksort(i+1, right); //递归处理右边
return;
}
int main()
{
int i, j;
scanf("%d", &n);
for (i = 0; i < n; i++)
{
scanf("%d", &a[i]);
}
quicksort(0, n-1);
for (i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
getchar();
return 0;
}
python代码:
def quicksort(array):
if len(array) < 2:
return array
else:
pivot = array[0] #选择基准值
#小于基准值的子数组
less = [i for i in array[1:] if i <= pivot]
#大于基准数的子数组
greater = [i for i in array[1:] if i > pivot]
return quicksort(less) + [pivot] + quicksort(greater)
print(quicksort([10, 5, 2, 3]))