快速排序每次递归进行的效果是基准数左侧的数都比它小,基准数右侧的数都比它大。
假设现在有一组数 12 5 7 3 14 2 6 9 ,我们应该怎样进行快速排序
元素值 | 12 | 5 | 7 | 3 | 14 | 2 | 6 | 9 |
位置下标 | ① | ② | ③ | ④ | ⑤ | ⑥ | ⑦ | ⑧ |
首先我们需要选定一个数作为基准数,让它左边的数都比它小,右边的数都比它大,一般选第一个数作为基准数,即12;然后还需定义两个“哨兵” i , j 。这两个哨兵的起始位置分别是,i 在最左侧,j 在最右侧,即 i 最开始指向首元素12, j 指向末尾元素9。
12 (i) | 5 | 7 | 3 | 14 | 2 | 6 | 9 (j) |
确定两个“哨兵”的位置后,首先让哨兵 j 向左运动 ,每次走一步,直到碰见比基准数12小的数并停在那个位置,在这里哨兵 j 首先碰见元素9 ,小于基准数12,遂停在那里,
12 | 5 | 7 | 3 | 14 | 2 | 6 | 9 |
j |
这时哨兵 i 开始向右走,每次走一步,直到碰见比基准数大的元素,在这里哨兵 i 碰见元素14,大于基准数,遂停在那里;
12 | 5 | 7 | 3 | 14 | 2 | 6 | 9 |
i | j |
这个时候交换哨兵 i 和 j 所在位置的值
12 | 5 | 7 | 3 | 9 | 2 | 6 | 14 |
i | j |
这时,哨兵 j 继续向左走找比基准数12小的数,这次它找到了元素6并停在那个位置处
12 | 5 | 7 | 3 | 9 | 2 | 6 | 14 |
i | j |
哨兵 i 继续向右走寻找比基准数12大的值,注意它走着走着还没找到比12大的元素,就在元素6处碰到了哨兵 j,这两个哨兵 i 和 j 相遇后,互相告知了自己一路走来的情况
12 | 5 | 7 | 3 | 9 | 2 | 6 | 14 |
i,j |
然后就哨兵 i 和 j 就不继续走了,将他俩遇见位置处的值和最开始的基准数12交换
6 | 5 | 7 | 3 | 9 | 2 | 12 | 14 |
i,j |
好了,到这里终于完成了第一轮探测 ,现在我们通过观察发现,基准数12左边的数都比它小,右边的数都比它大,12这个元素的位置就确定下来了。
接下来观察发现12左边的数列和右边的数列,发现它们还是比较混乱无序的,不需要担心,只要我们对左右两边的数列继续进行上面的操作
观察右半边的数列,只有一个元素14,可以认为它已经排好序了
对左半边的数列进行操作
6 | 5 | 7 | 3 | 9 | 2 |
i | j |
这时基准数选择为6,哨兵 i 和 j 的位置如上面所示,
哨兵 j 先走,寻找比基准数6小的元素,找到了元素2,并停在那个位置
6 | 5 | 7 | 3 | 9 | 2 |
i | j |
哨兵 i 开始走,寻找比基准数6大的元素,找到了元素7,并停在那个位置
6 | 5 | 7 | 3 | 9 | 2 |
i | j |
交换哨兵 i 和 j 所在位置的元素
6 | 5 | 2 | 3 | 9 | 7 |
i | j |
哨兵 j 继续向左走找比基准数6小的数,找到元素3并停在那里,哨兵i向右走碰到哨兵 j
6 | 5 | 2 | 3 | 9 | 7 |
i,j |
交换相遇位置的数和基准数
3 | 5 | 2 | 6 | 9 | 7 |
i,j |
这时候又完成了一轮探测,左边元素都比基准元素6小,右边元素都比基准元素大
现在又生成了数列 3,5,2和数列9 ,7
我们继续对这两个数列进行上面的操作
先看数列3,5,2
3 | 5 | 2 |
i | j |
现在选择基准数为3,哨兵 i 和 j 的位置如上所示
依然是哨兵 j 先向左边寻找比基准数3小的元素,找到了元素2并停在那个位置,
3 | 5 | 2 |
i | j |
哨兵 i 向右边寻找比基准数3大的元素,找到了元素5并停在那个位置
3 | 5 | 2 |
i | j |
交换哨兵 i 和哨兵 j 所在位置的元素
3 | 2 | 5 |
i | j |
哨兵 j 继续向左寻找,刚走一步就碰在元素2处碰到哨兵 i ,
3 | 2 | 5 |
i, j |
将两个哨兵碰见位置处的元素和基准元素交换,
2 | 3 | 5 |
i, j |
现在基准元素3左边的数都比它小,右边的元素都比它大
现在我们看数列9 ,7
9 | 7 |
i | j |
现在的基准元素是9,仍然是哨兵 j 先向左边走,寻找比基准元素9小的元素,但是刚出门就碰到了哨兵 i ,交换元素
7 | 9 |
i | j |
好了到此为止我们已经将所有数列排序完成,现在只需将每次排序得到的结果拼接起来就得到了最终排序结果
所以序列12 5 7 3 14 2 6 9排序后的结果是
2,3,5,6,7,9,12,14
那么怎样用C++来实现呢
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
void quicksort(int left, int right, int *a);
int main()
{
int arr[] = { 12,5,7,3,14,2,6,9 };
int len = sizeof(arr) / sizeof(arr[0]);
//vector<int> vec(arr, arr + len);
cout << "before quick sort: ";
for (auto c : arr)
{
cout << c << " ";
}
cout << endl;
cout << "after quick sort: ";
quicksort(0, len-1, arr);
for (int i = 0; i <len; ++i)
{
cout << arr[i]<<" ";
}
return 0;
}
void quicksort(int left, int right, int* a)
{
if (left >= right)
return;
int i, j, base, temp;
i = left, j = right;
base = a[left];//取最左边的数为基准数
while (i < j)
{
while (a[j] >= base && i < j)
j--;
while (a[i] <= base && i < j)
i++;
if (i < j)
{
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
//基准数归为
a[left] = a[i];
a[i] = base;
quicksort(left, i - 1, a);//递归左边
quicksort(i + 1, right, a);//递归右边
}
运算结果是
为什么要让哨兵 j 先走?
(哨兵 i 就是左指针,哨兵 j 就是右指针)
正如我们开头所说的快速排序每次递归要达成的效果是在基准数左侧的数都比基准数小,在基准数右侧的数都比基准数大;左指针向右逐元素运动,直到碰见比基准数大的数停下,右指针向左运动,直到碰见比基准数小的数停下;而我们最后是要把左右指针相遇位置处的值和基准位置处的值进行交换,而我们选择的基准数都是第一个数字,所以要确保交换的数字要比基准数小。只有右指针先走,才可以保证在相遇处的数字比基准数小,如果左指针先走,它首先找见的是比基准值大的一个元素,所以不能让左指针先走。