目录

归并排序

归并排序的思想

递归实现

非递归实现


归并排序

归并排序和之前讲的快速排序、希尔排序、堆排序一样,时间复杂度是O(N*logN)

它相对难理解一点,接下来我就从递归以及非递归两个方面详细介绍一下这种排序。

归并排序的思想

一个数组从中间将其分为左右两个区间,如果左右区间都是有序的,那么就可以进行归并,分别从两个区间取小的数尾插到新开辟的数组中,不断取小的尾插直到排完。

那么如果两个区间没有序呢?—— 那就再将这个问题划分子问题,左区间进行上述操作,右区间也进行上述操作......一直分治下去,直到左右区间只剩一个数,就划分为最小子问题,无需再往下分了,这个过程也就是递归的过程。

这就是归并排序的思想,划分子问题,分而治之。

递归实现

假设有一个数组a[10] ={9,6,5,3,8,7,1,2,0,4} 要将它排成升序,可以先找出数组的中间位置,将数组从中间分开,划为两个区间,分别将两个区间排成有序的

那如何将这两个区间排成有序的呢?————再重复上面步骤分别划分左右两个区间,再将其排成有序的,重复上述步骤......直到左右区间都分的只剩一个数,那也就可以认为它是有序的。整个过程就是一个递归调用的过程

大家觉得这个递归过程像什么呢?  是不是有点像二叉树。这里的归并递归过程有点类似二叉树里面求树的高度,先递归计算左右子树的高度,都是用的后序遍历。

先将大体框架完成一下:

void _MergeSort(int* a, int begin, int end,int* tmp)
{if (begin >= end)return;int mid = begin + (end - begin) / 2;_MergeSort(a,begin, mid,tmp);_MergeSort(a,mid+1,end,tmp);//归并
}void MergeSort(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}_MergeSort(a, tmp, 0, n-1);free(tmp);tmp = NULL;
}int main()
{int a[10] = { 9,6,5,3,8,7,1,2,0,4 };int n = sizeof(a) / sizeof(a[0]);MergeSort(a, n);for (int i = 0; i < n; ++i){printf("%d ", a[i]);}return 0;
}

归并排序一般用两个子函数,这样方便一些。

先计算中间位置,得出左右区间,对左右区间递归排序,直到各自只剩一个数时返回。

上面是局部递归展开图(归并过程没写),可借助展开图来理解递归过程。

接下来实现归并细节。

依据上图,先将左右区间的边界值表示出来:左区间 [begin,mid] 右区间 [mid+1,end]

找到每一组里面小的那个数,尾插到新开辟出的数组里,不断地取小的尾插,尾插一次就memcpy拷贝回原数组。

void _MergeSort(int* a, int begin, int end, int* tmp)
{if (begin >= end)return;int mid = begin + (end - begin) / 2;_MergeSort(a,begin, mid,tmp);_MergeSort(a,mid+1,end,tmp);//归并int i = begin;int begin1 = begin, end1 = mid,begin2 = mid + 1, end2 = end;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

 时间复杂度:O(N*logN), 递归深度为logN,每一层递归因为要遍历选小的数,所以是O(N),合起来就是O(N*logN)

空间复杂度:O(N),开辟了额外的tmp空间,这里不及快排,快排空间复杂度是O(logN)

非递归实现

在深度了解递归的情况下,我们也可以用非递归来实现。非递归实现还是有一定难度的,不太好理解,建议将递归展开图多画画,吃透了再实现非递归。

归并排序的非递归,我们不借助栈或队列等结构实现,直接用迭代手撕。

其本质和递归异曲同工,只是用循环完整的呈现了全部过程。

递归方法是一路往下走,到最底层只剩一个数归并,返回上一层有两个数,两两归并,依次往上返回。这里非递归相当于是反过来了,最开始一一归并,然后将有序的两两归并.......

大体思路简单,但是控制边界值挺麻烦,先实现大框架:

我们要一开始一一归并,再两两归并.......可以设定一个gap,让它一开始为1,每归并完一层就*2,

就可以实现控制了。

void MergeSortNon(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}int gap = 1;while (gap < n){for (int j = 0; j < n; j += gap * 2){int i = j;int begin1 = j, end1 = j + gap - 1,begin2 = j + gap, end2 = j + 2 * gap - 1;while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}gap *= 2;memcpy(a, tmp, sizeof(int) * n);}}free(tmp);tmp = NULL;
}

