快速排序算法(挖坑法+左右指针法+快慢指针法)

挖坑法

挖坑法,可以理解成拆东墙补西墙。这里以数组[4,1,7,6,9,2,8,0,3,5]为例子讲解。以数据第一个元素作为枢轴(也可以是最后一个,看你喜好)。
这里我们先把作为枢轴的4抠出来单独放置,此时数组变成了
[口,1,7,6,9,2,8,0,3,5]
这时我们要从数组尾部开始往前找,找第一个比4小的数字,5比4大,j - -,这时3比4小,把3放在坑里。因为把3抠出来补坑里了,因此3原来所在的位置又变成了一个坑。
[3,1,7,6,9,2,8,0,口,5]
这时候我们要从数组头部开始往后找,找第一个比4大的数字,1比4小,i + +,这时7比4大,把7放在坑里。这时,7原来的位置又变成了坑。
[3,1,口,6,9,2,8,0,7,5]
接着从数组右边开始找比4小的数,0比4小,把0抠出来放在坑里。0原来的位置又变成了坑。
[3,1,0,6,9,2,8,口,7,5]
接着从数组左边开始找比4大的数字,6比4大,把6抠出来放在坑里。6原来的位置又变成了一个坑。
[3,1,0,口,9,2,8,6,7,5]
接着从右边开始找比4小的数字,8不比4小,j - -,这时2比4小,把2抠出来放在坑里。2原来的位置变成了坑。
[3,1,0,2,9,口,8,6,7,5]
接着从数组左边找比4大的数字,9比4大,把9放进坑里。9原来的位置变成了坑。
[3,1,0,2,口,9,8,6,7,5]
此时i==j,即游标重叠了。这时,我们将一开始取出来的枢轴4放进最后这个坑里。这就完成了第一趟快排。
[3,1,0,2,4,9,8,6,7,5]
你会发现枢轴4左边的数字都比4小,4右边的数字都比4大。
接下来[3,1,0,2]和[9,8,6,7,5]分别都采用这样的挖坑法进行排序。一直到数组只包含一个数字为止,便完成了最终的排序。

public class Test {
    static void quicksort(int s[],int left,int right){
           if(left<right){
               int temp=s[left],i=left,j=right;   
               while(i<j) {
            	 //寻找右边第一个小于基准值的下标
                   while(s[j]>=temp&&i<j)j--;
                   if(i<j) {s[i]=s[j];i++;}
                 //寻找左边第一个大于基准值的下标
                   while(s[i]<=temp&&i<j)i++;
                   if(i<j) {s[j]=s[i];j--;}         
               }
                      
               s[i]=temp;//当游标指针i和j重叠时将枢轴填入坑
               System.out.println("-----枢轴:"+temp);
               quicksort(s,left,i-1);      //递归左边部分数组
               quicksort(s,i+1,right);     //递归右边部分数组
           }
       }

    public static void main(String[] args) {
        int []a= {4,1,7,6,9,2,8,0,3,5};
        quicksort(a,0,a.length-1);
        for(int i:a) {
            System.out.print(i+" ");
        }
    }
}
	         

输出:
-----枢轴:4
-----枢轴:3
-----枢轴:2
-----枢轴:0
-----枢轴:9
-----枢轴:5
-----枢轴:8
-----枢轴:7
0 1 2 3 4 5 6 7 8 9

左右指针法

思想:

1、选取一个关键字(key)作为枢轴,一般取整组记录的第一个数/最后一个为枢轴。
2、设置两个变量left = 0;right = N - 1;
3、从left一直向后走,直到找到一个大于key的值,right从后至前,直至找到一个小于key的值,然后交换这两个数。
4、重复第三步,一直往后找,直到left和right相遇,这时将key放置left的位置即可。

这里还是以[4,1,7,6,9,2,8,0,3,5]为例做解释。这里以第一个元素4作为枢轴。right向前找比4小的数,此时3比4小,left向后找比4大的数,此时7比4大,交换3和7;
[4,1,3,6,9,2,8,0,7,5]
right向前找比4小的数,此时0比4小,left向后找比4大的数,此时6比4大,交换0和6;
[4,1,3,0,9,2,8,6,7,5]
right向前找比4小的数,此时2比4小,left向后找比4大的数,此时9比4大,交换2和9;
[4,1,3,0,2,9,8,6,7,5]
这时将枢轴4和array[left]的值进行一次交换,就完成了第一趟快排。
[1,3,0,2,4,9,8,6,7,5]

快慢指针法

思想:

针对取最后一个元素作为枢轴的情况:
1、定义变量fast指向序列的开头,定义变量slow的前一个位置。
2、当array[fast] < key时,fast和slow同时往后走,如果array[fast]>key,fast往后走,slow留在大于key的数值前一个位置。
3、当array[fast]再次 < key时,交换array[fast]和array[slow]。

说白了就是,在没找到大于key值前,slow永远紧跟fast,遇到大于枢轴的值,两者之间就会拉开差距,中间差的肯定是连续的大于key的值,当再次遇到小于key的值时,交换两个下标对应的值就好了。

