归并排序 Merge Sort

利用分治策略来解决排序问题

分治法 Divide and Conquer:

分治法的基本思想如下:

  • 分解:将原问题分解为若干子问题,这些子问题是原问题的规模较小的实例。
  • 解决:递归地求解这些子问题。当子问题规模足够小时,可直接求解。
  • 合并:合并子问题的解成原问题的解。

归并排序算法完全遵循分治模式。其操作步骤如下:

  • 分解:将原序列分解为两个各占 n/2n/2n/2 个元素的子序列。
  • 解决:使用归并排序递归地排序两个子序列。这里的策略是不断分解直到子序列只包含一个元素,此时子序列必然有序。
  • 合并:合并两个已排序的子序列以产生有序序列。

归并排序的思路:

如上所述,归并排序的基本思路是先将序列分为两部分,让左右两部分分别有序,然后合并。但是左右两部分的排序又重复上述过程,不断地分裂成两部分,直到最终都只有一个元素,此时所有的数组都是有序的。然后不断合并两个有序数组,直到最终成为一个整体。

其中的重点是合并步骤。假设数组为 AAA,ppp、qqq 和 rrr 是数组下标。现在子数组 A[p..q]A[p..q]A[p..q] 和
A[q+1..r]A[q+1..r]A[q+1..r] 都已排好序,我们要合并这两个有序子数组以形成单一的有序数组并替代当前的 A[p..r]A[p..r]A[p..r]。

还是以扑克牌为例,假设现在有两堆牌面朝上的牌,每堆都已从小到大排好序,最小的牌位于顶端。我们每一次都从两堆牌的顶上选择更小的那一张放入输出堆中,这样不断循环直到一个牌堆为空,然后将剩余的牌堆全部放入输出堆即可。合并过程用伪代码表示如下:

MERGE(A, p, q, r)
n1 = q-p+1
n2 = r-q
let L[1..n1=1] and R[1..n2+1] be new arrays
for i = 1 to n1L[i] = A[p+i-1]
for j = 1 to n2R[j] = A[q+j]
L[n1+1] = inf
R[n2+1] = inf
i = 1
j = 1
for k = p to rif L[i] <= R[i]A[k] = L[i]i = i+1else A[k] = R[j]j = j+1

值得注意的是可以使用哨兵来避免判断数组溢出。在第 9-10 行,我们将两个临时列表的最后一位元素设置为无穷大,当一个牌堆已经到底的时候,其哨兵牌将大于另一牌堆的所有牌(哨兵牌除外)。

之后我们就可以把上述的合并过程当作归并排序的一个子程序来使用。我们使用 MERGE−SORT(A,p,r)\mathrm{MERGE-SORT}(A, p, r)MERGE−SORT(A,p,r) 来对子数组 A[p..r]A[p..r]A[p..r] 排序:

MERGE_SORT(A, p, r)
if p < rq = floor((p+r)/2) // 向下取整MERGE_SORT(A, p, q)MERGE_SORT(A, q+1, r)MERGE(A, p, q, r)

第 4-5 行对原序列不断分解,当全部子序列都只包含一个元素时,就已经完成了对最小子序列的排序工作,然后就开始合并。

图示如下:
分解:

合并:

实现:

以 Java 为例,C++代码见文末。

首先实现并的部分,当我们有两个有序数组需要合并时(这里采取的策略是用一个额外的数组 temptemptemp 来保存排好序的数组,然后将这个 temptemptemp 内的值返回到原数组),我们需要三个指针 left,midleft, midleft,mid 和 rightrightright , 分别比较两个数组中较小的那个,保存到 temptemptemp 中。排序完毕后,将 temptemptemp 中的数据返回到原数组中。

这里用最后一次的排序举例:

// java/*** 合并数组* * @param arr   排序的原始数组* @param left  左边有序序列的初始索引* @param mid   中间索引* @param right 右边索引* @param temp  临时数组*/public static void merge(int[] arr, int left, int mid, int right, int[] temp) {int i = left; // 初始化 i ,左边有序序列的初始索引int j = mid + 1; // 初始化 j ,右边有序序列的初始索引int t = 0; // 指向temp数组的第一位// 将左右两边的数据填充到temp数组while (i <= mid && j <= right) {if (arr[i] <=  arr[j]) {temp[t] = arr[i];i++;}else {temp[t] = arr[j];j++;}t++;}// 当一边填充完毕时,另一边数组一般还有剩余// 将剩余元素全部填充到temp数组// 如果左边还有剩余while (i<=mid) {temp[t] = arr[i];i++;t++;}// 如果右边还有剩余while (j <= right) {temp[t] = arr[j];j++;t++;}//将temp数组元素拷贝到arr数组,并不是将temp全部拷贝到arr,而是截取部分t = 0;int tl = left;while (tl <= right) {arr[tl] = temp[t];tl++;t++;}}

