快速排序

一、基本思路

给定一个长度为 N 的数组,使用快速排序由小到大进行排序:选择数组中的某个元素作为“标尺”,把这个数组分成三部分。把小于该元素的数全部放到它的左边,大于它的数则放到他的右边,等于它的数放在中间,然后再对左右两边的元素进行同样的操作。
下面用一组图片来解释一下:
初始状态下,小于区域和大于区域都为空。假设我们选取数组的最后一个元素作为“标尺”
在这里插入图片描述
我们把数组的第一个元素(4)和标尺(3)进行比较4 > 3把 4 和大于区域的前一个元素交换,大于区域长度加一,下标不动完成这个步骤后的情况如下:
在这里插入图片描述
然后,再把当前元素(3)和标尺(3)进行比较,相等,把下标右移一位,完成后情况如下:
在这里插入图片描述
再比较下标对应的元素(1)和标尺(3),1 < 3,把小于区域的下一个元素和当前元素互换,小于区域的长度加一,下标右移一位完成后情况如下:
在这里插入图片描述
同样,再把(6)和(3)进行比较,6 > 3,把大于区域的前一个元素和当前元素(6)交换,大于区域长度加一,下标不动,完成后情况如下:

在这里插入图片描述
再把(2)和(3)比较,2 < 3,把当前元素和小于区域下一个元素交换,下标右移一位完成后情况如下:
在这里插入图片描述
最后,把(5)和(3)比较,5 > 3,把大于区域的前一个元素和当前元素(5)交换,大于区域长度加一,下标不动完成后情况如下:
在这里插入图片描述
此时,当前元素的下标和大于区域的边界已经重合了,因此本轮排序结束
最后,我们发现整个数组被标尺(3)分成了两个部分,我们只要再把大于区域和小于区域分别进行这样的操作,最后就能得到一个有序的数组了。

二、代码实现

实现代码做了一些小小的优化,建议看的时候可以自己写一个数组,按着代码的步骤一步步来,这样就可以明白有的地方为什么是那样写的了。

public static void quickSort(int[] arr, int l, int r) {
    if(arr == null || arr.length < 2) return;
    if(l < r) {
    	swap(arr, l + (int) (Math.random() * (r - l + 1)), r);   //  随机快排
        int[] p = partition(arr, l, r);
        quickSort(arr, l, p[0]);
        quickSort(arr, p[1], r);
    }
}
public static int[] partition(int[] arr, int l, int r) {
	int less = l - 1;
	int more = r;
	while(l < more) {
		if(arr[l] < arr[r]) {
			swap(arr, ++less, l++);
		} else if(arr[l] > arr[r]) {
			swap(arr, --more, l);
		} else l++;
	}
	swap(arr, more, r);
	return new int[] { less, more + 1 };
}

三、时间复杂度分析及经典快排存在的问题。

1.时间复杂度分析

上述排序方法,我们称之为经典快排。假如我们每次通过 partition 这一过程都能把原数组分成长度基本相等的大于区域和小于区域,那么算法的时间复杂度应为:O(N long2N)。

2.可能存在的问题

假设要排序的数组是一个已经按照升序排列的数组(例如【1, 2, 3, 4, 5】),我们选取数组的最后一个元素(5)作为标尺,那么进行 partition 后,我们把数组分成大于区域和小于区域,大于区域的长度为0,小于区域的长度为 N-1,然后又要对长度为 N-1 的数组进行 partition,此时,算法的时间复杂度就变成了O(N2)。显然这是一个特别差的事件复杂度。

3.如何优化?---- 随机快排

分析问题出现的原因,为什么会导致时间复杂度变高呢?因为数组不再是均等分了。那为什么会这样呢?因为标尺的选择不够好,我们选择了一个最大的数作为标尺。因此,我们在选择标尺时,可以在要进行 partition 的数组中,随机选择一个数作为标尺。
尽管这样,我们还是有可能选到最大的数作为标尺,因此,我们不能保证快速排序的时间复杂的一定是O(N lon2N),只能说在一般情况下是这样的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值