这里还是以[4,1,7,6,9,2,8,0,3,5]为例做解释。这里取5作为枢轴。
从数组左边开始找比5大的数字,4不比5大,fast++,slow++,1不比5大,fast++,slow++,此时fast指向7,slow指向1。这时,7比5大,fast++,slow不动,此时,fast指向的6仍比5大,fast++,slow仍不动,此时fast指向的9比5大,fast++,slow不动。此时fast指向的2比5小,则这时候slow++,指向7。此时,交换fast指向的2和slow指向的7。
[4,1,2,6,9,7,8,0,3,5]
接着,往后遍历,fast++,fast指向8,比5大,slow不动,fast++,fast指向0,0比5小,此时slow++,slow指向6。这时候,交换fast指向的0和slow指向的6。
[4,1,2,0,9,7,8,6,3,5]
接着,往后遍历,fast++,fast指向3,比5小,slow++,slow指向9,此时,将fast指向的3和slow指向的9交换。
[4,1,2,0,3,7,8,6,9,5]
最后,将枢轴5赋值给array[slow]。
[4,1,2,0,3,5,7,8,6,9]

###最好情况:最好的情况是枢轴选取得当,每次都能均匀的划分序列。时间复杂度O(nlogn)

###最坏情况:枢轴为最大或者最小数字,那么所有数都划分到一个序列去了。时间复杂度为O(n^2)

###比较和移动次数最少时间复杂度表示为O(nlogn);
###比较和移动次数最多的时间复杂度表示为O(n^2);
###使用的辅助存储空间最少为logn,最多为n^2;是不稳定的排序;

经典快排

思想:以数组最后一个数x作为枢轴(它一开始不参与遍历,从数组的第一位到枢轴的前一位参与遍历),从数组最左边的数字开始遍历,把小于等于x的数放在数组的左边,大于x的数放在数组的右边。这样的排序算法,每一次排序只搞定一个位置上的数在正确的位置上(划分区域的枢轴),确保其左边的数都小于等于它,右边的数都大于它。

缺点:选择的枢轴和数据的原始规模有关系,如果选择的枢轴正好是整个数组的中位数,那么这样小于枢轴的部分和大于枢轴的部分规模就相当了,这样处理问题的子规模就从原来的O(N)变成了O(logN)。如果选择的枢轴是整个数组的边界值(最小值或者最大值),导致处理问题的子规模变成不一样大,那么处理问题的规模就会变成O(N2)。最好情况下,经典快排的算法复杂度是O(NlogN),最差情况下,经典快排的算法复杂度是O(N2)。空间复杂度为O(1)。

public class Test{
	public static void quickSort(int [] arr,int L,int R) {
		if(L<R) {
			int[] p=patition(arr,L,R);
			quickSort(arr,L,p[0]-1);
			quickSort(arr,p[1]+1,R);
		}
	}
	public static int[] patition(int[] arr,int L,int R) {
		int less=L-1;
		int more=R;
		int cur=L;
		while(cur<more) {
			if(arr[cur]<arr[R]) {
				swap(arr,++less,cur++);
			}else if(arr[cur]>arr[R]) {
				swap(arr,--more,cur);
			}else {
				cur++;
			}
		}
		swap(arr,more,R);//当遍历结束之后(指针相遇),将枢轴x与大于x的第一位上的数(数组下标为more的数)进行交换
		return new int[] {less+1,more};//less+1和more为数组中等于x的数在排序后的下标的左右临界位置
	}
	public static void swap(int[] arr,int i,int j) {
		int temp=arr[i];
		arr[i]=arr[j];	
		arr[j]=temp;
	}
	public static void main(String[] args) {
		int[] arr= {6,3,4,3,1,3,8,5};
		quickSort(arr,0,arr.length-1);
		for(int i:arr) {
			System.out.print(i+" ");
		}
	}
}

输出:1 3 3 3 4 5 6 8

随机快排(最重要!!!最常用!!!)

思路:随机选择数组中的一个数x和数组最后一个数进行交换,然后用随机选择的数x来作为枢轴进行划分。这样的话,就不能说会轻易的找到最差的情况了,因为每一次排序选择的枢轴都是随机的,可能选择到最好情况的枢轴(中位数),也有可能选择到最差情况的枢轴(边界值),这样的话,它的复杂度就是一个概率事件了。所以在估计的时候就不能说它有一个最差情况了,只能用长期期望的方式来算出它的时间复杂度。这里有计算公式证明长期期望的时间复杂度是O(NlogN)。空间复杂度为O(logN)。空间复杂度浪费在了划分点p的位置上,每一次排序都要记下划分点位置,方便找到递归子过程的边界位置。

public class Test{
	public static void quickSort(int [] arr,int L,int R) {
		if(L<R) {
			swap(arr,L+(int)(Math.random()*(R-L+1)),R);//随机选择数组中的一个数和数组最后一个数进行交换
			int[] p=patition(arr,L,R);
			quickSort(arr,L,p[0]-1);
			quickSort(arr,p[1]+1,R);
		}
	}
	public static int[] patition(int[] arr,int L,int R) {
		int less=L-1;
		int more=R;
		int cur=L;
		while(cur<more) {
			if(arr[cur]<arr[R]) {
				swap(arr,++less,cur++);
			}else if(arr[cur]>arr[R]) {
				swap(arr,--more,cur);
			}else {
				cur++;
			}
		}
		swap(arr,more,R);//当遍历结束之后(指针相遇),将枢轴x与大于x的第一位上的数(数组下标为more的数)进行交换
		return new int[] {less+1,more};//less+1和more为数组中等于x的数在排序后的下标的左右临界位置
	}
	public static void swap(int[] arr,int i,int j) {
		int temp=arr[i];
		arr[i]=arr[j];	
		arr[j]=temp;
	}
	public static void main(String[] args) {
		int[] arr= {6,3,4,3,1,3,8,5};
		quickSort(arr,0,arr.length-1);
		for(int i:arr) {
			System.out.print(i+" ");
		}
	}
}

输出:1 3 3 3 4 5 6 8

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值