java快速排序的两种思想与代码实现

算法描述(上)

  在数据结构(严蔚敏版)是这样描述快速排序的:通过一趟排序使得数据分成两部分,其中一部分均比另外一部分小,接着就可以对这两部分继续进行排序,以达到整个序列有序。
  其排序整体规则为:任意选取一个数,不过通常选取序列中第一个数作为枢轴(又可称为key或支点),将所有比key小的数放在它的位置之前,所有比key大的数放在它之后,划分为两个子序列,这样就完成了一趟快速排序。接下来需要将两个子序列的第一个数分别作为它们新的key值,重复之前的排序操作,直至整个序列有序时算法结束。
  那么如何实现所有比key小的数放在它的位置之前,所有比key大的数放在它之后呢?
  需求为从小到大排列。另 i 指向第一个数并作为key,另 j 指向最后一个数。先从 j 开始 ,j 向左走直至找见一个比key小的数,若找见这样的数 j 就停下来并交换 i 和 j 指向的数字。交换完毕后,i向右走,若找到比key大的数,i 停下来,交换 i 和 j 指向的数字。
  注意每次 i 和 j 移动时都要查看 i 和 j 是否碰头,如果碰头,这一轮快速排序结束,开始进行下一轮
  下图是第一趟快速排序过程:
第一轮快速排序
  比如对于上图中,另 i 指向49且另key = 49,j 指向27。27比49小,所以 j 就不向左走了,而是交换 i 和 j 指向的值,接着 i 向右走,发现65比49大,i 停下来与 j 互换值。接下来在第三行中,j 向左走发现13比49小,交换13和49,i 向右走发现97比49大,交换97和49,j 接着向左走,最后 i 和 j 碰头了,第一轮结束。
  注意上面只是进行了第一趟排序,第一趟排序结果为27 38 13 49 76 97 65
  接下来需要对key即49左边的序列和49右边的序列进行同样的处理,左边的序列需要另 i 指向27并作为新的key,j 指向13;右边的序列需要 i 指向76并作为新的key,j 指向末尾65。需要递归地进行这样的操作直至有序为止。
  如果使用伪代码,第一趟排序的算法可以这样描述

//条件这里也可以写成i!=j
while(i<j){
	while( i < j && L[j].value > key){
		j--;
	}
	swap(L[i].value,L[j].value);
	while( i < j && L[i].value < key){
		i++;
	}
	swap(L[i].value,L[j].value);
}

接下来只要使用递归就可以完整实现算法,不过我们先来看算法的改进

算法描述(下)算法的进一步改进

  事实上,在 i 和 j与key值比较并移动的过程中,只有当 i 和 j 碰头时 (此时 i== j ) ,我们才需要将key交换到 i和 j 碰头的那个位置,而不需要反复交换key的位置。
  具体实现还是让 j 先开始走,找到比key小的数,j 就停下来,接着从 i开始走,找到比key大的数,i 就停下来。接着交换 i 和 j 的值。交换完毕后,j 还是继续向左走,重复该过程直至 i 和 j 碰头。
在这里插入图片描述
  在上图中第三行,因为 i 和 j 碰头了,所以要将key指向的值49和 i(j)指向的值13互换。
  可以看到改进后的算法速度更快,而且同样实现了key左边的数比key小,key右边的数比key大的目的。
  注意上图中仅仅是第一轮快速排序,如果想要整个序列有序,需要借助递归及分治的思想。用同样的方式分别处理key左边和key右边的数。

java快速排序算法实现

import java.util.Arrays;
 
public class Test {
	public static void main(String[] args) {
		int[] arr = {1,2,3,4,5};
		System.out.println(Arrays.toString(arr));
		quickSort(arr,0,arr.length-1);
		System.out.println(Arrays.toString(arr));
	}
	
	public static void quickSort(int[] arr,int left,int right) {
		if(left>right) {
			/*
			*这里解释一下:例如只有一个数的排序序列,此时i==key==j==1,
			*当对key左边的序列调用quickSort(arr, left, i-1);时 i>j,即表示key的左边没有元素,应当返回;
			*对右边的序列调用递归quickSort(arr, i+1, right);时 i>j,即表示key右边没有元素,应当返回;
			*即只要排序序列中key的左边或右边没有元素时,应当返回
			*如果看不懂这里的解释可以先往下看,最后再看这里。
			*/
			return;
		}
		//key存放的就是基准数,注意这里的key并不是一成不变的,指的是每个序列中的第一个元素作为Key
		int key = arr[left];
		int i = left;
		int j = right;
		while(i!=j) {
			//注意这里别忘了i<j,当j向左走时,可能会与i碰头,这时应提前结束循环
			//如果想改成从大到小排,只需改动下面2处,arr[j]<=key以及arr[i]>=key
			//只要j指向的值比Key小,j 就不再做--操作
			while(arr[j]>=key && i<j) {
				j--;
			}
			//只要i指向的值比Key大,i 就停下来
			while(arr[i]<=key && i<j) {
				i++;
			}
			/*
			 * 这里也别忘了判断条件,当i和j没有相遇即i<j时,应交换它们对应的值
			 * 直到交换完毕后i和j再接着走。若是已经碰头,i和j的值交换已经没有意义,
			 * 而是需要退出循环,与基准数进行交换
			 */
			if(i<j) {
				int t = arr[i];
				arr[i] = arr[j];
				arr[j] = t;
			}
		}		
		/*思考下面的i能换成j吗?答案是可以的。
		*由于在循环内部的条件i<j以及外部条件i!=j,所以当退出while循环后,
		*此时的i必定和j是碰头的,即i==j
		*/
		arr[left] = arr[i];
		arr[i] = key;
		quickSort(arr, left, i-1);//处理左边的数
		quickSort(arr, i+1, right);//处理右边的数
		return;
	}
}

其他

  1. 在算法中,我们都是先让 j 开始走,那么可以先让 i 开始走吗?答案是可以的,只需将key定为每个序列中的最后的数字即可。
  2. 快速排序时间复杂度O(nlogn)
  3. 快速排序稳定性:不稳定
    什么叫稳定性?例如对于用户录入数据39 38 38,后面两个的关键字都是38,为了区分它们,人为给最后一个数上面加上一横(这也是为什么有些快速排序书上的数字要加一横的原因)
    在这里插入图片描述
    若经过排序,这些关键字相同的数字的相对顺序不变,就称这个算法是稳定的。例如只要排序后38还是相对 38’ 在前(不一定紧挨),算法就是稳定的。
    排序过程为:因为 j 指向的 38’ 比key小,j停下来, i 开始向右走,直至i 和 j 碰头,交换key和 i 的值 39 和 38’。再对39左递归,由于两个数关键字相同,无需改变顺序,最终结果如下:
    在这里插入图片描述
    显然 38’ 和 38 的相对位置发生了改变
  • 21
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值