说在前面的话
平常码砖的时候,对于一个数组进行排序更多的是起泡排序,起泡排序对于一般不是很长的数组进行操作没什么问题,一旦数组过大,很明显效率低。
而快排是对起泡排序的一种改进,效率明显优高。
快排思路
快排的思想是通过每一次排序将待排的数组分成两部分,左边的部分所有值均小于右边部分,然后再对这两部分分别再进行排序以达到整修序列有序。
Example:
有如下一个无序的序列 arr[](长度为10),现在要对其进行快排
|
10 | 9 | 22 | 38 | 47 | 7 | 11 | 2 | 82 | 1 |
1.首先要先任一个记录作为枢轴(也可称为支点),然后将其它的记录与期进行对比,比枢轴小的记录放左边,
比枢轴大的记录放右边通常我们选取序列的第一个记录作为枢轴。
|
10 | 9 | 22 | 38 | 47 | 7 | 11 | 2 | 82 | 1 |
将arr[0]=10 作为枢轴
2.接下来将各记录与其进行对比,关键是如何将各记录与枢轴进行对比,这里涉及到一个小的技巧,挖坑填坑!
将枢轴赋给一个临时的变量,挖一个坑,寻找一个比枢轴小的值来填此坑:
int tmp = arr[0]
| | 9 | 22 | 38 | 47 | 7 | 11 | 2 | 82 | 1 |
3. 寻找比枢轴小的值可以有几种方法,但是否都能行得通?我们来分析下
a. 如果先按从左到右的顺序进行寻找,经过一次排序后,序列如下:
| | 9 | 22 | 38 | 47 | 7 | 11 | 2 | 82 | 1 |
| 9 | | 22 | 38 | 47 | 7 | 11 | 2 | 82 | 1 |
| 9 | 7 | 22 | 38 | 47 | | 11 | 2 | 82 | 1 |
| 9 | 7 | 22 | 38 | 47 | 2 | 11 | | 82 | 1 |
| 9 | 7 | 22 | 38 | 47 | 2 | 11 | 1 | 82 |10 |
现在的情况是,比枢轴10小的记录确实已经排到了枢轴的左边,但你有办法将比10大的记录都排右边吗?
有吗? 好像没有吧...
如果,再把末尾的10挖出个坑来,让大的数来填,最后你
发现,大的记录确实到了枢轴的右边,但小的记录又分布枢轴两边...
所以无论是一开始从右向左或者是从左向边一口气的排序并不能解决我们的问题... Game over!
b.作为
人人敬仰的聪明小一休会坐下来静下心,迪迪哇哇迪迪哇哇三秒钟,脑洞大开,又有一方法:
我们轮流从头尾拿记录与枢轴进行对比进行交换,情况会如何?
| | 9 | 22 | 38 | 47 | 7 | 11 | 2 | 82 | 1 |
| 1 | 9 | 22 | 38 | 47 | 7 | 11 | 2 | 82 | |
| 1 | 9 | | 38 | 47 | 7 | 11 | 2 | 82 | 22|
| 1 | 9 | 2 | 38 | 47 | 7 | 11 | | 82 | 22|
| 1 | 9 | 2 | | 47 | 7 | 11 | 38| 82 | 22|
| 1 | 9 | 2 | 7 | 47 | | 11 | 38| 82 | 22|
| 1 | 9 | 2 | 7 | | 47| 11 | 38| 82 | 22|
| 1 | 9 | 2 | 7 |10 | 47| 11 | 38| 82 | 22|
经过轮流头尾记录与枢轴进行对比交换后,达到了我们的预期序列,开不开森呐?
将下来只是
递归的问题了,对枢轴左右两部分再次进行以上步骤,即可将此序列有序
The following code will tell us who they are!
class="java">public class QuickSort{
public static void quickSortMethodA(int arr[], int startPos, int endPos){
if(startPos >= endPos) return;
int tmp = arr[startPos];
int i = startPos;
int j = endPos;
while(i < j){
while(i < j && arr[j] > tmp){
j--;
}
if(i < j){
arr[i++] = arr[j];
}
while(i < j && arr[i] < tmp){
i++;
}
if(i < j){
arr[j--] = arr[i];
}
}
arr[i] = tmp;
quickSortMethodA(arr, startPos,i-1);
quickSortMethodA(arr, i+1, endPos);
}
}
对比
算法
上面我们用快排的思想写了一个quickSortMethodA()方法,这个快排到底有多快,没有参照物肯定是感受不到的,那么我们写一个起泡排序quickSortMethodB()方法。
public static void quickSortMethodB(int arr[]){
for (int i=0; i<arrayLength; i++){
for (int j=i; j<arrayLength;j++){
if(arr[i] > arr[j]){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
}
然后我们在QuickSort这个类下面写一个单元测试:
public class QuickSort{
private static int arrLength = 100000;
private static int[] arr = new int[arrLength];
public static void main(String[] args){
for(int i=0; i<arrLength; i++){
arr[i] = (int) Math.ceil(Math.rand()*arrLength);
}
//How long will it take us ?
long startTimeForA = System.currentTimeMillis();
quickSortMethodA(arr,0,arrLength);
System.out.println(System.currentTimeMillis()-startTimeForA);
System.out.println();
long startTimeForB = System.currentTimeMillis();
quickSortMethodB(arr);
System.out.println(System.currentTimeMillis()-startTimeForB);
}
}
然后运行下看下结果:
是不是很开森? 我们的快排对一个长度为10W的数组排序用了27毫秒,而起泡排序用了4407毫秒! 差距显然...
分析算法
通过对比,我们是不是应该寻根问底的了解下这两种算法的时间复杂度T(n) ?
不应该? 好吧.... 你可以撤了。Game over.
我们来分析下快排的时间复杂度问题...
对某个长度为n的序列进行排序所需时间T(n) =Tsort(n) + T(i-1) + T(n-i)
其中 Tsort(n) 是对n个记录进行一次快排所需要的时间,而T(i-1) 、T(n-i)分别表示对长度为[1,i-1]、[i+1,n]序列所花时间
对某长度为n的序列,进行排序所需要的时间Tsort(n) 由我们的quickSortMethodA()方法可知,与某一常量成正比,即Tsort(n)=cn
因序列中记录是随机分布,所以枢轴 i 为序列作[1,n]中任一记录的概率相同,刚快排所需时间平均值为:
=>
Tavg(n) = (n+1)/n(Tavg(n-1)) + (2n-1)c/n < (n+1)Tavg(1)/2 + 2(n+1)(1/2+1/3+...+1/n+1)c < (n+1)ln(n+1)
所以对于一个序列长度为n进行快排 时间复杂度大概为 Tn = O(nlogn)
面对于起泡排序,将每个数都要与后面的数进行对比,Tn = O(n*n) = O(n2)
引用出处:http://www.cnb
logs.com/jksmile/p/4398071.html
- 大小: 32.4 KB