然后是拆分部分,主要是递归

代码如下:

// javapublic static void mergeSort(int[] arr, int left, int right, int[] temp) {if (left < right) {int mid = (left + right)/2;// 向左分解mergeSort(arr, left, mid, temp);// 向右分解mergeSort(arr, mid+1, right, temp);// 合并merge(arr, left, mid, right, temp);}}

下图是逻辑顺序:

时间复杂度:

归并排序的时间复杂度主要考虑两个函数需要的时间,即 mergeSort 和 merge

merge 合并的时间复杂度为 O(n)O(n)O(n),因为全部都是长度为 nnn 的一次循环

利用之前说过的递归的时间复杂度公式,归并排序的时间复杂度为 T(n)=2(T(n/2))+O(n)T(n) = 2(T(n/2))+O(n)T(n)=2(T(n/2))+O(n)

这个递归式可以用递归树来解(假设解决最后一步子问题的时间是常数 ccc,则 nnn 个子问题花费的时间为 cncncn):

我们可以看到递归的每一层需要的时间都是 cncncn,总共有 logn+1logn+1logn+1 层,总的时间为 cn(logn+1)cn(logn+1)cn(logn+1) ,时间复杂度是 O(nlogn)O(nlogn)O(nlogn)。

空间复杂度:

归并排序在每一次合并时需要临时数组来储存排好序的序列,最后一次排序,要储存的数字最多,为 nnn,所以空间复杂度为 O(n)O(n)O(n)。

稳定性:

归并排序可以保证等值元素之间的顺序,因此是稳定的

相关章节
第一节 简述
第二节 稀疏数组 Sparse Array
第三节 队列 Queue
第四节 单链表 Single Linked List
第五节 双向链表 Double Linked List
第六节 单向环形链表 Circular Linked List
第七节 栈 Stack
第八节 递归 Recursion
第九节 时间复杂度 Time Complexity
第十节 排序算法 Sort Algorithm
第十一节 冒泡排序 Bubble Sort
第十二节 选择排序 Select Sort
第十三节 插入排序 Insertion Sort
第十四节 冒泡排序,选择排序和插入排序的总结
第十五节 希尔排序 Shell’s Sort
第十六节 快速排序 Quick Sort
第十七节 归并排序 Merge Sort

C++ 实现:

#include <iostream>
#include <vector>
using namespace std;/*** @brief Merge two ordered lists(nums[low..mid], nums[mid+1..high]).* * @param nums * @param low * @param mid * @param high */
void merge(vector<int>& nums, int low, int mid, int high) {int len1 = mid-low+1;int len2 = high-mid;vector<int> left(len1+1);vector<int> right(len2+1);for (int i=0;i<len1;i++) {left[i] = nums[i+low];}for (int j=0;j<len2;j++) {right[j] = nums[j+mid+1];}// 设置哨兵left[len1] = INT_MAX;right[len2] = INT_MAX;// 合并 int i = 0;int j = 0;for (int k=low;k<=high;k++) {if (left[i] <= right[j]) {nums[k] = left[i++];} else {nums[k] = right[j++];}}}void mergeSort(vector<int>& nums, int low, int high) {if (low < high) {int mid = (high-low)/2+low;mergeSort(nums, low, mid);mergeSort(nums, mid+1, high);merge(nums, low, mid, high);}
}// test
int main(int argc, char const *argv[])
{vector<int> nums = {4,7,2,9};mergeSort(nums, 0, nums.size()-1);for (auto num : nums) {cout << num << endl;}return 0;
}

