数据结构与算法:二路归并排序/合并排序

  • 概述
  • 图解
  • 设计代码
  • 实现
  • 复杂度分析
  • 全部代码

概述

  • 二路归并排序,又称合并排序。
  • 假设我们有这样一两个数组,A[1,4,5],B[2,3,7]。这两个数组是有序的。我们要将这两个数组,合并成为一个有序的数组,我们可以这样,将A B的第一个元素拿出来比较。小的元素提出来,也就是1,然后将剩下来的元素A[4,5],B[2,3,7],继续按照刚刚的方法,提出2,新数组就是[1,2]剩下A[4,5],B[3,7]。继续执行这样的操作。最后得出来了一个有序的数组 [1,2,3,4,5,7]。我们暂且叫他卡牌算法(因为在讲这个的时候,算法老师举了一个有两堆有序的卡牌,如何合并为一个有序卡牌的例子)
  • 因此,我们可以得知,我们只要有两个有序数组,我们就可以在时间复杂度为O(n)的情况下,把他们排序成一个有序的数组。
  • 所以说,在对于任意一个数组而言,我们只要找到他最小的有序部分,然后两两合并,两两合并,直到得到一个完全有序的新数组。那么,对于一个任意的数组,他的最小的有序部分是什么呢。
  • 因为数组是任意的,所以我们只敢肯定的说,最小的有序部分,肯定是单一一个元素,他肯定是有序的,因为只有他一个。比如A[4,1,2],他的最小有序部分有三份,[4],[1],[2]。

图解

  • 假设我们有这样一组数据 A[9] = {4,3,6,7,9,1,2},他在内存中这样排序

  • 我们首先将数组分为如图的部分。将他们划分为最小的部分,最小的部分我们称他为一个数组(大小为1),而不是一个元素,所以,图中划分完后有7个数组,且每个数组是有序的(因为只有一个元素)。分别为A[0…0],A[1…1]。。。

  • 也就是说,这7个小数组,大小都是1,然后,我们将相邻的数组,两两进行有序的合并,使得合并后的数组,仍然有序。

  • 在对两个有序数组合并的时候,采用的是卡牌算法。

  • 一轮排序后,继续重复执行上述操作:将相邻的数组,两两进行有序的合并,使得合并后的数组,仍然有序。

  • 两轮合并结束后,得到了如图的结果。
  • 整个过程如下
    -

设计代码

  • 第一步、首先要将数组进行划分,设待排序的数组是A[SIZE]。从图可知,划分形式如同一棵二叉树。我们每次从数组中间开始划分。

    • 设待划分的数组是A{ low…high }(是A中的一小段)。划分点也就是他的中点,mid = (low+high)/2
    • 若数组段只剩一个元素,比如A[5…5],划分出来也是(5+5)/2 = 5 ,A[5…5]也是他本身。
    • 若数组段是奇数项。比如A[3…5],(3+5)/2 = 4 ,划分为了A[3…4] A[5…5]
    • 若数组段是偶数项。比如A[2…5],(2+5)/2 = 3(因为是int),划分为了A[2…3]、A[4…5],均分
  • 第二步、划分必定是一个递归的操作。因此设计一个类似于二叉树遍历的递归代码。
    • 函数名为mergeSort( A[] ,int low , int high),每次对A[low…high]进行划分,
      划分为A[low…mid]、A[mid+1…high],然后再对这两段数组进行递归的划分。
    • 划分到单一元素的时候,进行合并操作。
  • 第三步、合并操作。对于任意两个有序的数组,我们要将他们合并,利用概述中的卡牌算法。
    • 在合并的时候,比如我们对A[2,4,6,1,3,5]合并,在中间划分,分成了两个小数组,这时候,在原址上合并,会造成数据丢失,因此,我们需要一个辅助数组B,和A一模一样,在B的基础上进行判断操作,在A的基础上进行排序操作。
    • 情况1 两个有序数组,刚好全部排序好。
    • 情况2 其中一个数组一个元素都没有了,但是另一个数组里,还有很多元素,这种情况下,这个数组里剩余的元素肯定都是大于已经排序好的那一部分(默认从小到大)。
      比如 A[1,2,4] B[3,5,6]
      当排序到[1,2,3,4]的情况下,A已经没了,但是B还有[5,6]这两个元素,这两个元素肯定是比所有的已排序的大的,直接接到已排序的后面就好。