注意这里begin1 ,end1,begin2,end2的初始值。对照着图可以推出来。

这是基本框架,但是注意:上面数组里面个数正好为8,是2^3,当数组个数不是2^n时,情况就完全不同了。

比如个数为9,10时,如图:

比如这样两种情况就会出现问题。

以左图为例,对照代码看:当gap = 4时,j = 8,begin2 = 9,end2 = 9 越界了。

同理,右边也一样,其他情况下也会产生越界。为了方便观察什么情况下会越界,我们加入打印。

         int i = j;int begin1 = j, end1 = j + gap - 1,begin2 = j + gap, end2 = j + 2 * gap - 1;printf("[%d][%d],[%d][%d] ", begin1, end1, begin2, end2);

 当数组元素个数为2^n 时

 当数组元素个数为奇数个时

 当数组元素个数是偶数个但不为2^n 时

根据上图,我们把越界情况分为三种

1、第一组end1越界

2、第二组begin2,end2全部越界

3、第二组end2越界

针对上述三种情况进行修正,就可以避免越界实现非递归。

如果拷贝的地方是像上面一样全部归并完了再拷贝的,那就比较麻烦了。因为中间可能会拷回去越界的随机值,所以需要一一修正边界,不推荐这种拷贝方式。

可以将拷贝放到循环里,归并多少数据就拷贝多少数据,就不会产生越界拷贝随机数的情况了,也不需要一一修正边界。

如果是第一组end1越界,那他后面也不需要归并了,直接break出去就行。

如果是第二组begin2,end2全部越界,那也是一样,说明不需要归并,也直接break即可。

如果是第二组end2越界,那end2前面一个数还需要归并,此时修正一下end2,改成n-1。

