java数据结构与算法之排序之快速排序

QuickSorting.java

package com.zhanglei.quicksorting;

import java.util.Arrays;

public class QuickSorting {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int[] arr= {-9,0,78,0,23,0,-567,70};
		quickSort(arr, 0, arr.length-1);
		System.out.println("After quickSort:"+Arrays.toString(arr));

	}
	
	public static void quickSort(int[] arr,int left,int right) {
		int l=left;
		int r=right;
		int pivot=arr[(left+right)/2];
		int temp;
		//while循环的目的是将比pivot小的值放置在其左边,大的在右边
		while(l<r) {
			//在pivot的左边一直找,一直找到大于等于它的才退出
			while(arr[l]<pivot) {
				l++;
			}
			//在pivot的右边一直找,一直找到小于等于它的才退出
			while(arr[r]>pivot) {
				r--;
			}
			//如果l>=r,说明此时pivot左边的值都小于它,右边的值都大于它
			if(l>=r) {
				break;
			}
			//交换
			temp=arr[l];
			arr[l]=arr[r];
			arr[r]=temp;
			//下面的是考虑到如果正好l和r对应的值至少有一个等于pivot时,这时就可以进行下一个元素的判断
			//试想如果没有下面的代码,当l和r对应的值都等于pivot时,程序将卡在while(l<r)循环中出不来
			//具体就是交换来交换去都是一样的数,l和r的值都无法改变
			//实际下面的两个条件只需要一个即可,后面有说明
			if(arr[l]==pivot) {
				r--;
			}
			if(arr[r]==pivot) {
				l++;
			}
			
		}
		//如果l==r,必须l++,r--,否则会出现栈溢出
		//经过debug可知栈溢出的原因是下面的向右递归或向左递归会无限调用
		//当l==r时,说明此时左右指针都指向了pivot对应的位置,此时就需要各自再进一步进入到各自的下一个子序列
		if(l==r) {
			l++;
			r--;
		}
		//向左递归
		if(left<r) {
			quickSort(arr,left,r);
		}
		//向右递归
		if(right>l) {
			quickSort(arr,l,right);
		}
		
	}

}

输出结果:

After quickSort:[-567, -9, 0, 0, 0, 23, 70, 78]

备注1:

  1. 快速排序可以从一个小问题出发,即对于一个给定的数组,按照某种策略选择其中的一个数作为pivot(中心点),如何将小于pivot的数放到其左边,大于pivot的数放到其右边,等于pivot的数左边或者右边都可以,具体看13。
  2. 最容易想到的是遍历插入,将小于pivot的数插入到pivot左边,大于pivot的数插入到pivot右边,但是这样的缺点是当进行数组的插入的时候会引起大量元素移动的问题,影响程序速度。
  3. 遍历交换则不需要进行插入,通过交换来移动元素效率更高
  4. 实施方案是设定两个首尾指针,两个指针往中间靠拢,当两个指针碰头时,任务完成
  5. while(l<r)就是意思当两个指针碰头时,任务完成
  6. while(arr[l]<pivot)表示指针l的左边都是小于pivot的
  7. while(arr[r]>pivot)表示指针r的右边都是大于pivot的
  8. 由6和7可知,当l==r时,两者共同指向的值一定是等于pivot,此时完成了任务,即pivot左边都是小于它的数,右边都是大于它的数
  9. 接下来的交换,当l指向的值大于等于pivot时,r指向的值小于等于pivot时,此时就交换两个数,此时l指向的值小于等于pivot,r指向的值大于等于pivot
  10. 接下来的判断的目的是考虑到特殊的情况:比如l指向的值和r指向值都等于pivot,如果此时不进行r–或者l++,那么此时while(l<r)就是一个死循环
  11. 实际情况下并不需要两个判断条件,只需要任选其中一个即可,我的理解是如果当前不满足已选择的唯一的判断条件,那么当下一次发生交换时肯定会满足已选择的唯一的判断条件,就能打破l和r都不变的这个僵局,打破死循环
  12. 至此小问题解决
  13. 现在的问题:当l==r时,两者都指向了等于pivot的数,那请问此时其他等于pivot的数是当前指针的左边还是右边,对此,我做了一个测试;发现如果使用的是判断条件if(arr[l]==pivot) ,那么就是右边,如果使用的是判断条件if(arr[r]==pivot),则就是左边
  14. 易误点:不要关心一开始的pivot对应的元素的位置,l==r时指针指向的数并不一定就是原始的pivot元素,但是一定是跟pivot值相等的元素
  15. 下面的代码最好是理解并背下来
        int[] arr=new int[] {1,2,0,4,9,2,9,5,10};
		int l=0;
		int r=arr.length-1;
		int pivot=arr[(l+r)/2];
		while(l<r) {
			while(arr[l]<pivot) {
				l++;
			}
			while(arr[r]>pivot) {
				r--;
			}
			if(l>=r) {
				break;
			}
			int temp=arr[l];
			arr[l]=arr[r];
			arr[r]=temp;
			//下面的判断条件可以两个都用,也可以只选其中一个
			if(arr[l]==pivot) {
				r--;
			}
			if(arr[r]==pivot) {
				l++;
			}
		}