实现

  • 划分函数
/*这个函数的作用就是对数组进行一个递归
*把数组划分为最小块 然后进行
合并排序->合并排序->合并排序。
*/
void mergeSort(ElemType A[],int low,int high){/*递归的边界条件是,原数组已经被划分为一个一个单独的数了。*也就是low = high的情况。 就会跳出递归。*/ if(low < high){ //划分规则 中点 int mid = (low + high)/2; mergeSort(A,low,mid);mergeSort(A,mid+1,high);//一次划分 一次合并merge(A,low,mid,high);        }
}
  • 合并函数
/*这个函数的作用是:
*将A[low..mid] 和 A[mid+1...high]
*这两段数据 进行合并排序 (卡牌算法)
*这里需要一个临时数组 来存放 A[]
*/
void merge(ElemType A[],int low,int mid,int high){//B里暂存A的数据 for(int k = low ; k < high + 1 ; k++){B[k] = A[k]; } /*这里对即将合并的两个数组 *A[low..mid] 头元素 A[i]和 A[mid+1...high] 头元素  A[j] *进行一个头部的标记, 分别表示为数组片段的第一个元素 *k 是目前插入位置。 */ int i = low , j = mid + 1 , k = low;  //只有在这种情况下 才不会越界 while(i < mid + 1 && j < high + 1) {//A的元素暂存在B里,因为不能再A上原地操作,会打乱数据//这也是为什么二路归并排序(合并排序)空间复杂度是O(n)的原因 //我们这里把值小的放在前面,最后排序结果就是从小到大 if(B[i] > B[j]){A[k++] = B[j++]; }else{A[k++] = B[i++]; }      } //循环结束后,会有一个没有遍历结束的数组段。处理上文的情况2while(i < mid + 1) A[k++] = B[i++]; while(j < high + 1) A[k++] = B[j++];
}
  • 这样就完成了mergeSort 合并排序。

复杂度分析

  • 时间复杂度

    • 我们每次合并,都花费O(n)的时间,因为每次合并,都要遍历一下整个数组。
    • 一共画合并的次数,是树的高度。 对于长度是n的数组,划分成数,高度是logn
      设高度是h ,2^h = n ,h = logn(底数2省略了),所以一共合并h = logn次
    • 时间复杂度 O(nlogn)
  • 空间复杂度
    • 因为合并操作不能原地进行,所以需要一个辅助数组B 大小和A一样
    • 空间复杂度O(n)
  • 在测试数据为10万的情况下。
数据量100000
O(nlogn)归并排序花费时间---------------------0.031000
O(n^2)选择排序花费时间---------------------14.135000
普通排序花费时间是合并排序的455.967742倍
--------------------------------
Process exited after 14.21 seconds with return value 0
请按任意键继续. . .

学好算法 人人有责。

全部代码

  • 这里设置了随机数,只要在宏上修改就行。和普通的排序进行了比较
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define SIZE 100000
typedef int ElemType; void merge(ElemType A[],int low,int mid,int high);
void mergeSort(ElemType A[],int low,int high);
void selectSort(ElemType A[],int low,int high);ElemType *B = (ElemType*)malloc(sizeof(ElemType)*SIZE); int main(){printf("数据量%d",SIZE); srand(time(NULL));ElemType *A = (ElemType*)malloc(sizeof(ElemType)*SIZE); ElemType *A1 = (ElemType*)malloc(sizeof(ElemType)*SIZE); ElemType *A2 = (ElemType*)malloc(sizeof(ElemType)*SIZE);  //随机数产生的范围是0~19999 随机了1W个数据; for(int i = 0; i < SIZE; i++)A[i] = rand()%SIZE*2;for(int i = 0; i < SIZE; i++) {A1[i] = A[i];A2[i] = A[i]; }/*  printf("原数组---------------------\n"); for(int i = 0 ; i < SIZE ;i++){if(i%20 == 0)printf("\n"); printf("%5d ",A[i]);}*/ free(A);  printf("\n");     clock_t start,end; double total1,total2; start = clock();mergeSort(A1,0,SIZE-1);end = clock();total1 = (double)(end - start) / CLOCKS_PER_SEC;printf("O(nlogn)归并排序花费时间---------------------%f\n",total1);/*for(int i = 0 ; i < SIZE ;i++){if(i%20 == 0)printf("\n"); printf("%5d ",A1[i]);}*/      free(A1);    printf("\n"); start = clock();selectSort(A2,0,SIZE-1);end = clock();total2 = (double)(end - start) / CLOCKS_PER_SEC;printf("O(n^2)选择排序花费时间---------------------%f\n",total2);printf("普通排序花费时间是合并排序的%f倍",total2/total1); /*    for(int i = 0 ; i < SIZE ;i++){if(i%20 == 0)printf("\n"); printf("%5d ",A2[i]);}    */ free(A2);    return 0;
} /*这个函数的作用是:
*将A[low..mid] 和 A[mid+1...high]
*这两段数据 进行合并排序 (卡牌算法)
*这里需要一个临时数组 来存放 A[]
*/
void merge(ElemType A[],int low,int mid,int high){//B里暂存A的数据 for(int k = low ; k < high + 1 ; k++){B[k] = A[k]; } /*这里对即将合并的两个数组 *A[low..mid] 头元素 A[i]和 A[mid+1...high] 头元素  A[j] *进行一个头部的标记, 分别表示为数组片段的第一个元素 *k 是目前插入位置。 */ int i = low , j = mid + 1 , k = low; //只有在这种情况下 才不会越界 while(i < mid + 1 && j < high + 1) {//A的元素暂存在B里,因为不能再A上原地操作,会打乱数据//这也是为什么二路归并排序(合并排序)空间复杂度是O(n)的原因 //我们这里把值小的放在前面,最后排序结果就是从小到大 if(B[i] > B[j]){A[k++] = B[j++]; }else{A[k++] = B[i++]; } } //循环结束后,会有一个没有遍历结束的数组段。while(i < mid + 1) A[k++] = B[i++]; while(j < high + 1) A[k++] = B[j++];
}/*这个函数的作用就是对数组进行一个递归
*把数组划分为最小块 然后进行
合并排序->合并排序->合并排序。
*/
void mergeSort(ElemType A[],int low,int high){/*递归的边界条件是,原数组已经被划分为一个一个单独的数了。*也就是low = high的情况。 就会跳出递归。*/ if(low < high){ //划分规则 中点 int mid = (low + high)/2; mergeSort(A,low,mid);mergeSort(A,mid+1,high);//一次划分 一次合并merge(A,low,mid,high);        }
}
void selectSort(ElemType A[],int low,int high){//分割点 int i = low; while(i < high + 1){int min = A[i];int k = i; for(int j = i ; j < high + 1 ; j++){if(A[j] < min){min = A[j]; k = j;}} A[k] = A[i]; A[i] = min;i ++;}
} 

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

  1. 数据结构与算法:十大排序算法之归并排序

    数据结构与算法:十大排序算法之归并排序 package TopTenSortingAlgorithms;/*** 归并排序:Java** @author skywang* @date 2014/03/ ...

  2. 数据结构与算法 第八天常见排序+冒泡排序+快速排序+文件IO+大数据排序+文件合并

    数据结构与算法 第八天常见排序+冒泡排序+快速排序+文件IO+大数据排序+文件合并 第一章 冒泡排序 [1]Bubble_Sort.c 第二章 快速排序 [1]quick_sort.c 第三章 大数据 ...

  3. python程序结构有哪几种_Python数据结构与算法(几种排序)小结

    Python数据结构与算法(几种排序) 数据结构与算法(Python) 冒泡排序 冒泡排序(英语:Bubble Sort)是一种简单的排序算法.它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺 ...

  4. 数据结构与算法之归并排序

    数据结构与算法之归并排序 目录: 归并排序介绍 归并排序思想示意图 代码实现 1. 归并排序介绍 归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-a ...

  5. 一步一步写算法(之合并排序)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 前面一篇博客提到的快速排序是排序算法中的一种经典算法.和快速排序一样,合并排序是另外一种经常使 ...

  6. 数据结构与算法:十大排序算法之插入排序

    数据结构与算法:十大排序算法之插入排序 package TopTenSortingAlgorithms;import java.util.Arrays; import java.util.Scanne ...

  7. 数据结构与算法:十大排序算法之堆排序

    数据结构与算法:十大排序算法之堆排序 堆排序可以说是选择排序的优化 package TopTenSortingAlgorithms;import java.util.Arrays; import ja ...

  8. 数据结构与算法:十大排序算法之冒泡排序

    数据结构与算法:十大排序算法之冒泡排序 package array;import java.util.Arrays;//冒泡排序 //1.比较数组中两个相邻的元素,如果第一个数比第二个数大,我们就交换 ...

  9. 《数据结构与算法》实验:排序算法实验比较——选择排序 堆排序

    <数据结构与算法>实验和课程Github资源 <数据结构与算法>实验:线性结构及其应用--算术表达式求值 <数据结构与算法>实验:树型结构的建立与遍历 <数据 ...

  10. 数据结构与算法(一)——排序

    虽然之前学过数据结构,但是已时隔四年,大概四月份复习了一遍,但是很多概念也是一知半解,所以重新整理知识点和运行代码的方式来巩固知识. 引言 排序:是计算机程序设计中的一种重要操作,功能是将一个数据元素 ...

最新文章

  1. 深入浅出Spring Security(一):三句话解释框架原理
  2. Waymo向客户发邮件,宣布纯无人驾驶汽车即将上路
  3. boost::lambda::ret用法的测试程序
  4. 栈的基础概念与经典题目(Leetcode题解-Python语言)
  5. 烟草局计算机笔试,2020年广西南宁烟草局什么时候笔试?
  6. animate css3 应用的借鉴,一个同事写的JS
  7. python 套接字 struck_Python socket粘包问题(最终解决办法)
  8. java xml 画表格_用js+xml自动生成表格的东西
  9. 大数据之-Hadoop伪分布式_配置文件说明---大数据之hadoop工作笔记0029
  10. SharePoint 解决管理员密码修改后的问题
  11. CentOS6.5安装telnet命令
  12. 阿里云服务器遭ddos攻击防御案例
  13. win10发送到桌面快捷方式没了
  14. Chrome调试工具使用及waterfall含义详解
  15. Arturia Sound Explorers Collection Belledonne现已上市
  16. 新版天猫刷红包js代码以及使用方式增加砸金砖代码
  17. java课题背景,办公自动化系统论文-课题研究的背景和意义及国内外发展状况.doc...
  18. uIP各部分协议代码的分析
  19. 关于西数黑盘那些事 原来这样滴 小伙伴们都惊呆了
  20. Linux解压tar.gz文件

热门文章

  1. java基础-面向对象
  2. 信息安全等级保护一到三级涉及到的网络安全设备
  3. 基于VB的员工请假管理系统设计与实现
  4. Unity3D物理渲染算法研究【PBR】
  5. 北京市城八区廉租住房和经济适用住房保障家庭收入、住房、资产标准已确定
  6. Mac 软件汉化教程(一)
  7. Java实现首字母转大写、小写StringUtils.capitalize
  8. 这“⼀⼝”和那“一口”有什么不同,关于unicode一个小知识
  9. abb机器人伺服电机报闸是什么_什么是抱闸电机
  10. 花书笔记1——向量乘法、矩阵乘积(相乘)、内积、点积都是什么、Python代码实现、区别及联系