void MergeSortNon(int* a, int n)
{int* tmp = (int*)malloc(sizeof(int) * n);if (tmp == NULL){perror("malloc fail");return;}int gap = 1;while (gap < n){for (int j = 0; j < n; j += 2 * gap){int begin1 = j, end1 = j + gap - 1,begin2 = j + gap, end2 = j + 2 * gap - 1;int i = j;//第一组end1越界if (end1 >= n){printf("[%d][%d]", begin1, n - 1);break;}//第二组全部越界if (begin2 >= n){printf("[%d][%d]", begin1, end1);break;}//第二组end2越界if (end2 >= n){printf("[%d][%d]", begin2, n - 1);//修正end2,继续归并end2 = n - 1;}printf("[%d][%d],[%d][%d] ", begin1, end1, begin2, end2);while (begin1 <= end1 && begin2 <= end2){if (a[begin1] <= a[begin2]){tmp[i++] = a[begin1++];}else{tmp[i++] = a[begin2++];}}while (begin1 <= end1){tmp[i++] = a[begin1++];}while (begin2 <= end2){tmp[i++] = a[begin2++];}//归并哪一段,拷贝哪一段memcpy(a + j, tmp + j, sizeof(int) * (end2 - j + 1));}printf("\n");gap *= 2;}free(tmp);tmp = NULL;
}

为了方便观察,这里添加几个打印

是不是和之前画图分析的过程一样呢。这就是非递归的玩法。

如果是一次拷贝的修正边界值,麻烦一点,大家可以去尝试一下。

归并排序详解(递归+非递归)相关推荐

  1. 归并排序过程实现c语言,C语言归并排序详解

    C语言归并排序详解 发布日期:2015-12-31 11:16 来源: 标签: 编程语言 C教程 C语言归并排序 C语言归并排序算法 本章我们主要学习C语言实现排序算法之归并排序,对归并排序的原理及实 ...

  2. 归并排序详解(Acwing 归并排序y总模板)

    归并排序详解 文章目录 归并排序过程 demo(完整y总模板) 结束语 每个人心里都有一团火,路过的人却只看见了烟.加油xdm!!! 归并排序过程 基础知识:我们知道归并排序也是一种基于分治法思想的排 ...

  3. 一文彻底搞定二叉树的前序、中序、后序遍历(图解递归非递归)

    前言 大家好,我是bigsai,在数据结构与算法中,二叉树无论是考研.笔试都是非常高频的考点内容,在二叉树中,二叉树的遍历又是非常重要的知识点,今天给大家讲讲二叉树的层序遍历. 这部分很多人可能会但是 ...

  4. java 建树源码_Java实现的二叉树常用操作【前序建树,前中后递归非递归遍历及层序遍历】...

    import java.util.ArrayDeque; import java.util.Queue; import java.util.Stack; //二叉树的建树,前中后 递归非递归遍历 层序 ...

  5. 快排递归非递归python_Python递归神经网络终极指南

    快排递归非递归python Recurrent neural networks are deep learning models that are typically used to solve ti ...

  6. 归并排序详解(python实现)

    归并排序详解(python实现) 因为上个星期leetcode的一道题(Median of Two Sorted Arrays)所以想仔细了解一下归并排序的实现.

  7. c语言折半查找递归程序,C语言数据结构中二分查找递归非递归实现并分析

    C语言数据结构中二分查找递归非递归实现并分析 前言: 二分查找在有序数列的查找过程中算法复杂度低,并且效率很高.因此较为受我们追捧.其实二分查找算法,是一个很经典的算法.但是呢,又容易写错.因为总是考 ...

  8. 二叉树的深度(前序 中序 后序 递归非递归搜素)、广度、搜索 C++

    a b c 使用 1 2 3 表示 /* 描述:二叉树的深度(前序 中序 后序 递归非递归搜素).广度.搜索 作者:jz 日期:20140819 */ #include<stdio.h> ...

  9. 二叉树——中序遍历(递归/非递归)

    中序遍历,即遍历顺序为:左节点.根节点.右节点. 二叉树节点: public class Node {public Node left;public Node right;public int val ...

  10. 树的深度 递归非递归实现

    树的深度 递归非递归实现 package com.dugstudio.SwordToOffer;import java.util.LinkedList;/*** 树的深度递归和非递归算法*/ clas ...

最新文章

  1. 改变自己,YT,吵架,和好,感冒,烦,新的项目,旧的垃圾,呵呵呵。。
  2. (翻译) MongoDB(2) 数据库和集合
  3. java/02/java运算符,java逻辑控制,java方法的定义及使用
  4. Ubuntu系统下环境变量那些事儿
  5. python机器学习案例系列教程——推荐系统
  6. maven 项目 spring mvc + jdbc 配置文件
  7. 语料库与python应用_语料库与Python应用/语料库翻译学文库
  8. 【面试经验】关于BERT,面试官们都怎么问
  9. Android 极光IM-基础篇
  10. 给MK802(USB大小的Android4.0小PC)引出串口信号,变成ARM开发版
  11. poi批量导入html,读取excel(POI)【转换为html】 - bcoffee的专栏 - 博客频道 - CSDN.NET...
  12. wxid中文是什么_wxid开头的微信号是什么意思?怎么添加微信好友
  13. 物联网技术应用在智慧医疗的应用案例
  14. android als传感器,环境光传感器(ALS)背光控制解决方案
  15. 武汉市企业研究开发中心备案
  16. 软件测试基础知识学习
  17. 该为您的HTTPS安全证书续期了
  18. 报头压缩-ROHC压缩系统
  19. Additive smoothing
  20. 58同城疑收购优信二手车:优势增补布局未来市场

热门文章

  1. 查看进程名称 linux,在Linux系统服务器按名称查找进程的命令
  2. (翻译)Fully Automated Deep Learning System for Bone Age Assessment
  3. 003_wz_bbk_Linux下GCC的安装
  4. 河北计算机对口高考数学考试题,2015年河北省对口高考数学试题(含答案)
  5. 华为走进成都 看智简网络如何彻底改变企业?
  6. AMM和ASMM理解
  7. 第10课:主流的分布式消息队列方案解读及比较
  8. ArduinoIDE初步开发ESP8266时钟
  9. win10环境下AndroidStudio输入法光标不跟随解决办法
  10. python如何撤销_python如何查看微信消息撤回