归并排序

  • 前言
  • 归并排序
    • 序列分割-divide
    • 序列合并-merge
      • 合并到新序列
      • 原地合并-merge
      • 原地合并-merge-实现
  • 归并排序完整代码
  • 复杂度与稳定性
    • 常见的递推式与复杂度
  • LeetCode真题
    • 88. 合并两个有序数组
    • 思路一:从前往后合并
    • 思路二:从后往前合并

经典的十大排序算法!

前言

请务必看一下这个:排序算法前置知识+代码环境准备。

当上面的内容都准备好以后,那就开始归并排序吧!

归并排序

1945年由约翰·冯·诺伊曼(John von Neumann)首次提出。

执行流程

  • ① 不断地将当前序列平均分割成 2 个子序列
    直到不能再分割(序列中只剩 1 个元素)
  • ② 不断地将 2 个子序列合并成一个有序序列
    直到最终只剩下 1 个有序序列

序列分割-divide

sort(0, array.length);
-----------------------------------------------------
/*** 对 [begin, end) 范围的数据进行归并排序*/
private void sort(int begin, int end){if(end - begin < 2) return; // 至少要2个元素int mid = (begin + end) >> 1;sort(begin, mid); // 归并排序左半子序列sort(mid, end);   // 归并排序右半子序列merge(begin, mid, end); // 合并整个序列
}

序列合并-merge

合并到新序列

将两个序列合并的思路为:左序列和右序列的中元素挨个比较,将较小的放入新序列中,最后新序列中的元素必然升序。

下图中 li,ri 分别代表指向左、右序列的元素索引,ai 为新序列(合并后的序列)的元素索引
【li】代表左序列 li 位置的元素,【ri】代表右序列 ri 位置的元素,【ai】为新序列 ai 位置的元素

  • 第一轮:【li】 < 【ri】,【li】放入新数组,【ai】=【li】,li++; ai++;
  • 第二轮:【li】 > 【ri】,【ri】放入新数组,【ai】=【ri】,ri++; ai++;
  • 第三轮:【li】 < 【ri】,【li】放入新数组,【ai】=【li】,li++; ai++;
  • 第四轮:左序列已经遍历完毕,直接将右序列剩余元素放入新序列,得到新序列(升序)。

原地合并-merge

将两个序列合并时,不一定要合并到新空间,可以合理的利用原空间实现原地合并

例如:

  • array的左半部分[begin, mid),备份到 leftArray 中;
  • 然后将 leftArray 视为左子序列,arrary的右半部分[mid, end] 视为右子序列;
  • 将左子序列和右子序列合并到 array 中。

merge 过程:

  • li < ri
    array[ai] = leftArray[li];
    li++,ai++;
  • li >= ri
    array[ai] = array[ri];
    ri++,ai++;

对序列 { 3, 8, 6, 10 } 进行归并排序:

对序列 { 3, 6, 8, 10 } 进行归并排序:左子序列先遍历结束,那就归并结束。

对序列 { 8, 10, 3, 6 } 进行归并排序:右子序列先结束,则将左边剩余全部放入。

原地合并-merge-实现

/*** 将 [begin, mid) 和 [mid, end) 范围的序列合并成一个有序序列*/
private void merge(int begin, int mid, int end){int li = 0, le = mid - begin; // 左边数组(基于leftArray)int ri = mid, re = end;   // 右边数组(array)int ai = begin; // array的索引// 备份左边数组到leftArrayfor(int i = li; i < le; i++){leftArray[i] = array[begin + i];}// 如果左边还没有结束 while(li < le){ // li == le 左边结束, 则直接结束归并if(ri < re && cmp(array[ri], leftArray[li]) < 0){ // cmp改为<=0会失去稳定性array[ai++] = array[ri++]; // 右边<左边, 拷贝右边数组到array}else{array[ai++] = leftArray[li++]; // 左边<=右边, 拷贝左边数组到array}}
}

归并排序完整代码

