文章目录
- 算法介绍 :
- 1. 随机快排解析
- 2. 荷兰国旗问题
- 3. 随机快排优化
- 4. 总结随机快排
算法介绍 :
随机快速排序和传统的快速排序的逻辑本质是一致的,都是找到一个值作为划分的中间位置,左边数值均小于该数值,右边数值均大于该数值,但是与传统的快排又不一致的是,我们的这个位置是通过随机获得的,对于随机行为的时间复杂度不能简单的按照最坏的情况解读
1. 随机快排解析
下面是我们随机快排的代码实现
public class Sort {public static void quickSort(int[] nums) {if (nums == null || nums.length <= 1) {return;}quickSort(nums, 0, nums.length - 1);}//下面是经典版本随机快排的函数主体private static void quickSort(int[] nums, int left, int right) {if (left >= right) {return;}//在left -- right范围上随机出来一个数字int randn = nums[left + (int) (Math.random() * (right - left + 1))];int boundary = partiton(nums, left, right, randn);quickSort(nums, left, boundary - 1);quickSort(nums, boundary + 1, right);}//经典快排的partition过程private static int partiton(int[] nums, int left, int right, int randn) {//逐个遍历的下标int i = left;//用来区别 <= randn的边界位置(已经越界了)int j = left;//记录找到的x的位置(用于交换)int randIndex = left;while (i <= right) {if (nums[i] <= randn) {swap(nums, i, j);if (nums[j] == randn) {randIndex = j;}i++;j++;} else {i++;}}swap(nums, j - 1, randIndex);return j - 1;}private static void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
}
下面主要解析一下上述代码的逻辑是什么
首先我们通过
(int)(Math.random()*(right - left + 1)) + left
这段代码可以随机产生一个left – right的下标值
然后我们就拿到该位置的随机数作为划分
之后通过partiton过程来进行快排的逻辑,其中定义了两个指针,一个遍历数组下标,一个进行边界位置的扩大,当遍历到随机数的时候我们就记录下来该随机数的下标,然后通过左右区间调用递归过程完成排序, 但是这个快排的逻辑在leetcode上是跑不过全部的,因为你一轮只能排出来一个数据
2. 荷兰国旗问题
荷兰国旗问题可以为我们的接下来的排序优化打下基础, 上面的排序说了我们创造了一个左边界线, 那为什么不创建一个有边界线呢 ? 荷兰国旗就是这样解决的,但是要注意的是,在移动右边界线的时候,遍历的下标值不可以移动,在移动左边界线却可以, 因为左边已经遍历过一轮了, 可以确定交换过来的元素属性,但是右边是不确定的, 代码实现如下
class Solution {public void sortColors(int[] nums) {int first = 0;int last = nums.length - 1;int i = 0;while(i <= last){if(nums[i] == 0){swap(nums,i++,first++);}else if(nums[i] == 1){i++;}else{swap(nums,i,last--);}}}public void swap(int[] nums,int i,int j){int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
}
3. 随机快排优化
有了上面的荷兰国旗的基础,我们的快排优化也是这样做的,逻辑也是定出来两个边界,然后逐个进行移动…
代码实现如下
/*** 下面是根据荷兰国旗问题的改进版本(分为三个区间, 一次搞定一批)* 下面这个两个属性是左右边界的越界位置*/
public class Sort{public int first = 0;public int last = 0;public void quickSortImprove(int[] nums) {if (nums == null || nums.length <= 1) {return;}quickSortImprove(nums, 0, nums.length - 1);}public void quickSortImprove(int[] nums,int left,int right){if (left >= right) {return;}int randn = nums[left + (int) (Math.random() * (right - left + 1))];partitonImprove(nums,left,right,randn);int fir = first;int la = last;quickSortImprove(nums,left,fir - 1);quickSortImprove(nums,la + 1, right);}private void partitonImprove(int[] nums,int left,int right,int randn){first = left;last = right;int i = left;while(i <= last){if(nums[i] < randn){swap(nums,i++,first++);}else if(nums[i] == randn){i++;}else{swap(nums,i,last--);}}}private static void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}
}
4. 总结随机快排
随机快速排序算法
- 随机快速排序的经典版本就是下面这样,随机出一个位置作为分界点,然后左右递归…
- 我们下面的快排代码里面的 j 是作为左/右边界的越界位置存在 --> 随着下标指针的遍历(边界在不断地扩大)
- Math.random方法的常数时间复杂度属于随机行为的时间复杂度
-
- 荷兰国旗问题其实是随机快速排序的一种优化方式
- 因为原本我们随即快排一次只能搞定一个数字, 优化之后我们一次可以搞定一批 x == randNum 的数据
-
- 随机快速排序的时间复杂度与空间复杂度的估计
- 关于随机行为的时间复杂度的估计不可以采用最坏的情况估计(因为最坏的情况就是无穷大),应该采用期望的方式估计
- 最好情况(正好是中点位置) --> 时间复杂度 O(N*LogN)(Master公式) 空间复杂度O(LogN)(堆栈中树的高度)
- 最坏情况(边界位置) --> 时间复杂度 O(N^2) 空间复杂度O(N)(堆栈中树的高度)
- 经过大批数学家的证明(因为最终的时间复杂度应该算的是期望值(包含随机行为作为核心步骤))
- –> 结论就是 时间复杂度 O(N*LogN) 空间复杂度O(LogN) --> 详见算法导论(有详细证明)
-
- 所以 : 随机快排要比普通的快排更加优秀