备注2:

  1. 当l==r时,此时小问题完成,接下来就是l++,和r–,此时将原数组剔除掉共同指向的数(此时对于待排序的原数组而言,我们已经确定了一个元素的位置,剩下的就是确定其他元素的位置),剩下两个子数组,此时就可以对两个子数组进行相同的小问题操作,注意此时首尾两个指针需要相应的变化,
  2. 左边的子数组的首指针是父数组的原始首指针,而尾指针则是父数组的当前尾指针
  3. 右边的子数组的首指针是父数组的当前首指针,而尾指针则是父数组的原始尾指针
  4. 递归还是挺难的,关键是用代码实现很难

备注3:

QuickSortingSpeedMeasuring.java

package com.zhanglei.quicksorting;

public class QuickSortingSpeedMeasuring {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int maxsize=80000;
		int[] a=new int[maxsize];
		for(int i=0;i<a.length;i++) {
			a[i]=(int)(Math.random()*8000000);//output is in range of [0,8000000)
		}

		Long t1=System.currentTimeMillis();
		quickSort(a,0,a.length-1);//参数为引用类型,故可以形参变,实参也跟着变
		Long t2=System.currentTimeMillis();
		System.out.println("QuickSort cost:"+String.valueOf(t2-t1)+"ms");
	}
	
	public static void quickSort(int[] arr,int left,int right) {
		int l=left;
		int r=right;
		int pivot=arr[(left+right)/2];
		int temp;
		//while循环的目的是将比pivot小的值放置在其左边,大的在右边
		while(l<r) {
			//在pivot的左边一直找,一直找到大于等于它的才退出
			while(arr[l]<pivot) {
				l++;
			}
			//在pivot的右边一直找,一直找到小于等于它的才退出
			while(arr[r]>pivot) {
				r--;
			}
			//如果l>=r,说明此时pivot左边的值都小于它,右边的值都大于它
			if(l>=r) {
				break;
			}
			//交换
			temp=arr[l];
			arr[l]=arr[r];
			arr[r]=temp;
			//下面的是考虑到如果正好l和r对应的值至少有一个等于pivot时,这时就可以进行下一个元素的判断
			//试想如果没有下面的代码,当l和r对应的值都等于pivot时,程序将卡在while(l<r)循环中出不来
			//具体就是交换来交换去都是一样的数,l和r的值都无法改变
			if(arr[l]==pivot) {
				r--;
			}
//			if(arr[r]==pivot) {
//				l++;
//			}
			
		}
		//如果l==r,必须l++,r--,否则会出现栈溢出
		//经过debug可知栈溢出的原因是下面的向右递归或向左递归会无限调用
		//当l==r时,说明此时左右指针都指向了pivot对应的位置,此时就需要各自再进一步进入到各自的下一个子序列
		if(l==r) {
			l++;
			r--;
		}
		//向左递归
		if(left<r) {
			quickSort(arr,left,r);
		}
		//向右递归
		if(right>l) {
			quickSort(arr,l,right);
		}
		
	}

}

输出结果:

QuickSort cost:27ms

备注

快的离谱!名副其实的排!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值