快速排序
1.快排原理
基本算法思想是分治算法。
通过一轮的排序将序列分割成独立的两部分,其中一部分序列的关键字均比另一部分关键字小。继续对长度较短的序列进
行同样的分割,最后到达整体有序。
在排序过程中,由于已经分开的两部分的元素不需要进行比较,故减少了比较次数,降低了排序时间。
1.1详细描述
先在要排序的序列 a 中选取一个基准数(基准数,可以随机取,也可以取固定位置,本文就取第一个为基准数吧,不要
被这个名词吓到了,就是一个用来参照的数),经过一趟排序将序列分成两个部分,其中左边的部分 b 中的元素均小于基
准数,右边的部分 c 的元素均大于基数,然后通过递归调用快速排序分别对两个部分进行排序,直到序列为空或者只有一
个元素,得到最终的有序序列。
2.例子
例子就从网上找一个吧。
假设我们现在对“6 1 2 7 9 3 4 5 10 8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数。
为了方便,就让第一个数6作为基准数吧。
在初始状态下,数字6在序列的第1位。我们的目标是将6挪到序列中间的某个位置,假设这个位置是k。现在就
需要寻找这个k,并且以第k位为分界点,左边的数都小于等于6,右边的数都大于等于6。想一想,你有办法可以
做到这点吗?
方法其实很简单
1.分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一
个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们为这两个变量起个
好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=0),指向数字6。让哨兵j指向序列的
最右边(即j=9),指向数字8。
首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为
什么)。哨兵j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。
2.接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停
在了数字7面前。
现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下。
6 1 2 5 9 3 4 7 10 8
3.第一次交换结束。接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要
小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再
次进行交换,交换之后的序列如下。
6 1 2 5 4 3 9 7 10 8
第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。哨兵i
继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进
行交换。交换之后的序列如下:
3 1 2 5 4 6 9 7 10 8
到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。
回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,
直到i和j碰头为止。剩下的步骤就是重复上面的过程。
现在基准数6已经归位,它正好处在序列的第6位。此时我们已经将原来的序列,以6为分界点拆分成了两
个序列,左边的序列是“3 1 2 5 4”,右边的序列是“9 7 10 8”。接下来还需要分别处理这两个序列。因为
6左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法
分别处理6左边和右边的序列即可。现在先来处理6左边的序列现吧。
左边的序列是“3 1 2 5 4”。请将这个序列以3为基准数进行调整,使得3左边的数都小于等于3,3右边的
数都大于等于3。好了开始动笔吧。如果你模拟的没有错,调整完毕之后的序列的顺序应该是:
2 1 3 5 4
OK,现在3已经归位。接下来需要处理3左边的序列“2 1”和右边的序列“5 4”。对序列“2 1”以2为基准数进行
调整,处理完毕之后的序列为“1 2”,到此2已经归位。序列“1”只有一个数,也不需要进行任何处理。至此我们
对序列“2 1”已全部处理完毕,得到序列是“1 2”。序列“5 4”的处理也仿照此方法,最后得到的序列如下:
1 2 3 4 5 6 9 7 10 8
对于序列“9 7 10 8”也模拟刚才的过程,直到不可拆分出新的子序列为止。最终将会得到这样的序列,如下:
1 2 3 4 5 6 7 8 9 10
3.代码实现
3.1实现方式一(上边的例子实现方法)
/**
* 快速排序
* @param a
* @param i
* @param j
*/
public static void quick_sort(int[] a,int i,int j){
if(i>j){return;}
//定义两个哨兵 start和 end
int start = i;
int end = j;
//基准数
int key = a[i];
int t;
while(end>start){
int temp;
//先从后往前比较,如果没有比基准数小的,依次往前一个数,直到有比基准数小的
while(end>start && a[end]>=key){end--;}
//从前往后比较,如果没有比基准数大的,依次往下一个数,直到有比基准数大的
while(end>start && a[start]<=key){start++;}
if(start<end){
temp=a[start];
a[start]=a[end];
a[end]=temp;
}
}
//最终将基准数归位
t=a[i];
a[i]=a[start];
a[start]=t;
// for(int l = 0; l<a.length; l++){
// System.out.print(a[l]+" ");
// }
// System.out.println();
//此时一次循环比较结束,基准数的位置已经确定了。左边的值都比基准数小,
//右边的值都比基准数大,但是两边的顺序还有可能不一样,下面进行递归调用
//左边序列,第一个索引位置到 关键值索引-1
if(start>i) quick_sort(a,i,start-1);
//右边序列,从 关键值索引+1 到 最后一个
if(end<j) quick_sort(a,end+1,j);
}
3.2实现方式二(另一种)
/**
* 快速排序
* @param a
* @param low
* @param high
*/
public static void quick_sort(int[] a,int low,int high){
//定义两个哨兵 start和 end
int start = low;
int end = high;
//基准数
int key = a[low];
while( end > start ){
//先从后往前比较,如果没有比基准数小的,依次往前一个数,直到有比基准数小的交换位置
while( end > start && a[end] >= key){end--;}
if( a[end] < key ){
int temp = a[end];
a[end] = a[start];
a[start] = temp;
}
//从前往后比较,如果没有比基准数大的,依次往下一个数,直到有比基准数大的交换位置
while(end>start && a[start]<=key){start++;}
if( a[start] > key ) {
int temp = a[start];
a[start] = a[end];
a[end] = temp;
}
}
//此时一次循环比较结束,基准数的位置已经确定了。左边的值都比基准数小,右边的值都比基准数大
//但是两边的顺序还有可能不一样,下面进行递归调用
//左边序列,第一个索引位置到 关键值索引-1
if(start > low) quick_sort(a,low,start-1);
//右边序列,从 关键值索引+1 到 最后一个
if(end < high) quick_sort(a,end+1,high);
}
3.3完整代码(java)(实现方式二)
public class QuickSort {
public static void main(String []args){
//创建一个数组
int[] a = {16,20,5,12,15,1,30,45,23,9};
int start = 0;
int end = a.length-1;
quick_sort(a,start,end);
System.out.println("排序后的数组:");
for(int i = 0; i<a.length; i++){
System.out.print(a[i]+" ");
}
}
/**
* 快速排序
* @param a
* @param low
* @param high
*/
public static void quick_sort(int[] a,int low,int high){
//定义两个哨兵 start和 end
int start = low;
int end = high;
//基准数
int key = a[low];
while(end > start){
//先从后往前比较,如果没有比基准数小的,依次往前一个数,直到有比基准数小的交换位置
while(end>start && a[end]>=key){end--;}
if(a[end] < key){
int temp = a[end];
a[end] = a[start];
a[start] = temp;
}
//从前往后比较,如果没有比基准数大的,依次往下一个数,直到有比基准数大的交换位置
while(end>start && a[start]<=key){start++;}
if(a[start] > key) {
int temp = a[start];
a[start] = a[end];
a[end] = temp;
}
}
//此时一次循环比较结束,基准数的位置已经确定了。左边的值都比基准数小,右边的值都比基准数大
//但是两边的顺序还有可能不一样,下面进行递归调用
//左边序列,第一个索引位置到 关键值索引-1
if(start > low) quick_sort(a,low,start-1);
//右边序列,从 关键值索引+1 到 最后一个
if(end < high) quick_sort(a,end+1,high);
}
}
4.时间复杂度
平均为O(nlogn)
最好为O(nlogn)
最差为O(n^2),例如,数组倒序,基准数选第一位。