快速排序

之前在走路时在脑海中默写冒泡排序,后来又想到了快速排序,快速排序也只是有一个概念,选取一个基准值,将序列种比这个

基准值小的数放在其左边,比基准值大的数放在其右边。但是想来想去某些细节的地方就是无法实现,今天就介绍一些快速排序

算法。

快速排序的思路为:

1.选取序列中的一个数作为基准值pivot,可以指定也可以随机选择

2.将待排序列中小于这个基准值的放到基准值左边大于基准值的放到其右边

3.重复1,2的步骤,直到序列的长度为1

思路很简单,但是很多人要完整的写出这个算法时就蒙了,根本原因就是对快速排序的细节了解的不够,很多人无法写出来就是

卡在了第二步了,或者说可以实现第二步,但是写出的的方法无法放到第三步进行递归。下面我们就来剖析快速排序算法:

首先我们来搭建一个框架

1.将两个数的交换代码写好

	public void swap(Object array[],int i,int j){
		Object temp = array[i];
		array[i]=array[j];
		array[j]=temp;
	}

2.将两个数的比较代码实现

class Cmp implements Comparator<Integer>{

	@Override
	public int compare(Integer o1, Integer o2) {
		if(o1<o2){
			return -1;
		}else if(o1>o2){
			return 1;
		}
		return 0;
	}	
}

3.重要的一步,也就是思路的第二步,定义好大小序列的划分方法,他返回的是划分好大小序列的后基准值在序列中的下标值

    public <E> int partition(E array[], int begin, int end,Comparator<E> cmp){
        return 0;
    }

4.实现递归调用函数,递归的退出条件是最后的序列长度为一,那么也就是在序列的begin下标小于end下标时,递归继续,否

递归退出。

	public <E> void sort(E array[],int begin,int end,Comparator<E>cmp){
		if(begin<end){
			int index = partition(array, begin, end, cmp);			
			sort(array, index+1, end, cmp);
			sort(array, begin, index-1, cmp);
		}	
	}

5.这样快速排序的框架便搭建起来了,下面最重要的便是如何实现大小序列的划分,我们根据以下步骤来实现

1.随机选择一个基准值pivot,保存这个基准值以及这个基准值的下标

2.从头开始遍历这个序列,与基准值进行比较,比基准值小或者等于的就与放到序列的首部,即与序列的首部元素进行交换,如

果下次再遇到比基准值小或者等于的,就将这个元素放在之前首部的后面,即与首部后面的元素进行交换,也就是,遇到比基准

值小的或者等于的就放在序列0,1,2....,直到将整个序列与基准值比较完毕。这个过程最重要的是在整个序列与基准值比较完毕后

是怎样记住基准值的下标的,也就是基准值被放到序列的哪个位置了。

3.1、2步完成后返回基准值的下标,然后进行分别对小于基准值的序列和大于基准值的序列进行递归。

明白了这三步,那么写起来大小序列的划分方法的思路就清晰了

	public <E> int partition(E array[],int begin,int end,Comparator<E> cmp){
		int index = begin + random.nextInt(end-begin+1);
		E pivot = array[index];
		swap(array, index, end);//这个地方很巧妙,将基准值放到序列末尾,这样便保存了基准值的下标
		
		//由于前面基准值的下标已经保存了,那么index就能够另作他用了,用来保存下一次要插入的元素的下标
		//由于i<end,所以避免了基准值与自身进行比较。
		for (int i =index=begin; i <end; i++) {
			//与基准值比较,如果小于等于则将其从队首开始依次放置,这个排列的序列我称之为小序列
			if(cmp.compare(array[i], pivot)<=0){
				swap(array,index,i);
				index++;//小于基准值的元素每排列一次,index自增,以便下一次遇到小于基准值的元素排列在这个小序列的后面。			
			}
		}
		//序列比较完毕后,所有小于基准值的元素都从队首依次排列了,那么只要将基准值插入这个排列的后面即可
		//这样便形成了所有小于基准值的元素都在基准值的左边,大于基准值的元素都在基准值的右边
		swap(array, index, end);//这个index便又变成了基准值的下标了
		
		return index;
	}

上面这个方法是维基百科上的一个写法,我自己为了加深理解又对其进行了改写:

	public <E> int partition2(E array[],int begin,int end,Comparator<E> cmp){
		int index = begin + random.nextInt(end-begin+1);//基准值下标
		E pivot = array[index];//保存基准值
		//排序序列下标,即当找到一个小于基准值的数,那么这个数就放在下标为pivotIndex的序列中
		//刚开始pivotIndex为队首的位置,然后pivotIndex自增以便
		//让下一个数放在前一个数的后面。
		int pivotIndex=begin;
		for (int i = begin; i <=end; i++) {
			//不让基准值与其自身进行比较,因为当遇到小于或者等于基准值的元素时,就必须对元素交换位置
			//这样就会导致pivotIndex值自增,那么下一次在次遇到比基准值小的元素那么就会被排在基准值
			//的右边,与我们原来的意愿相反。
			if(i!=index){
				if(cmp.compare(array[i], pivot)<=0){
					//当较小的元素需要与基准值进行交换位置时,记住交换后基准值的下标
					//以便下次再次遇到基准值时,能够避开与其自身的比较。
					if(pivotIndex==index){
						swap(array,pivotIndex,i);
						index =i;
					}else{
						swap(array,pivotIndex,i);
					}
					pivotIndex++;			
				}
			}
			//当循环结束时,也就是所有比基准值小的元素都排到了基准值的左边,这时将基准值插入到这个小序列的后面即可
			//这样便将所有比基准值小的元素排列到了基准值的左右,比他大的元素排列到了他的右边
			if(i==end){
				swap(array,pivotIndex,index);
			}
		}
		//当交换完毕后,pivotIndex的值即为基准值的下标,返回index或者pivotIndex都可以
		System.out.println(pivotIndex+" "+index);
		return pivotIndex;	
	}

方法一显然要比方法二高效,代码也要简洁一些,他重复利用了index这个值,而且保存基准值的下标也没有额外的花销,要实现方法一

主要是要理解他是怎样保存基准值的下标的,这一点刚开始我看了很久才理解。方法二没有重复利用index,而且额外定义了一个pivotIndex

用来保存小序列下一次要插入的位置,最后当整个序列遍历完毕后,所有比基准值小的元素都放到了基准值的左边,但是还无法保证所有比

基准值大的在基准值的右边,所以将基准值插入后小序列的后面,这样就都能保证了。

快速排序的根本策略为分治法,即将一个序列分成两个子序列,然后进行递归。快速排序不是一个稳定的排序方法,他最坏的情况下的时间

复杂度为O(n²),在平均状态下的复杂度为O(nlogn),但是最坏的情况并不常见,所以他比一般的复杂度为O(nlogn)的排序算法速度更快,而

且更容易在大部分的架构上实现出来。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值