算法与数据结构(归并排序)相关推荐

  1. python算法与数据结构-归并排序算法

    代码如下所示: # -*-coding=utf-8-*- def merge_sort(alist):"""归并排序"""n = len(a ...

  2. Caché 算法与数据结构

    第一章 Caché 算法与数据结构 基础和概念 ☆☆☆☆☆ 第二章 Caché 算法与数据结构 数组原理 ☆☆☆☆☆ 第三章 Caché 算法与数据结构 链表原理 ☆☆☆☆☆ 第四章 Caché 算法 ...

  3. Hark的数据结构与算法练习之归并排序

    算法说明: 归并排序的思路就是分而治之,将数组中的数字递归折半进行排序. 递归到最底层就只剩下有两个数字进行比较,再从底层往下进行排序合并.最终得出结果. 同样,语言描述可能对于不知道这个算法的人来说 ...

  4. 维基百科上的算法和数据结构链接很强大

    突然发现维基百科上的算法和数据结构比百度百科强多啦,图文并茂. 其实这个网站不错:http://www.sorting-algorithms.com 冒泡排序: bubble冒泡的意思 http:// ...

  5. GitHub标星3w+的项目,全面了解算法和数据结构知识

    作者 | 程序员小吴 来源 | 五分钟学算法(ID: CXYxiaowu) 导语:今天分享一个开源项目,里面汇总了程序员技术面试时需要了解的算法和数据结构知识,并且还提供了相应的代码,目前 GitHu ...

  6. python数据结构推荐书-「算法与数据结构」从入门到进阶吐血整理推荐书单

    推荐一下「算法与数据结构」从入门到进阶的书单. 一.入门系列 这些书籍通过图片.打比方等通俗易懂的方法来讲述,让你能达到懂一些基础算法,线性表,堆栈,队列,树,图,DP算法,背包问题等,不要求会实现, ...

  7. 【算法与数据结构】堆排序是什么鬼?

    排序算法相必大家都见过很多种,例如快速排序.归并排序.冒泡排序等等.今天,我们就来简单讲讲堆排序. 在上一篇中,我们讲解了二叉堆,今天的堆排序算法主要就是依赖于二叉堆来完成的,不清楚二叉堆是什么鬼的, ...

  8. 利用for循环调用插入方法批量插入 一条失败_算法与数据结构(1):基础部分——以插入排序为例...

    本文将会以插入排序为例,介绍算法与数据结构的基础部分. 插入排序 排序可以说是整个算法中最为基础,最为重要的一部分,而插入排序正是排序算法中最简单的一种解决办法. 什么是排序问题? 输入:n个数的一个 ...

  9. 算法与数据结构c语言版PPT,C语言算法与数据结构.ppt

    C语言算法与数据结构.ppt 第十二章 算法与数据结构12.1 算法的基本概念,该节知识点所占试题比重为12,属于重点考查对象,基本上每次必考,主要考查算法的定义和对算法复杂度的理解.历次试题分值在0 ...

  10. 算法与数据结构学习资料及面试

    from 算法之美作者:  http://blog.csdn.net/tiandijun/article/details/50550056 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录( ...

最新文章

  1. 关于hp惠普笔记本电脑清洗(真的要水洗哟)
  2. 水果电池打造柠檬电动汽车!
  3. MySQL连表分组统计使用count查询出数据不准确问题解决方案
  4. 【安全牛学习笔记】思路、身份认证方法、密码破解方法、字典
  5. 将信息从个人计算机传递到中央,上载-常识-工控百科-工控家
  6. python打印当前文件的绝对路径,并解决打印为空
  7. @ManyToOne和@OneToMany 注解
  8. Topological Sorting(拓扑排序)
  9. egg结合mysql如何做数据返回_egg-mysql使用体验和笔记
  10. 信用评分卡 (part 3of 7)
  11. Java案例实现用户登录
  12. 一起探索云服务之云数据库
  13. url指定服务器是什么意思,URL是什么意思?网页的URL是什么意思?
  14. chrome插件之——Enhanced Github和Octotree
  15. 缓解精神内耗的“马原疗法”
  16. 三位代表中国科技圈的85后技术男,他们眼中的AI是什么?
  17. python内置函数有哪些_Python 中的内置函数(一)
  18. 南通青鸟第一天学习心得
  19. “技术男”升为“管理者”,角色一定要转变,有好方法吗?
  20. 争对让望对思野葛对山栀注解_笠翁对韵·上卷·四支在线阅读-翻译及赏析

热门文章

  1. php 接口数组排序,php 数组排序
  2. Tensorflow实现MNIST数据自编码(2)
  3. pytorch离线安装(探索尝试版本)
  4. 文巾解题 181. 超过经理收入的员工
  5. 【数学建模】MATLAB应用实战系列(九十)-变异系数法应用案例(附MATLAB和Python代码)
  6. TensorFlow在windows 下的安装
  7. NLP通用模型decaNLP诞生,一个模型搞定十大自然语言常见任务
  8. 概率主题模型简介 Introduction to Probabilistic Topic Models
  9. 【android-tips】关于string.xml的使用
  10. Redis分布式锁的正确实现方式(Java版)