/*** 归并排序*/
@SuppressWarnings("unchecked")public class MergeSort <T extends Comparable<T>> extends Sort<T> {private T[] leftArray;@Overrideprotected void sort() {// 准备一段临时的数组空间, 在merge操作中使用leftArray = (T[])new Comparable[array.length >> 1];sort(0, array.length);}/*** 对 [begin, end) 范围的数据进行归并排序*/   private void sort(int begin, int end){if(end - begin < 2) return; // 至少要2个元素int mid = (begin + end) >> 1;sort(begin, mid); // 归并排序左半子序列sort(mid, end);   // 归并排序右半子序列merge(begin, mid, end); // 合并整个序列}/*** 将 [begin, mid) 和 [mid, end) 范围的序列合并成一个有序序列*/private void merge(int begin, int mid, int end){int li = 0, le = mid - begin; // 左边数组(基于leftArray)int ri = mid, re = end;    // 右边数组(array)int ai = begin; // array的索引// 备份左边数组到leftArrayfor(int i = li; i < le; i++){leftArray[i] = array[begin + i];}// 如果左边还没有结束while(li < le){ // li == le 左边结束, 则直接结束归并if(ri < re && cmp(array[ri], leftArray[li]) < 0){ // cmp改为<=0会失去稳定性array[ai++] = array[ri++]; // 右边<左边, 拷贝右边数组到array}else{array[ai++] = leftArray[li++]; // 左边<=右边, 拷贝左边数组到array}}}}

生成 20000 个取值在[1, 10000] 的随机数进行排序:

复杂度与稳定性

归并排序花费的时间递推式

  • T(n) = 2 ∗ T(n/2) + O(n)
  • T(1) = O(1)
  • T(n) / n = T(n/2)/(n/2) + O(1)

根据递推式计算复杂度
令 Sn = T(n) / n

  • S(1) = O(1)
  • Sn = S(n/2) + O(1) = S(n/4) + O(2) = S(n/8) + O(3) = S(n/2k) + O(k) = S(1) + O(logn) = O(logn)
  • Tn = n ∗ Sn = O(nlogn)

由于归并排序总是平均分割子列,所以

  • 最好、最坏时间复杂度都 O(nlogn)
  • 归并排序属于稳定排序
  • 归并排序的空间复杂度是 O(n/2 + logn) = O(n)
    n / 2 用于临时存放左侧数组, logn 是因为递归调用

常见的递推式与复杂度

以后遇到复杂的时间复杂度计算,写出递归式直接看这张表即可。

LeetCode真题

88. 合并两个有序数组

题目地址:88. 合并两个有序数组

题目:

给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 num1 成为一个有序数组。

说明:

  • 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
  • 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。

示例:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3输出: [1,2,2,3,5,6]

思路一:从前往后合并

原地合并,nums1 从头往后合并,将 nums2 元素插入到 nums1 时需要将挪动元素,效率较低

class Solution {public void merge(int[] nums1, int m, int[] nums2, int n) {int li = 0, ri = 0, ai = 0, m2 = m; // 备份一下m      while(ri < n){ //nums2遍历完则直接结束if(nums1[li] <= nums2[ri] && li < m2){li++;ai++;}else{ // nums1[li] >= nums2[ri]// 要令ri指向元素插入到ai, 首先将 ai到ai+li往后移1位for(int i = m+n-1; i > ai; i--){nums1[i] = nums1[i - 1];}nums1[ai++] = nums2[ri++];li++;m2++;}}}}

思路二:从后往前合并

在思路一的基础上,将从前往后合并变为从后往前合并

原地合并,nums1 从后往前合并,将 nums2 元素放入 nums1 时无需移动元素,效率较高

class Solution {public void merge(int[] nums1, int m, int[] nums2, int n) {int len = m + n; //从后往前移, 需要合并后的总长度for(int i = len - 1; i >=0; i--){if(m>0 && n>0 && nums1[m-1] > nums2[n-1]  || n==0){nums1[i] = nums1[--m];}else{nums1[i] = nums2[--n];}}        }}

【恋上数据结构】归并排序 + LeetCode真题相关推荐

  1. 【恋上数据结构】复杂度知识以及LeetCode刷题指南

    基础知识 什么是算法? 如何评判一个算法的好坏? 大O表示法(Big O) 对数阶的细节 常见的复杂度 多个数据规模的情况 LeetCode刷题指南 斐波那契数列复杂度分析 斐波那契数列 - 递归 斐 ...

  2. 归并排序算法 C++实现与时间复杂度(考过)恋上数据结构笔记

    复习梗概 画图,自己整个数组,看代码写步骤,这个对理解归并排序还是很有必要的 合并两个有序数组的merge函数写法 时间复杂度的分析方法!!! 其实我觉得去b站找个动态的步骤分解视频也是不错的复习方法 ...

  3. 《恋上数据结构第1季》二叉树基础、真二叉树、满二叉树、完全二叉树、二叉树的遍历(重点)

    二叉树(Binary Tree) 树(Tree)的基本概念 有序树.无序树.森林 二叉树(Binary Tree) 二叉树的性质 真二叉树(Proper Binary Tree) 满二叉树(Full ...

  4. MJ恋上数据结构(第1季 + 第2季)笔记

    文章转载自:https://blog.csdn.net/weixin_43734095/article/details/104847976 恋上数据结构完整笔记(第1季 + 第2季) 前言 数据结构 ...

  5. 【恋上数据结构】排序算法前置知识及代码环境准备

    排序准备工作 何为排序? 何为稳定性? 何为原地算法? 时间复杂度的知识 写排序算法前的准备 项目结构 Sort.java Asserts.java Integers.java Times.java ...

  6. 江西师范大学电子信息考研865数据结构(附真题以及部分资料)

    江西师范大学电子信息考研865数据结构(附真题以及部分资料)附真题下载链接真题下载 22专硕考研选手,专业课865数据结构141 可以添加我的企鹅:1442704297 免费赠予865真题(13-19 ...

  7. 快速排序 C++代码实现及其算法思想及时间复杂度分析及优化 恋上数据结构笔记

    文章目录 复习梗概 算法思想 算法复杂度分析及稳定性 如何优化? 快速排序改进版代码C++ 快速排序个人青春版代码 完整代码 复习梗概 算法思想,别的排序名字直接就能让人联想到它的算法思想,唯独快速排 ...

  8. 乘风破浪:LeetCode真题_038_Count and Say

    乘风破浪:LeetCode真题_038_Count and Say 一.前言     这一道题目,很类似于小学的问题,但是如果硬是要将输入和结果产生数值上的联系就会产生混乱了,因此我们要打破思维定势. ...

  9. 【恋上数据结构】串匹配算法(蛮力匹配、KMP【重点】、Boyer-Moore、Karp-Rabin、Sunday)

    串(Sequence) 串(前缀.后缀) 串匹配算法 蛮力(Brute Force) 蛮力1 – 执行过程 + 实现 蛮力1 – 优化 蛮力2 – 执行过程 + 实现 蛮力 – 性能分析 KMP 蛮力 ...

最新文章

  1. 2021年大数据Flink(二十):案例二 基于数量的滚动和滑动窗口
  2. 美团把AI搞出一股烟火气
  3. 分布式图处理系统同步异步执行模式
  4. 查看oracle系统信息,查看 ORACLE 系统级信息
  5. FreeBSD8.0搭建Apache+PHP+MySql平台
  6. LL-verilog语法-generate语句
  7. 天际数见数据质量巡检架构优化
  8. 【CF1215E】Marbles【状压DP】
  9. 2019无盘游戏服务器128g内存,云更新无盘客户端 v2019.8.15.12486官方版
  10. EasyUI 1.5.1 美化主题大包 Insdep Theme 1.0.3 已发布,开源下载
  11. Chrome 浏览器小恐龙游戏变身超级马利奥
  12. 为什么Java编程语言用一种咖啡名做名字
  13. JavaScript 正则表达式
  14. java 什么是成员变量_java成员变量和方法的含义是什么?异同点有哪些?
  15. Golang Hotfix技术背景
  16. 006-Archer@冯鹤楠 MySQL 打卡作业2
  17. 【开源】思源笔记自动备份
  18. 实用selenium+python实现web自动化测试
  19. java 监控linux服务器cpu使用率、内存使用率、磁盘使用率、java进程是否存活等服务
  20. 分页器的使用-2 手写一个分页器

热门文章

  1. 年轻时听到别人金钱至上的话语
  2. 许多新兴的互联网O2O企业,做的都是一些“无中生有”的事情
  3. 华为达芬奇架构到底好在哪里?
  4. NFC的实用性有多高,真的是刚需吗?
  5. Spring IOC容器的依赖注入流程(收集和注册、分析和组装)
  6. c语言程序设计小学生测验,c语言程序设计(1) 小学生计算机辅助教学系统
  7. php和asp渲染页面,Vue.js与 ASP.NET Core 服务端渲染功能
  8. SQL Server下载指南
  9. sql azure 语法_如何使用Azure门户,Cloud Shell和T-SQL复制Azure SQL数据库
  10. 多线程蜂鸣器研究,友善之臂Smart210开发版