快速排序的思想是通过一次排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归方式实现,以此达到整个数据变成有序序列。

在实现快速排序之前,先了解:

  • Partition
  • 荷兰国旗问题

这两个问题,将有助于我们实现快速排序算法。

Partition

Partition的过程:给定一个数组arr,和一个整数num。把小于等于num的数放在数组的左边,大于num的数放在数组的右边。

比如数组int[] arr = {18, 15, 13, 17, 6, 20, 15, 9};

给定一个数15,小于等于15的数放在数组的左边,大于15的数放在数组的右边。

分析Partition过程:

分支1:arr[i] <= 15,arr[i]和小于等于区的右边一个元素交换,同时小于等于区向右扩展1个i++

分支2:arr[i] > 15,不做操作,只是i++

初始化i、小于等于区:

  • i初始值为0;
  • 小于等于区右边界为-1;

数组初始状态:

i=0,比较arr[0]和15,arr[0] > 15,走分支2,没有操作,只是i++

i=1,比较arr[1]和15,arr[1] == 15,走分支1,将arr[1]和arr[0](就是arr[smaller+1])交换,小于等于区的右边界右移,同时i++:

i=2,比较arr[2]和15,arr[1] < 15,走分支1,将arr[2]和arr[1](就是arr[smaller+1])交换,小于等于区的右边界右移,同时i++:

i=3,比较arr[3]和15,arr[2] > 15,走分支2,不做操作,只是i++

i=4,比较arr[4]和15,arr[4] < 15,走分支1,将arr[4]和arr[2](就是arr[smaller+1])交换,小于等于区的右边界右移,同时i++:

i=5,比较arr[5]和15,arr[5] > 15,走分支2,不做操作,只是i++

i=6,比较arr[6]和15,arr[6] == 15,走分支1,将arr[6]和arr[3](就是arr[smaller+1])交换,小于区的右边界右移,同时i++:

i=7,比较arr[7]和15,arr[5] < 15,走分支1,将arr[7]和arr[4](就是arr[bigger-1])交换,小于区的右边界右移,同时i++:

此时,i越界,所有小于等于给定数15的元素都在数组的左边,大于15的元素都在数组的右边,完成了划分。

代码实现:

/*** Partition:给数组指定范围进行分区,小于等于arr[R]的放左边,大于arr[R]的放右边* @param arr 数组* @param L 划分数组范围的左边界* @param R 划分数组范围的右边界* @return int 返回小于等于区域的右边界**/
public static int partition(int[] arr, int L, int R) {if (L > R) {return -1;}if (L == R) {return L;}//定义小于等于区的右边界int smallAndEq = L - 1;int index = L;while (index < R) {if (arr[index] <= arr[R]) {swap(arr, index, ++smallAndEq);}index++;}swap(arr, ++smallAndEq, R);return smallAndEq;
}

荷兰国旗问题

荷兰国旗问题:给定一个数组arr,和一个整数num。把小于num的数放在数组的左边,等于num的数放在中间,大于num的数放在数组的右边。

类比于荷兰国旗中的红白蓝三个区域,因此这种数组划分叫荷兰国旗问题。

解决这类问题,分析其过程如下:

给定一个数15,小于等于15的数放在数组的左边,大于15的数放在数组的右边

分支1:arr[i] < 15,arr[i]和小于区的右边一个元素交换,同时小于区向右扩展1个i++

分支2:arr[i] == 15,i++

分支3:arr[i] > 15,arr[i]和大于区的左边一个元素交换,同时大于区向左扩展1个i不变(因为此时arr[i]还未和交换过来的数据进行比较)

初始化i、小于区和大于区:

  • i初始值为0;
  • 小于区右边界初始值为-1;
  • 大于区左边界初始值为arr.length=8

初始状态:

i=0,比较arr[0]和15,arr[0] > 15,走分支3,将arr[0]和arr[7](arr[bigger-1])交换,大于区的左边界左移:

i仍然=0,比较arr[0]和15,arr[0] < 15,走分支1,将arr[0]和arr[0](就是arr[smaller+1])交换(等于不动),小于等于区的右边界右移,同时i++:

i=1,比较arr[1]和15,arr[1]==15,走分支2,不做操作,只是i++:

i=2,比较arr[2]和15,arr[2] < 15,走分支1,将arr[2]和arr[1](就是arr[smaller+1])交换,小于等于区的右边界右移,同时i++

i=3,比较arr[3]和15,arr[3] > 15,走分支3,将arr[3]和arr[6](就是arr[bigger-1])交换,大于区的左边界左移:

i仍然=3,比较arr[3]和15,arr[3] == 15,走分支2i++

i=4,比较arr[4]和15,arr[4] < 15,走分支1,将arr[4]和arr[2](就是arr[smaller+1])交换,小于区的右边界右移,同时i++

i=5,比较arr[5]和15,arr[5] > 15,走分支3,将arr[5]和arr[5](就是arr[bigger-1])交换,大于区的左边界左移:

此时i==bigger了,荷兰国旗完成,停止循环。

代码实现:

