快排和归并排序

  • 归并排序
  • 归并排序性能分析
    • 归并排序是稳定的排序算法吗
    • 归并排序时间复杂度
      • 空间复杂度
  • 快排
  • 归并排序对比快排
  • 快排处理第k大元素

冒泡、插入、排序这些排序时间复杂度都是 O(n2),时间复杂度高,适合小数据规模处理。
对于大数据规模排序,用归并排序和快速排序比较适合。
归并排序和快速排序都使用了分而治之的思想。

归并排序

归并排序的思想就是将数组分成前后两部分,然后再对分开的两部分再分成两部分,以此类推,分到不可再分为止,再合并一起,如此数组便可成有序了。
这个先分两半、再把两半分成两半、再分成两半,这就是前面所讲述的递归思想。所以可以用递归完成归并排序。

归并排序思想是分治,分而治之,将大问题分为小问题,小问题解决了,大问题也解决了。
分治思想是一种解决问题的思想,递归是一种编程技巧,两者并不冲突。
下面用递归处理归并排序:

1.递归公式
merge_sort(p…r) = merge(merge_sort(p…q),merge_sort(q+1…r))这里的q是中间元素下标,代表对半
2.终止条件:
p>=r的时候终止

p、r是无序数组的初始坐标和末元素坐标,将一整个排序问题化为了两个子问题,一个是merge_sort(p…q)和merge_sort(q+1…r)。用递归处理子问题,当子问题处理好了,再进行合并,这样p到r的数据大问题也处理好了。
代码如下:

    public static void merge(int[] arr, int start, int mid,int end){int i = start; //左边遍历起点 int j = mid+1; //右边遍历起点int[] tmp = new int[end-start+1];  //临时数组int tmp_i = 0; //临时数组起点坐标while (i<=mid&&j<=end){  //左边从起点到mid,右边mid+1到end遍历,两边一起开始遍历if (arr[i]<arr[j]){ //把小的放入数组如果右边比左边大,就先把小的左边放入临时数组tmp[tmp_i++] = arr[i++];//把小的,左边放入数组}else {tmp[tmp_i++] = arr[j++]; //把小的右边放入数组}}while (i<=mid){ 上面遍历完后,左边还有值没放入数组,直接放tmp[tmp_i++] = arr[i++];}while (j<=end){上面遍历完后,右边还有值没放入数组,直接放tmp[tmp_i++] = arr[j++];}for (i=0; i<tmp_i;++i){ //遍历临时数组,替换原数组arr[start++] = tmp[i];}}public static void merger_up_todown(int[] arr, int start, int end){if (start<end){//递归结束条件int mid= (start+end)/2;merger_up_todown(arr,start,mid); //递归左边merger_up_todown(arr,mid+1,end);//递归右边merge(arr,start,mid,end);//合并}}

先从merger_up_todown这个方法看,里面递归了三个merge,
前面两个merger_up_todown将数组的两半分别进行了拆分然后合并成有序的子数组
最后的一个merge将前面的两个有序子数组进行最后的合并成一个有序的数组.
整体过程如下:

1.申请一个tmp临时数组,大小和无序数组相同arr。
2.然后申请两个坐标i和j代表了了初始元素i和中间元素+1。代表了将数组分为两半的起始点元素,即A[p…q]和 A[q+1…r]
3.比较两个元素A[i]和 A[j],哪个小就放入临时数组tmp,且i或者j后移一位。
4.上面第三步操作进行循环,直到一个子数组的所有数组都放进了临时数组,再把另一个数组的剩余元素全部依次放入临时数组末尾。这个时候临时数组存放的就是两个子数组合并的最后结果了,再把临时数组的数据拷贝到无序数组arr中。

归并排序性能分析

归并排序是稳定的排序算法吗

合并过程中,如果A[p…q]和 A[q+1…r]之间有值相同的元素,先将A[p…q]的元素放入临时数组中,这样就没有改变相同元素的位置,所以是稳定的。

归并排序时间复杂度

假设归并排序中,问题a分解为求解问题b和问题c,等b和c解决后再合并b和c
求解a问题的时间是T(a),求解b和c的时间是 T(b) 和 T( c)
那么

T(1) =C ;C代表常量时间
(a) = T(b) + T© + K
K是合并两个问题bc的时间
假设对n个元素归并排序时间的T(n),
那么两个分解子问题的时间是T(n/2),merge合并两个有序子数组的时间复杂度是O(n)
所以归并时间复杂度的计算公式是
T(n) = 2T(n/2) + n; n>1

T(n) = 2
T(n/2) + n
= 2*(2T(n/4) + n/2) + n = 4T(n/4) + 2n
= 4
(2T(n/8) + n/4) + 2n = 8T(n/8) + 3n
= 8*(2T(n/16) + n/8) + 3n = 16T(n/16) + 4n

= 2^k * T(n/2^k) + k * n

T(n) =2^k * T(n/2^k) + k * n。
当T(n/2^k)=T(1) 时
即n/2^k=1 ,所以k=log2n ,将K带入公式,得到T(n)=Cn+nlog2n,换成O(n),T(n) 就等于 O(nlogn)

所以归并排序的时间复杂度是O(nlogn)。这个时间复杂度是稳定的,包括了最好、最坏、平均时间复杂度都是O(nlogn)。

空间复杂度

这是归并排序的弱点,归并排序不是原地排序。
归并排序在排序的时候需要借助一个临时数组tmp,这个数组大小跟原无序数组一样是n,所以空间复杂度是O(n)。

快排

快速排序的核心核心思想是分治,例如要排序下坐标p到r数组的数据,然后规定p-r之间一个点叫分区点pivot,使得分区点左边的数是比分区点上的数小的,即arr[p]<arr[pivot] ,分区点右边的数比分区点上的数大,即arr[pivot] < arr[r],然后通过递归、分治思想取缩减p到pivot的距离、pivot到r的距离,使得区间变为1 ,此时数据有序。

如何编排数据使得左边的数比分区点小,右边的数比分区点大,通常油三种方法,如下:

package com.company;import java.util.Arrays;public class MyquickSort {public static void main(String[] args) {int[] arr1 = {0,11111,103,100,1,3,2,6,4,5,8,7,10,9};quecksort3(arr1,0,arr1.length-1);System.out.println(Arrays.toString(arr1));}public static void quicksort1(int[] arr, int begin, int end){//左右指针法/*** 1.选出一个key,一般是最左或者最右* 2.定义begin,end,begin从左往右走,end从右往左走* 3.key在左边的话,end先走。key在右边的话end先走* 4.走的过程中,如果end遇到的数小于key遇到的数,停下此时为arr[end]* 5.end停下后,begin也往前走,如果遇到大于key的数, 也停下,此时为arr[begin]* 6.arr[end]和arr[begin]进行交换* 7.交换后end继续往左,如果遇到比key的数小,停下* 8.begin往右走,如果遇到比key大的数,停下,交换。* 9.如果在第8的时候没有遇到比key大的数,begin就会遇到end。就是begin=end* 10.这个时候,将key跟begin对应的数进行交换,交换后,新key左边是比key小的数,右边的数是比key大的数* **/if (begin >= end){return;}/** begin,0, end arr.length-1* */int left = begin;  //定义一个左变量,留着做递归int right = end;  //定义一个右变量,留着做递归int keyi = begin;  //定义key所在的下坐标。while (begin < end){  //当begin<end的时候遍历,当begin=end的时候跳出循环,跟arr[begin]跟arr[keyi]交换while (arr[end] >= arr[keyi] && begin <end){ --end;} //找出当begin<end的时候,arr[end]<arr[keyi]的下坐标,即在右边找出比key小的值while (arr[begin] <= arr[keyi] && begin < end){ ++begin;}//跟上同理,在左边找出比key大的值int tmp = arr[end]; // 此三行是将上面找出的右边大值arr[end]、左边小值arr[begin]进行交换arr[end] = arr[begin];arr[begin]=tmp;}//经过上面的while循环后,这时候begin跟end指针已经相遇,即begin = end,所以这时候需要将begin坐标对应的值跟key值进行交换int tmp = arr[end];arr[end] = arr[keyi];arr[keyi] = tmp;keyi = end;  //将begin和end相遇的下坐标赋值给keyi,作为数据切割点,切割左边的数据做下一次的递归,切割左边的数据做递归quicksort1(arr,left,keyi-1);quicksort1(arr,keyi+1,right);}public static void quicksort2(int[] arr, int begin, int end){//挖坑法/*** 1.定义一个单独变量key保存key值(是key值,不是下坐标),一般是数组最左或者最右的的一个元素* 2.1完成后,key值对应的坐标可以看成是一个坑位,可以随意用别的数填取(即赋值给该下坐标)* 3.定义两个指针begin\end对应数组的0坐标和最末尾坐标* 4.如果是选最左(即arr[begin],数组的下坐标0)作为key值,即坑位是arr[begin],因为arr[begin]的值已经赋值给了key,不受数组影响了,先从end--遍历* 5.如果end--遍历,找到比key值小的arr[end],这时候需要arr[end]赋值给4说的坑位,arr[begin] = arr[end],俗称填坑。赋值填坑完成后,此时的arr[end]就变成新的坑位* 6.end填坑后,开始begin++遍历,找出比key大的值arr[begin],然后将该值赋值给5的arr[end]坑位进行填坑,此时的arr[begin]就变成了新坑位。* 7.begin填坑后,再次循环做5、6步骤,直至begin和end在一个坑位相遇(end/begin填坑后,end/begin位置留下坑位,然后另一个指针begin/end遍历++/--找不到比key大或者小的值,一直遍历到begin=end)* 8.相遇后,下坐标就是坑位,将key值赋值到坑位中,* 9.将该坑位定义给keyi,进行分割数据分别做递归* */if (begin>=end) {return;}int left = begin;//定义一个左变量,留着做递归int right = end;//定义一个右变量,留着做递归int key = arr[begin]; //将数组最左位arr[0]定义并赋值给变量key,留下坑位while (begin<end){//左右指针遍历while (arr[end] >=key && begin < end){ --end; } //end找出指针比key小的值,arr[begin] = arr[end]; //将end找出的比key小的值赋值给坑位arr[begin]进行填坑,留下坑位arr[begin]while (arr[begin] <= key && begin < end){++begin;}//end填坑后,begin++找出比key大的值,arr[end] = arr[begin];//将begin找出的值赋值给arr[begin]进行填坑,留下坑位}//begin跟end相遇在某个坑位arr[begin] = key; //用key填坑int keyi = begin; //将此次处理的最后坑位做数据切割点,切分数据quicksort2(arr,left,keyi-1);//切分数据,左边递归quicksort2(arr,keyi+1,right);//切分数据,右边递归}public static void quecksort3(int[] arr, int begin, int end){//前后指针/*** 1.选出一个key,用keyi记录它的下坐标,一般最右或者最左。* 2.定义pre之前指向数组开头(在一开始的时候,pre指向begin-1),cur指针指向pre+1(begin)* 3.首先arr[cur]会跟key值arr[keyi]比较,如果arr[cur]比key值小,pre指针则会往后移动一位,即pre+1,然后arr[pre+1]跟arr[cur]交换数据,交换完后cur继续往后cur++* 4.如果arr[cur]比key值大,则cur一直往后走,一直cur++* 5.cur++一直往后走, 直到cur到end位置,即cur=end* 6.cur=end后将key值arr[keyi]和++pre值arr[++pre]交换,* 7.然后现在pre左边是比它小的数,右边是比他大的数,所以将此时的pre赋值给keyi,切分数据,递归* */if (begin >= end){return;}int pre = begin - 1,cur = begin;int keyi = end;while (cur != end){System.out.println("pre:"+pre);System.out.println("cur:"+cur);System.out.println("stop");if (arr[cur] < arr[keyi] && ++pre !=cur){int tmp = arr[pre];arr[pre] = arr[cur];arr[cur] = tmp;}++cur;}int tmp1 = arr[keyi];arr[keyi] = arr[++pre];arr[pre] = tmp1;keyi = pre;quecksort3(arr,begin,keyi-1);quecksort3(arr,keyi+1,end);}
}

归并排序对比快排

归并排序和快排都是使用了分治、递归的思想去进行实现。理解归并排序重要的是理解递归公式和merge()合并函数,理解快排重要的是理解递归公式和partition()方式。

归并排序是一种时间复杂度比较稳定的排序方法,缺点是不是原地排序,空间复杂度高,O(N)

快排最坏时间复杂度是O(n2),但是平均复杂度是O(nlogn)。而且很小概率演变为最坏时间复杂度。

快排处理第k大元素

package main.java.java_test;import java.util.Arrays;/*** @discreption:* @author: Chen* @date: 2022年01月15日 22:13* @version: 2022年01月15日 admin*/
public class 第k大元素 {public static void main(String[] args) {int[] arr1 = {0,11111,103,100,1,3,2,6,4,5,8,7,10,9};int result = quickfindLarge(arr1,3);System.out.println(Arrays.toString(arr1));System.out.println(result);}public static int quickfindLarge(int[] arr, int k){k = arr.length-k; //在有序序列中找序号为arr.length-k大的数int begin = 0,end = arr.length-1;  //定义遍历数组while (begin < end){  //左右指针便利int keyi = quickPartition(arr,begin,end); //找出每次的分区点if (keyi == k){  //当分区点==k的时候,为第几大元素,如k=1,为最大的数break;}else if (keyi < k){ //当分区点在k左边,这个时候arr[keyi]的数不够大,需要左指针右移再分区一次,begin = keyi+1;}else {  //分区点在k右边,这个时候arr[keyi]太大,需要右指针左移一次end = keyi-1;}}return arr[k];}//快排分区方法,切分数据,使得分区点左边数据小于分区点上的数据,分区点右边的数据大于分区点数据public static int quickPartition(int[] arr, int begin, int end){int   keyi = begin; // 设置数组最左边为分区点while (begin < end){ // 左右指针便利while (arr[end] >= arr[keyi] && begin < end){ --end;} //找出右指针比分区点小的数while (arr[begin] <= arr[keyi] && begin < end){ ++begin;} //找出左指针比分区点大的数int tmp = arr[end]; //  交换数据arr[end] = arr[begin];arr[begin] = tmp;}int temp = arr[end];  //左右指针相遇,移动分区点到相遇点arr[end] = arr[keyi];arr[keyi] = temp;keyi = end; //找出最新的分区点return keyi;}
}

在处理第k大元素的时候,需要将之前快排的代码稍微改一下,将原本合并递归的代码修改为两个方法,一个是分区方法,即将数据左右分区,左分区的数小于分区点,右分区的数大于分区点, 另一个方法是寻找第k大分区点的方法,将分区方法求出来的分区点所在的位置与k相比,当分区点=k的时候,此时的arr[分区点]为第k大的数

快排和归并排序--快排处理第k大元素相关推荐

  1. 分治法:快速排序,3种划分方式,随机化快排,快排快,还是归并排序快?

    快速排序不同于之前了解的分治,他是通过一系列操作划分得到子问题,不同之前的划分子问题很简单,划分子问题的过程也是解决问题的过程 我们通常划分子问题尽量保持均衡,而快排缺无法保持均衡 快排第一种划分子问 ...

  2. 12 | 排序(下):如何用快排思想在O(n)内查找第K大元素?

    算法对比: 算法 时间复杂度 适合场景 冒泡排序.插入排序.选择排序 O(n2) 小规模数据 归并排序.快速排序 O(nlogn) 大规模数据 归并排序和快速排序都用到了分治思想,非常巧妙.我们可以借 ...

  3. 【数据结构与算法之美】排序(下):如何用快排思想在O(n)内查找第K大元素?

    目录 一.分治思想 二.归并排序 三.快速排序 四.归并排序与快速排序的区别 五.课后思考 一.分治思想 1.分治思想:分治,顾明思意,就是分而治之,将一个大问题分解成小的子问题来解决,小的子问题解决 ...

  4. 选择第K大元素(快排、快选以及k-选取比较)

    选择第K大元素(快排.快选以及k-选取比较) 问题:编写一段程序,随机生成10^4, 10^5, 10^6个随机数,并分别在这三者中,分别使用快排和linearSelect()方法,选择出第100大的 ...

  5. 【LeetCode】快排-无序整数数组中找第k大的数(或者最小的k个数)

    一个有代表性的题目:无序整数数组中找第k大的数,对快排进行优化. 这里先不说这个题目怎么解答,先仔细回顾回顾快排,掰开了揉碎了理解理解这个排序算法:时间复杂度.空间复杂度:什么情况下是复杂度最高的情况 ...

  6. 查找两个已经排好序的数组的第k大的元素

    http://www.cnblogs.com/buptLizer/archive/2012/03/31/2427579.html 给出两个排好序的数组 ,不妨设为a,b都按升序排列,及k的值,求出第k ...

  7. 【LeetCode笔记】215. 数组中的第K个最大元素(Java、快排、堆排、并发快排)

    文章目录 题目描述 思路 & 代码 快排 基于 Fork / Join 的并发快排 针对 topK 的快排优化 堆排 基本堆排 结合题目的堆排 二刷 题目描述 大名鼎鼎的TOP K,主要考察排 ...

  8. (归并排序 快排 堆)

    文章目录 归并排序 递归方法实现 非递归方法实现 求数组的小和 快排 荷兰国旗问题(Partition过程) 快排1.0 快排2.0 快排3.0 堆 大根堆 堆排序 使用堆排序 归并排序 递归方法实现 ...

  9. 快排和归并排序哪个更快

    在看算法图解的过程中,看到书中说时间复杂度同为O(nlogn),快排比归并排序快的原因是快排查找的常量要比归并小. 看完还不是很理解,去网上查了下资料,看了几种回答,还是<数据结构与算法分析:C ...

最新文章

  1. 前端资源系列(2)-SublimeText快捷键大全
  2. ng-repeat支持的输入种类
  3. java中字符串的截取
  4. qt修改程序图标名称_解决Qt应用程序添加icon图标,修改窗口图标以及添加系统托盘问题...
  5. 手机自动化测试:appium源码分析之bootstrap八
  6. qt5 make 找不到QApplication
  7. android java 调用js,Android中Java和JavaScript交互实例
  8. 算法 --- 希尔排序、归并排序、快速排序的js实现
  9. electron 打开调试_Electron 应用调试指南
  10. poj 2528_2
  11. prometheus修改数据保留时间
  12. [Ubuntu] 无法修正错误,因为您要求某些软件包保持现状,就是它们破坏了软件包间的依赖关系
  13. 单片机课程设计——《基于AT89S52单片机和DS1302时钟芯片的电子时钟(可蓝牙校准)》... 1
  14. 工业大数据有哪些特征
  15. mac nginx 指定php.ini,基于Mac自带nginx、php,配置php运行环境
  16. 3dmax2020卸载/安装失败/如何彻底卸载清除干净3dmax2020注册表和文件的方法
  17. 【爬虫工具】哔哩哔哩插件姬(bilibili-plugin)
  18. 华为/荣耀 笔记本 HiboardDataReport.exe应用程序错误
  19. xss.haozi.me靶场详解
  20. 2012 CSP-S 初赛 答案解析

热门文章

  1. DCL(数据控制语言)和TCL(事务控制语言)
  2. Linux中ifconfig command not found
  3. 股票中MACD如何计算,有什么意义
  4. C语言语句篇-------赋值语句
  5. 利用ESP8266-12F实现与51单片机通信及温湿度传感器数据交互
  6. 机器人操作系统ROS(4)话题编程
  7. iptables配置SNAT实现共享上网
  8. Cohen's kappa coefficient
  9. 软文营销文案兼具有趣的灵魂才能形成广泛传播
  10. 高斯白噪声及Matlab常用实现方法