快速排序的基本思想: 在待排序的序列中选取一个值作为一个基准值,按照其他数值与这个基准值的大小关系将这个序列划分成两个子序列(基准值会在这两个子序列的中间,划分成一边所有元素比基准值大,一边所有元素比基准值小)。这样快速排序第一次排完,我们选取的这个基准值就会出现在它该出现的位置上。这就是快速排序的单趟算法,也就是完成了一次快速排序。
具体操作:
以10个整型元素的数组arr表示:int arr[10] = { 3,5,1,4,2,9,8,6,7,10 };
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
3 | 5 | 1 | 4 | 2 | 9 | 8 | 6 | 7 | 10 |
第一行字体为红色的表示元素索引,第二行字体为黑色的表示元素的数值。
第一次迭代:
- ①选第一个元素(索引为“0”)为基准值。此时v = a[0] = 3 j = 0 i = 1 left = 0 right = 9
- ②从第二个元素开始遍历,找出比基准值小的数,可以发现是第三个元素,即i = 2时 arr[2] = 1 < v ,于是交换 ++j 与 i的元素值。(即第二个和第三个元素的数值)此时结果如下: 交换后 v = a[0] = 3 j = 1 i = 2
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
3 | 1 | 5 | 4 | 2 | 9 | 8 | 6 | 7 | 10 |
- ③接下来从第四个元素开始遍历(i = 3),直到遍历到第五个元素,即 i = 4 时 arr[4] = 2 < v 于是交换 ++j 与 i的元素值。 此时结果如下: 交换后 v = a[0] = 3 j = 2 i = 4
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
3 | 1 | 2 | 4 | 5 | 9 | 8 | 6 | 7 | 10 |
-
④可以看出后面都是大于“3”的值,所以 i 遍历完 9 后结束,此时 j = 2 ,交换 基准值与 j 的值。数值”3“就到了它该在的位置。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
2 | 1 | 3 | 4 | 5 | 9 | 8 | 6 | 7 | 10 |
第二次迭代:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
2 | 1 | 3 | 4 | 5 | 9 | 8 | 6 | 7 | 10 |
- ①此时 v = a[0] = 2 j = 0 i = 1 left = 0 right = 2
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
2 | 1 | 3 | 4 | 5 | 9 | 8 | 6 | 7 | 10 |
- ② arr[1] = 1 < v 所以 交换 ++j 与 索引为 1 的位置。其实就是自己跟自己交换
-
③ 再往后遍历 ,直到 遍历完i = right = 2 此时 j = 1 交换 基准值 和 j 的元素值。
-
0 1 2 3 4 5 6 7 8 9 1 2 3 4 5 9 8 6 7 10
第三次迭代:
left = right = 0 直接返回,不用排序了
接下来就是第一次迭代选的比 "3"数字大的进行迭代,也就是索引"3"-"9"部分用同样的方法进行迭代。
这是算法第四版(中文版 第 183 页的算法执行示意图)
C++代码:
#include<iostream>
int __partition(int arr[], int left, int right) {
int v = arr[left];
int j = left;
for (int i = left+1; i <= right; i++)
if (arr[i] < v) //如果发现比 基准值小的
swap(arr[++j], arr[i]);//就将当前数组中 第一个比基准值大的数与arr[i]交换
arr[left] = arr[j];
arr[j] = v;
return j;
}
void __quickSort(int arr[], int left, int right) {
if (left>=right)
return;
int index = __partition(arr, left, right);
__quickSort(arr, left, index - 1);
__quickSort(arr, index + 1, right);
}
void sort(int arr[], int n)
{
__quickSort(arr, 0, n - 1);
}
快速排序是平均上的快,不是每个排序用这个算法解决都是最快的,就更别说某些情况下还不好使用快速排序这个算法。比如一个比较大的,还近乎有序的数组使用快速排序, 那么程序迭代的次数就近乎等于元素的个数了(复杂度退化为O(n^2)),栈不溢出才怪,想想都可怕。。。但如果选择插入排序,那么插入排序的算法复杂度可以降到O(nlogn)级别,可以说很快了。
为了不让这种近乎有序的数组选出的基准值分成的两部分太不平衡的情况出现,我们可以随机选择个基准值。即在元素中随机选择一个数值,然后再让这个数值与第一个数值交换,但基准值还是选择的是第一个元素。其它代码不变,这样相对的可以优化很多。
还有种情况,当数组中的重复元素过多,也会出现分开的两个数组太不平衡的情况,这时候我们可以用双路快速排序法进行优化,具体看:
(双路快速排序算法)https://blog.youkuaiyun.com/ive_lanco/article/details/81156432
(三路快速排序算法,对于重复元素较多的更优)https://blog.youkuaiyun.com/ive_lanco/article/details/81157826