先来观察一下这个有序序列:
我们可以看出:
- 该序列整体有序
- 该序列由前五个元素组成的序列有序
- 该序列由后五个元素组成的序列有序
- 该序列右侧五个元素均比左侧五个元素大
- ……
- 每个元素对于自身有序
基本思想:分治
把我们看出的内容整理一下,我们可以得到一个结论: 一个有序序列是由许多个有序的子序列按序组合而成的。 这句话看似有些绕,但它体现出了一个 “分治” 的思想。
以升序为例:
- 选取一个元素,以它为分界线(基准数),分成左右两个子序列。要求这两个子序列满足“ 左侧的子序列中的所有元素比基准数小,右侧的子序列中的所有元素比基准数大” 。
- 分别把两个子序列当成一个独立的序列进行第一步操作。
转化为计算机语言:
在进行快速排序的过程中,最重要的元素莫过于 “基准数” ,接下来的操作中都将选取当前区间的第一个元素作为基准数。
在确定了基准数之后,我们需要做的就是将其他元素按照相对基准数的相对大小置于基准数两侧,我们可以轻易地一眼看出某个元素对于基准数的相对大小,但计算机最不擅长的就是“一眼看出”。那么问题来了: “如何确定基准数的位置” ,总不能每有一个元素比基准数小,基准数及比基准数大的元素都往后移动一个位置吧,那样的效率也太可怕了!
或许,我们可以反着来,先确定其他元素的位置,最后留出的一个位置,就是基准数的位置了!
实现:
从上述逻辑中不难看出,在实现时将使用递归操作,而递归的结束条件为当前区间仅有一个元素,即区间左右端点相等时递归结束。
//l为区间左端点,r为区间右端点
if (l >= r){
return;
}
先从两端开始遍历,从右侧开始寻找比基准数小的元素,从左侧寻找比基准数大的元素:
//temp是基准数,i从左端点出发,j从右端点出发
//由于选取的基准数为左端点,故基准数远端的j先出发,这样可以保证ij相遇时的元素本就应置于基准数左侧
while (i != j){
while (a[j] >= temp && i < j){
j--;
}
while (a[i] <= temp && i < j){
i++;
}
if (i < j){
a[i] ^= a[j] ^= a[i] ^= a[j];//交换a[i]与a[j]的数值
}
}
当i和j相遇时,将当前元素与基准数进行交换:
a[l] = a[i];
a[i] = temp;
这样,我们就完成了快速排序的一次操作,接下来递归调用即可:
quicksort(l, i-1);
quicksort(i+1, r);
附完整代码:
#include <iostream>
using namespace std;
int n,a[255];
void quicksort(int l,int r){
if (l >= r){
return;
}
int i,j,temp;
temp = a[l];
i = l;
j = r;
while (i != j){
while (a[j] >= temp && i < j){
j--;
}
while (a[i] <= temp && i < j){
i++;
}
if (i < j){
a[i] ^= a[j] ^= a[i] ^= a[j];
}
}
a[l] = a[i];
a[i] = temp;
quicksort(l, i-1);
quicksort(i+1, r);
}
int main (){
cin >> n;
for (int i = 0;i < n; i++){
cin >> a[i];
}
quicksort(0, n-1);
for (int i = 0;i < n; i++){
cout << a[i] << " ";
}
cout << endl;
return 0;
}