算法外功修炼之一 快速排序的Java实现

先装疯卖傻一下:算法外功,其实就是实践。实践多了,便会有心得,心得所成,便是大神。

序言:最近公司不是特别忙,开始琢磨着把算法基础重新看看,毕竟自己做的事情几乎和算法扯不上关系,大学毕业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面相对象的一个体现吧。

所以,为了超级计算机发挥更好的效率,不会内存溢出,还是应该交替比较的,这样不用生成新对象。如代码篇贴出来的代码。


结语,也许有一天我已经忘记算法,或许本文能告诉我,曾近思考过:为什么快速排序要一会儿从右比起,一会儿从左比起。















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值