为了更具有普遍性,荷兰国旗问题定义为:让一个数组的L ~ R位置上,另小于等于arr[R]的元素放在数组左边,等于arr[R]的元素放在中间,大于arr[R]的元素放在数组右边。

/*** 荷兰国旗问题:给数组指定范围进行分区,小于arr[R]的放左边,大于arr[R]的放右边,中间是等于arr[R]的* @param arr 数组* @param L 待划分数组范围的左边界* @param R 到划分数组范围的右边界* @return int[] 返回相等区域的左右边界索引**/
public static int[] hollandFlag(int[] arr, int L, int R) {if (L > R) {return new int[] {-1, -1};}if (L == R) {return new int[] {L, R};}int smaller = L - 1;int bigger = R;int index = L;while (index < bigger) {//System.out.println("index:" + index + ",smaller:" + smaller + ",bigger:" + bigger + ",arr[index]:" + arr[index] + ",arr[R]:" + arr[R]);//分支1,arr[index] < arr[R]if (arr[index] < arr[R]) {swap(arr, index++, ++smaller);}//分支2,arr[index] == arr[R]else if (arr[index] == arr[R]) {index++;}//分支3,arr[index] > arr[R]else {swap(arr, index, --bigger);}//System.out.println(Arrays.toString(arr));}//要把R位置上的数放到大于区的第一个位置swap(arr, bigger, R);return new int[] {smaller + 1, bigger};
}

快速排序算法

快速排序也是采用分治思想实现,基于前面Partition和荷兰国旗问题的解决方案,我们把排序过程划分成很多小的规模,每个规模都调用Partition或者荷兰国旗问题来解决就可以完成排序了。

快排V1:使用Partition

在arr[L…R]范围上,进行快速排序的过程:

1)用arr[R]对该范围做partition,<= arr[R]的数在左部分并且保证arr[R]最后来到左部分的最后一个位置,记为M; <= arr[R]的数在右部分(arr[M+1…R])

2)对arr[L…M-1]进行快速排序(递归)

3)对arr[M+1…R]进行快速排序(递归)

因为每一次Partition都会搞定一个数的位置且不会再变动,所以排序能完成。

/*** 快速排序v1 用Partition方法**/
public static void quickSortV1(int[] arr) {if (arr == null || arr.length < 2) {return;}processV1(arr, 0, arr.length - 1);
}public static void processV1(int[] arr, int L, int R) {if (L >= R) {return;}int M = partition(arr, L, R);processV1(arr, L, M - 1);processV1(arr, M + 1, R);
}

快排V2:使用解决荷兰国旗问题的方案

在arr[L…R]范围上,进行快速排序的过程:

1)用arr[R]对该范围做partition,< arr[R]的数在左部分,== arr[R]的数中间,>arr[R]的数在右部分。假设== arr[R]的数所在范围是[a,b]

2)对arr[L…a-1]进行快速排序(递归)

3)对arr[b+1…R]进行快速排序(递归)

因为每一次Partition都会搞定一批数的位置且不会再变动,所以排序能完成。

/*** 快速排序V2 升级版Partition-荷兰国旗问题解决方案**/
public static void quickSortV2(int[] arr) {if (arr == null || arr.length < 2) {return;}processV2(arr, 0, arr.length - 1);
}public static void processV2(int[] arr, int L, int R) {if (L >= R) {return;}int[] equalArea = hollandFlag(arr, L, R);processV2(arr, L, equalArea[0] - 1);processV2(arr, equalArea[1] + 1, R);
}

快排V3:随机快排+荷兰国旗技巧优化

前两个版本的时间复杂度,数组已经排好序的情况下,复杂度均为O(N²),性能不太好,还有更好的解决方案。

在arr[L…R]范围上,进行快速排序的过程:

1)在这个范围上,随机选一个数记为num

2)用num对该范围做Partition,< num的数在左部分,== num的数中间,>num的数在右部分。假设== num的数所在范围是[a,b]

3)对arr[L…a-1]进行快速排序(递归)

4)对arr[b+1…R]进行快速排序(递归)

因为每一次Partition都会搞定一批数的位置且不会再变动,所以排序能完成。

变化点就是在数组中选一个随机数做为比较对象,然后进行Partition。

/*** 快速排序V3 随机快排+荷兰国旗技巧优化**/
public static void quickSortV3(int[] arr) {if (arr == null || arr.length < 2) {return;}processV3(arr, 0, arr.length - 1);
}public static void processV3(int[] arr, int L, int R) {if (L >= R) {return;}//优化点:选一个随机位置的数进行Partitionswap(arr, L + (int) (Math.random() * (R - L + 1)), R);int[] equalArea = hollandFlag(arr, L, R);processV3(arr, L, equalArea[0] - 1);processV3(arr, equalArea[1] + 1, R);
}

时间复杂度:

1)随机选的数越靠近中间,性能越好;越靠近两边,性能越差

2)随机选一个数进行划分的目的就是让好情况和差情况都变成概率事件

3)把每一种情况都列出来,会有每种情况下的时间复杂度,但概率都是1/N

