【恋上数据结构】归并排序 + LeetCode真题
归并排序
- 前言
- 归并排序
- 序列分割-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真题相关推荐
- 【恋上数据结构】复杂度知识以及LeetCode刷题指南
基础知识 什么是算法? 如何评判一个算法的好坏? 大O表示法(Big O) 对数阶的细节 常见的复杂度 多个数据规模的情况 LeetCode刷题指南 斐波那契数列复杂度分析 斐波那契数列 - 递归 斐 ...
- 归并排序算法 C++实现与时间复杂度(考过)恋上数据结构笔记
复习梗概 画图,自己整个数组,看代码写步骤,这个对理解归并排序还是很有必要的 合并两个有序数组的merge函数写法 时间复杂度的分析方法!!! 其实我觉得去b站找个动态的步骤分解视频也是不错的复习方法 ...
- 《恋上数据结构第1季》二叉树基础、真二叉树、满二叉树、完全二叉树、二叉树的遍历(重点)
二叉树(Binary Tree) 树(Tree)的基本概念 有序树.无序树.森林 二叉树(Binary Tree) 二叉树的性质 真二叉树(Proper Binary Tree) 满二叉树(Full ...
- MJ恋上数据结构(第1季 + 第2季)笔记
文章转载自:https://blog.csdn.net/weixin_43734095/article/details/104847976 恋上数据结构完整笔记(第1季 + 第2季) 前言 数据结构 ...
- 【恋上数据结构】排序算法前置知识及代码环境准备
排序准备工作 何为排序? 何为稳定性? 何为原地算法? 时间复杂度的知识 写排序算法前的准备 项目结构 Sort.java Asserts.java Integers.java Times.java ...
- 江西师范大学电子信息考研865数据结构(附真题以及部分资料)
江西师范大学电子信息考研865数据结构(附真题以及部分资料)附真题下载链接真题下载 22专硕考研选手,专业课865数据结构141 可以添加我的企鹅:1442704297 免费赠予865真题(13-19 ...
- 快速排序 C++代码实现及其算法思想及时间复杂度分析及优化 恋上数据结构笔记
文章目录 复习梗概 算法思想 算法复杂度分析及稳定性 如何优化? 快速排序改进版代码C++ 快速排序个人青春版代码 完整代码 复习梗概 算法思想,别的排序名字直接就能让人联想到它的算法思想,唯独快速排 ...
- 乘风破浪:LeetCode真题_038_Count and Say
乘风破浪:LeetCode真题_038_Count and Say 一.前言 这一道题目,很类似于小学的问题,但是如果硬是要将输入和结果产生数值上的联系就会产生混乱了,因此我们要打破思维定势. ...
- 【恋上数据结构】串匹配算法(蛮力匹配、KMP【重点】、Boyer-Moore、Karp-Rabin、Sunday)
串(Sequence) 串(前缀.后缀) 串匹配算法 蛮力(Brute Force) 蛮力1 – 执行过程 + 实现 蛮力1 – 优化 蛮力2 – 执行过程 + 实现 蛮力 – 性能分析 KMP 蛮力 ...
最新文章
- 2021年大数据Flink(二十):案例二 基于数量的滚动和滑动窗口
- 美团把AI搞出一股烟火气
- 分布式图处理系统同步异步执行模式
- 查看oracle系统信息,查看 ORACLE 系统级信息
- FreeBSD8.0搭建Apache+PHP+MySql平台
- LL-verilog语法-generate语句
- 天际数见数据质量巡检架构优化
- 【CF1215E】Marbles【状压DP】
- 2019无盘游戏服务器128g内存,云更新无盘客户端 v2019.8.15.12486官方版
- EasyUI 1.5.1 美化主题大包 Insdep Theme 1.0.3 已发布,开源下载
- Chrome 浏览器小恐龙游戏变身超级马利奥
- 为什么Java编程语言用一种咖啡名做名字
- JavaScript 正则表达式
- java 什么是成员变量_java成员变量和方法的含义是什么?异同点有哪些?
- Golang Hotfix技术背景
- 006-Archer@冯鹤楠 MySQL 打卡作业2
- 【开源】思源笔记自动备份
- 实用selenium+python实现web自动化测试
- java 监控linux服务器cpu使用率、内存使用率、磁盘使用率、java进程是否存活等服务
- 分页器的使用-2 手写一个分页器
热门文章
- 年轻时听到别人金钱至上的话语
- 许多新兴的互联网O2O企业,做的都是一些“无中生有”的事情
- 华为达芬奇架构到底好在哪里?
- NFC的实用性有多高,真的是刚需吗?
- Spring IOC容器的依赖注入流程(收集和注册、分析和组装)
- c语言程序设计小学生测验,c语言程序设计(1) 小学生计算机辅助教学系统
- php和asp渲染页面,Vue.js与 ASP.NET Core 服务端渲染功能
- SQL Server下载指南
- sql azure 语法_如何使用Azure门户,Cloud Shell和T-SQL复制Azure SQL数据库
- 多线程蜂鸣器研究,友善之臂Smart210开发版