归并排序时一种使用分治思想实现的高效的排序算法,它的最好/最坏/平均时间复杂度都是O(nlogn),而且是一种稳定的排序算法。然而归并排序并没有得到广泛的应用,今天我们来看看归并排序的特点、优化和应用。

1. 算法简介

归并排序的原理
  归并排序(Merge Sort)的核心思想就是一个典型的分治算法。如果要排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。算法示意图如下:

  代码实现如下:

class Solution {
public:vector<int> sortArray(vector<int>& nums) {int n = nums.size();temp.resize(n);mergeSort(nums, 0, n-1);return nums;}
private:vector<int> temp;void mergeSort(vector<int>& nums, int left, int right){if(left >= right) return;int mid = left+(right-left)/2;mergeSort(nums, left, mid);mergeSort(nums, mid+1, right);merge(nums, left, mid, right);}void merge(vector<int>& nums, int left, int mid, int right){for (int i = left; i <= right; ++i)temp[i] = nums[i];int i = left, j = mid+1, index = left;while(i <= mid && j <= right){if(temp[i] < temp[j])nums[index++] = temp[i++];elsenums[index++] = temp[j++];}while(i <= mid)  nums[index++] = temp[i++];while(j <= right)  nums[index++] = temp[j++];}
};

 由上面算法的实现我们可以看到,归并分解合并的过程就像一颗二叉树,树的高度是logn, 每层的合并时间复杂度是O(n)。归并算法的分解合并与数据是否有序无关,所以任何情况下的时间复杂度都是O(nlogn)。由于在合并的过程中使用了一个大小为n的临时数组,因此归并算法的空间复杂度是O(n),非原地排序。与快速排序和堆排序相比,归并排序不是原地排序,这是归并排序的一大劣势,因而使用较少。

2.自底向上的归并算法

  以上就是课本上经常讲的最典型的归并算法,由于依赖于递归实现,所以它是一种自顶向下的归并算法。
  归并算法也可以不依赖递归,实现自底向上的归并。算法处理过程示意图如下:

  自底向上的归并没有问题的分解过程,直接从元素数只有一个的问题开始,首先进行两两归并,然后四四归并,再之后八八归并,一直下去。
  代码实现如下:

class Solution {
public:vector<int> sortArray(vector<int>& nums) {int n = nums.size();temp.resize(n);for(int size = 1; size < n; size += size){for(int left = 0; left < n-size; left += size+size){merge(nums, left, left+size-1, min(left+size+size-1,n-1));}}return nums;}
private:vector<int> temp;void merge(vector<int>& nums, int left, int mid, int right){for (int i = left; i <= right; ++i)temp[i] = nums[i];int i = left, j = mid+1, index = left;while(i <= mid && j <= right){if(temp[i] < temp[j])nums[index++] = temp[i++];elsenums[index++] = temp[j++];}while(i <= mid)  nums[index++] = temp[i++];while(j <= right)  nums[index++] = temp[j++];}
};

  算法特点:
自底向上的归并算法也是使用分治思想解决问题,时间复杂度也是O(nlogn),由于使用辅助数组存储空间复杂度也是O(n)。
与自顶向下的归并相比,自底向上的归并没有分解过程,不依赖与递归实现,不需要使用系统递归栈。所以虽然时间复杂度相同,但性能上自底向上的归并要优于自顶向下归并。

