先装疯卖傻一下:算法外功,其实就是实践。实践多了,便会有心得,心得所成,便是大神。
序言:最近公司不是特别忙,开始琢磨着把算法基础重新看看,毕竟自己做的事情几乎和算法扯不上关系,大学毕业1年半,该忘得也忘了。
言归正传,快速排序的Java实现,先贴代码在写心得。
一、代码篇
代码分为两个类,一个是父类AlgorithmBase,用来生成数据和输出数据。一个是子类QuickSort,是本篇的核心,用来实现快排。
AlgorithmBase.java代码如下:
package com.marsyoung.algorithm;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class AlgorithmBase {
List<Integer> disorderList;
public void initDisorderList(int n,int max){
disorderList=new ArrayList<Integer>();
for(int i=0;i<n;i++){
disorderList.add(new Random().nextInt(max));
}
}
public void printList(List<Integer> list){
for (Integer i : list) {
System.out.print(i + " ");
}
System.out.print("\n");
}
public static void main(String[] args) {
AlgorithmBase ab=new AlgorithmBase();
ab.initDisorderList(100, 100);
for(Integer i:ab.disorderList){
System.out.println(i);
}
}
}
QuickSort.java代码如下:
package com.marsyoung.algorithm;
import java.util.List;
public class QuickSort extends AlgorithmBase {
public int sortUnit(List<Integer> list, int startPoint, int endPoint) {
Integer pivot = list.get(startPoint);// 记录本次比较的中轴值,赋值给pivot
endPoint++;
while (startPoint < endPoint) {// 设置本次比较的结束条件,为循环过后开始指针和结束指针在同一个位置
// 1.从结束指针开始比较
//这个地方可以减是由于最外层的循环刚判断完startPoint<endPoint
endPoint--;
// 开始指针减1
while (startPoint < endPoint && list.get(endPoint) > pivot) {
endPoint--;// 移动结束指针
}
// 不满足上面条件时,设置开始指针所指位置的值为结束指针所指的值
list.set(startPoint, list.get(endPoint));
if(startPoint<endPoint){
startPoint++;
}
// 2.然后换转方向继续比较
while (startPoint < endPoint && list.get(startPoint) < pivot) {
startPoint++;
}
// 不满足上面条件时,设置结束指针的位置为开始指针所指的值。
list.set(endPoint, list.get(startPoint));
}
list.set(startPoint, pivot);
// 结束时,startPoint和endPoint应该指向同一个点。
return startPoint;
}
public void quickSort(List<Integer> list, int start, int end) {
if (start < end) {
int middlePoint = sortUnit(list, start, end);
// 2分思想,分别处理分成的两个list,middlePoint所在的值是不用比较的,这个可以叫做3分思想吧
// 递归处理
quickSort(list, start, middlePoint -1);
quickSort(list, middlePoint + 1, end);
}
}
public void quickSort(List<Integer> list) {
quickSort(list, 0, list.size()-1);
}
public static void main(String[] args) {
QuickSort qs = new QuickSort();
qs.initDisorderList(5, 5);
qs.printList(qs.disorderList);
qs.quickSort(qs.disorderList);
qs.printList(qs.disorderList);
}
}
二、心法篇
代码写出来,就像武功使出来,都是有依据的。对于算法代码,核心其实是算法本身的逻辑。
1.什么是快排?
快速排序(Quicksort)是对冒泡排序的一种改进。由C. A. R. Hoare在1962年提出。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。(来自百度百科)
2.核心的逻辑有些什么?
通过概念可以看出来,要分成两部分,这是分治思想;每次排序之后又形成新的子队列,直到子队列排完,这是要涉及到递归。
3.如何实现?
快排实现,感觉挺难得,因为快排不仅需要将数据拆分成两部分,而且拆分的过程是前后交替的,用程序语言描绘不是很轻松。
先来串数字:5 9 8 4 2 6 7
Step 1:我们先选开头个一个数字,然后从左到右依次比较,然后把比这个数字大的放右边,小的放左边。进行这个操作之后,我们得到的结果应该是:
4 2 5 9 8 6 7
那么下一步,我们对4 2 和 9 8 6 7 分别执行step 1得到结果应该是两个(2 4 空) 和( 8 6 7 9 空)。
再下一步,我们对2 , 空,8 6 7,空 这4个队列进行step 1.这时候我们发现,2和空队列是不需要比较的啊。那也就是说拆分成的队列如果长度<2就不需要拆分比较了。同时,我们队8 6 7这个队列进行拆分比较,结果应该是 (6 7 8 空)这个队列。
下一步中,还需要拆分的是6 7队列,拆分比较后,结果为(空 6 7),子队列长度小于2,可以结束了。
上面的逻辑可以描述如下图:
好吧,高潮要来了,如何让这个逻辑在计算机上实现呢?
有几个点,计算机需要会比较,然后可以把比较完的结果分别放入左边和右边,需要有地方可以存储左边和右边的数据。对左边和右边拆分好的数据比较完之后,最后把结果放在一起,就是拍好的队列。
好吧,试着用代码实现:
public List<Integer> quickSort2(List<Integer> list){
if(list==null||list.size()<2){
return list;
}else{
//来个拆分
int firstNumber=list.get(0);
List<Integer> leftList=new ArrayList<Integer>();
List<Integer> rightList=new ArrayList<Integer>();
for(int i=1;i<list.size();i++){
if(list.get(i)>firstNumber){
rightList.add(list.get(i));
}else{
leftList.add(list.get(i));
}
}
//对拆分的两个list进行递归排序
rightList=quickSort2(rightList);
leftList=quickSort2(leftList);
//拼成结果集
List<Integer> resultList=new ArrayList<Integer>();
for(Integer i:leftList){
resultList.add(i);
}
resultList.add(firstNumber);
for(Integer i:rightList){
resultList.add(i);
}
return resultList;
}
}
public static void main(String[] args) {
QuickSort qs = new QuickSort();
qs.initDisorderList(5, 5);
qs.printList(qs.disorderList);
// qs.quickSort(qs.disorderList);
qs.printList(qs.quickSort2(qs.disorderList));
}
结果完全没有问题,这个时候我们就要看看别人是怎么实现的了,和自己有什么区别么?我随便找了一篇文章来看:
http://blog.csdn.net/wangkuifeng0118/article/details/7286332 (先看个10分钟,然后实践20分钟。)
~~~30分钟后~~~
我在代码中,没有所谓的交换方向比较,只有一个递归和拆分的思想。而他人的实现,也让我有陌生的一种感觉。为什么要一会儿从前比较,一会儿从后比较呢?换几篇文章看看,也是同样的实现逻辑。这个时候,一个疑问挂在心头,难道全世界的coder都脑袋缺氧,非要绕弯子实现么,明明我的方法思路很清晰啊。
经过0.0001s的思考,加上大学时候C语言实现的模糊印象,我猜想原因是,在过去那个物质贫乏的年代,在那个内存128M的时代,在那个比尔盖茨的时代,他们的计算机效率不高,需要尽可能的节省空间。而现在,我们不怕消耗,尤其是在java里,空间用完了会自动回收。
但是,程序还是要尽可能的保证可靠的,如果数列足够长,使用这种方法,一定会很早的内存溢出。亲们,java不就常常溢出么?在循环或者地柜中不停的生成对象,会导致这种现象的发生。这区别也许也是C的面相过程,和java面相对象的一个体现吧。
所以,为了超级计算机发挥更好的效率,不会内存溢出,还是应该交替比较的,这样不用生成新对象。如代码篇贴出来的代码。
结语,也许有一天我已经忘记算法,或许本文能告诉我,曾近思考过:为什么快速排序要一会儿从右比起,一会儿从左比起。