4)那么所有情况都考虑,时间复杂度就是这种概率模型下的长期期望!

时间复杂度O(N*logN),额外空间复杂度O(logN)都是这么来的。

文章首发公众号:行百里er,欢迎关注,谢谢。

多图养眼!Partition,荷兰国旗问题与随机快排相关推荐

  1. 快速排序(由荷兰国旗问题到快排)

    文章目录 一.荷兰国旗问题 荷兰国旗解题思路 荷兰国旗代码 二.荷兰国旗到快排 时间复杂度分析 稳定性分析 一.荷兰国旗问题 在了解快排之前,我们首先了解荷兰国旗问题 给定一个数组arr,和一个整数n ...

  2. leetcode 75. Sort Colors | 75. 颜色分类(荷兰国旗问题,快速排序)

    题目 https://leetcode.com/problems/sort-colors/ 题解 快速排序3.0(随机快排+荷兰国旗技巧优化) 在arr[L-R]范围上,进行快速排序的过程: 1)在这 ...

  3. 荷兰国旗问题(partition)总结

    在之前总结的的快速排序算法中,用到了partition算法,故今天来总结下partition算法和其应用 partition算法,又称为荷兰国旗问题,其主要包括两个问题. 文章目录 1 问题1-二分p ...

  4. 福利~ 用异步爬虫快速爬取养眼美图

    一.准备爬取 这里我们选取的网站是彼岸图网:4K美女壁纸_高清4K美女图片_彼岸图网 使用的爬虫工具包是asyncio.aiohttp异步请求包,还有xpath爬取方法 同时需要os模块帮我们创建一个 ...

  5. 养眼神器,爬取美女网站个人的写真套图

    很久之前写的爬虫,一直没有放出来,这次放出来,单线程爬取,又有延时,所以有点慢.不过这也不耽误爬取了一个人的2万多张写真套图,因为这个人的很好看,哈哈哈...不过后来又全都删了,因为又发现了更好的网站 ...

  6. 荷兰国旗问题和快速排序

    荷兰国旗问题有两种情况:1)给定一个数x,<= x的整体放在左边,>x的整体放在右边2)给定一个数x,< x的整体放在左边,= x的整体放在中间,> x的整体放在右边规定不需要 ...

  7. python单链表实现荷兰国旗问题_这道荷兰旗问题,我面试时遇到三次!

    01.题目示例 "荷兰国旗问题" 是计算机科学中的一个经典题目,它是由Edsger Dijkstra提出的.荷兰国旗由红.白.蓝三色组成. 荷兰国旗问题:现在有若干个红.白.蓝三种 ...

  8. 荷兰国旗问题(分三块)

    在说 "荷兰国旗" 问题之前,首先来看一个引例. 给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边.要求额外空间复杂度O(1) ...

  9. 快速排序以及荷兰国旗问题

    荷兰国旗 要点:指针在移动时, 1.如果指针对应的值小于给定值,左标记和指针均向右移 2-如果指针对应的值等于给定值,指针右移 3-如果指针对应的值大于给定值,右标志位左移 package Test; ...

最新文章

  1. 印度孟买机器人餐厅_印度孟买自动化展:该国最重要、规模最大的国际自动化展...
  2. dayjs​​​​​​​文档
  3. Java生产力提示:社区的热门选择
  4. Ext4文件系统修复
  5. C++ 程序员要像医生一样调试代码?
  6. php 查询sybase 实例,php访问sybase16
  7. java零碎要点---用java实现生成二维码,与解析代码实现
  8. MTK:NICODE与ASCII码使用
  9. CCF201403-4 无线网络(100分)
  10. c语言编程统计磁盘文件,C语言编程题经典40题(附解答).ppt
  11. 译者后记 ——《DOOM启示录》读后感(二)
  12. 易鲸捷首架刘明:Trafodion值得放入工具箱,因为有以下优点
  13. 服务器不能创建对象教务系统,[转]解决强智教务系统非IE下无法创建对象错误...
  14. 二维正态分布matlab程序,一维正态分布、二维正态分布的matlab实现
  15. Java基于ssm的超市多商家系自提系统
  16. Your system is missing DirectX components
  17. 字节跳动面试官这样问消息队列:高可用、不重复消费、可靠传输、顺序消费、消息堆积,我整理了下
  18. 【408】计算机网络第一轮强化笔记
  19. Win98 is gone
  20. hihocoder1636-Pangu and Stones

热门文章

  1. Matlab 归一化函数premnmx [-1,1]
  2. 适应大众化教育的创客理念设计
  3. 这些年,这些挖掘机算法,这些反思
  4. SQL常用查询语法整理
  5. 同程旅游网开放平台SDK开发完成
  6. 读懂英文文章所需的单词量
  7. java 自然常数e中出现的连续的第一个10个数字组成的质数_冷知识:数学常数“e”的传奇故事...
  8. python 实现文件的批量压缩为.zip格式+.zip格式文件的解析
  9. 标准查询运算符(SQO)
  10. 中柏mini4 s 8寸 win10平板折腾