3.归并排序的优化

  • 对小规模子数组使用插入排序,小规模范围内插入排序性能更优
  • 测试子数组是否已经有序,如果有序则不需要合并
 void mergeSort(vector<int>& nums, int left, int right){if(left >= right) return;int mid = left+(right-left)/2;mergeSort(nums, left, mid);mergeSort(nums, mid+1, right);//测试子数组是否已经有序if(nums[mid] > nums[mid+1]){merge(nums, left, mid, right);}}
  • 不将元素复制到辅助数组

4.链表的归并排序(自顶向下)

148. 排序链表
题目描述:在O(nlogn) 时间复杂度内对链表排序
递归实现的自顶向下归并:

class Solution {
public:ListNode* sortList(ListNode* head) {return sortList(head, nullptr);}
private:ListNode* sortList(ListNode* head, ListNode* tail){if(!head) return head;if(head->next == tail){head->next = nullptr;return head;}ListNode* slow = head, *fast = head;while(fast != tail){slow = slow->next;fast = fast->next;if(fast != tail)fast = fast->next;}return merge(sortList(head,slow), sortList(slow,tail));}ListNode* merge(ListNode* list1, ListNode* list2){auto dummy = new ListNode(0);auto node = dummy;while(list1 && list2){if(list1->val < list2->val){node->next = new ListNode(list1->val);list1 = list1->next;}else{node->next = new ListNode(list2->val);list2 = list2->next;}node = node->next;}while(list1){node->next = new ListNode(list1->val);list1 = list1->next;node = node->next;}while(list2){node->next = new ListNode(list2->val);list2 = list2->next;node = node->next;}return dummy->next;}
};

算法分析:递归分治实现归并排序,时间复杂度O(nlogn), 虽然未使用额外存储,但递归会使用函数栈空间,空间复杂度O(logn)。

5. 链表的归并排序(自底向上)

148. 排序链表
题目描述:在O(nlogn)时间复杂度和常数空间复杂度内对链表排序
题目分析:为了实现O(1)空间复杂度,因此不能使用递归,使用自底向上的归并可以实现此目标。

class Solution {
public:ListNode* sortList(ListNode* head) {int n = 0;auto node = head;while(node){node = node->next;n++;}auto dummy = new ListNode(0,head);for(int size = 1; size < n; size <<= 1){auto prev = dummy;auto cur = dummy->next;while(cur){//找到子链表的开始位置、中间位置和结束位置auto head1 = cur;for(int i = 1; i < size && cur->next; i++)cur = cur->next;auto head2 = cur->next;cur->next = nullptr; //链表打断cur = head2;for(int i = 1; i < size && cur && cur->next; i++)cur = cur->next;ListNode* next = nullptr; //子链表结尾后的位置if(cur){next = cur->next;cur->next = nullptr;//链表打断}//合并子链表auto temp = merge(head1, head2); //挂接已排序子链表prev->next = temp;//移动prev和cur指针位置while(prev->next) prev = prev->next;cur = next;}}return dummy->next;}
private:ListNode* merge(ListNode* list1, ListNode* list2){auto dummy = new ListNode(0);auto node = dummy;while(list1 && list2){if(list1->val < list2->val){node->next = new ListNode(list1->val);list1 = list1->next;}else{node->next = new ListNode(list2->val);list2 = list2->next;}node = node->next;}while(list1){node->next = new ListNode(list1->val);list1 = list1->next;node = node->next;}while(list2){node->next = new ListNode(list2->val);list2 = list2->next;node = node->next;}return dummy->next;}
};

归并排序及其优化(数组归并/链表归并,自顶向下/自底向上等)相关推荐

  1. 链表的各种操作实现 链表逆序 链表排序 有序链表归并 链表存在环的判定

    链表的各种操作实现 链表逆序 链表排序 有序链表归并 链表存在环的判定 链表基本操作实现 c语言版本, 该程序在visual c++ 6.0上调试通过! 本人写该程序完全是为学习交流之用,还望大家多多 ...

  2. [编程技巧] 巧用CPU缓存优化代码:数组 vs. 链表

    一个常见的编程问题: 遍历同样大小的数组和链表, 哪个比较快? 如果按照大学教科书上的算法分析方法,你会得出结论,这2者一样快, 因为时间复杂度都是 O(n). 但是在实践中, 这2者却有极大的差异. ...

  3. 【外排序】外排序算法(磁盘排序、磁带排序) 外存设备结构分析 败者树多路归并 最佳归并树白话讲解

    外排序 外排序概述 外排序的基本方法是归并排序法 例子 总结 存储设备(可忽略) 磁带 磁带结构 磁盘 硬盘结构 块 硬盘上的数据定位 磁盘排序 磁盘排序过程 1.生成初始顺串 方法1(常规方法): ...

  4. 数据结构之数组、链表、栈和队列

    1.数组 1.1:概念 数组是一种线性表数据结构,它用一组连续的内存空间,来存储一组具有相同类型的数据.这里我们要抽取出三个跟数组相关的关键词:线性表,连续内存空间,相同数据类型:数组具有连续的内存空 ...

  5. 【数据结构与算法】数组与链表

    数组的定义和特性 数组(Array)是一种线性表数据结构.它用一组连续的内存空间,来存储一组具有相同类型的数据. 线性表(Linear List):数组.链表.队列.栈 非线性表:树 图 连续的内存空 ...

  6. c++使用单向链表存储一组有序数据_《一起学习java和数据结构》系列-数组和链表...

    数组 数组是一个线性表数据结构.它用一段连续的内存地址空间,来存储一些相同类型的数据. 从上面的定义,我们不难看出几个关键词. 线性表:顾名思义,线性表就是数据排列成一条线的数据结构.每一个线性表只有 ...

  7. 数据结构和算法详解(二)——线性表(数组、链表、栈、队列)

    一.数组 线性表:   线性表就是数据排成像一条线一样的结构.每个现行表上的数据最多只有前和后两个方向.常见的线性表结构:数组,链表.队列.栈等. 什么是数组: 数组(Array)是一种线性表数据结构 ...

  8. 数据结构各结构特点(数组、链表、栈、队列、树)

    目录 一.数组 二.链表 三.栈 四.队列 五.树 1.二叉树 2.二叉查找树 3.平衡二叉树(AVL树) 4.红黑树 六.总结: 1.红黑树和平衡二叉树的区别: 2.为什么有了数组和链表还要引入二叉 ...

  9. C++:数组、链表与哈希表

    文章目录 数组和链表 数组 什么是数组? 访问数组元素 可变长的动态数组:vector Vector基本用法 链表 什么是链表? 链表的操作 双向链表(list) list的成员函数 哈希表 什么是哈 ...

最新文章

  1. 【VMCloud云平台】SCCM(四)域内推送代理
  2. SVN linux 服务器端配置
  3. 36 ES5中新增的方法
  4. 用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表
  5. JavaScript-数据类型
  6. 从零开始写项目第八篇【将未完成的项目发布在Tomcat上】
  7. 安装Redis教程(详细过程)
  8. Android 签名文件的sha1值查看
  9. KEIL出现ERROR:L6002U时处理方法
  10. Linux:CentOS 7 解压 7zip 压缩的文件
  11. SSM整合——简单的小项目实战
  12. 如何使div 上下左右居中 css
  13. 西门子840d高级编程手册_840D NC 高级编程简单介绍
  14. 创建自己的个人网站(一)
  15. 求数组内子数组最大的和(Maximum Subarray )
  16. python小游戏之垃圾分类
  17. Mysql多表查询效率的研究(一)
  18. cpld xilinx 定义全局时钟_FPGA/CPLD设计工具:Xilinx ISE 5.x使用详解
  19. 中医药文化代表东方的思维方式是智慧结晶
  20. 区位码、国标码、机内码、GBK

热门文章

  1. c++中opencv对图片旋转镜像并调整尺寸显示
  2. [转载]tensorflow二次开发
  3. 我的世界自动生成服务器主城指令,我的世界生成主城的指令
  4. win10计算机中删除桌面,win10系统电脑桌面壁纸历史记录怎么删除
  5. vrchat新手教程_VRChat简易教程2-创建一个最基本的世界(world)
  6. cmd返回上一级和根目录
  7. 一本通1375:骑马修栅栏(fence)
  8. android 获取屏幕旋转方向,android获取手机屏幕尺寸和旋转方向
  9. Labelme标注流程
  10. 双向广搜(DBFS)