归并和归并排序

  1. 归并操作:是将两个有序独立的文件合并成为一个有序文件的过程。
  2. 归并排序:和快速排序的过程相反,它是两个递归调用(排序子文件)后是一个归并的过程。

    快速排序时,先分解成两个子问题后是两个递归调用(排序子文件)的过程。

    • 归并操作

      • 1 基本的两路归并
      • 2 抽象原位归并
    • 归并排序
      • 1 自顶向下的归并排序
      • 2 自底向上的归并排序
      • 3 归并排序的性能特征
    • 归并排序的链表实现
    • 归并排序与快速排序对比

1. 归并操作

1.1 基本的两路归并

将两个已经有序的数组 a 和 b 合并成一个有序的数组 c .

归并操作的过程如下:

1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列

2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置

3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置

4. 重复步骤3直到某一指针到达序列尾

5. 将另一序列剩下的所有元素直接复制到合并序列尾

//将有序数组a,b合并成为一个有序数组c
void mergeAB0(Item c[], Item a[], int M, Item b[], int N)
{
    int k = 0;
    int i = 0, j = 0;
    while(k < M+N)
    {
        if(i == M) //a数组已完结
        {
            c[k++] = b[j++];
            continue;
        }
        if(j == N) //b数组已完结
        {
            c[k++] = a[i++];
            continue;
        }

        if(less(a[i], b[j]))
            c[k++] = a[i++];
        else
            c[k++] = b[j++];
    }
}

此归并是稳定的,它保持了相同关键字的相对顺序。

1.2 抽象原位归并

目标:将数组a[l],……,a[m]和a[m+1],……,a[r]合并成一个有序文件,结果保存在a[l],……,a[r].

实现:通过构建双向序列(bitonic顺序),省略了1.1中的检查是否到达数组尾部的测试操作

将第一个数组复制到辅助数组aux[]中,将第二个数组按倒序方式紧跟第一个数组复制到aux[]中。第一个for循环移动第一个数组,使i指向l;第二个for移动第二个数组,使j指向r,为归并做好准备。接着,在归并操作(第三个for),将最大的元素当做观察哨,不管它在哪一个数组。

//抽象原位归并算法:双向序列的归并算法,不稳定
void mergeAB(Item a[], int l, int m, int r)
{
    //构建双向序列
    int i, j, k;
    for(i = m+1; i > l; i--)
        aux[i-1] = a[i-1];
    for(j = m, k = r; j < r; j++, k--)
        aux[k] = a[j+1];
    //归并操作;此时,i=l,j=r
    for(k = l; k <= r; k++)
    {
        if(less(aux[i], aux[j]))
            a[k] = aux[i++];
        else
            a[k] = aux[j--];
    }
}

此归并是不稳定的。

2. 归并排序

分治策略(1)将原文件分解成大小相等的2个子文件;(2)将2个子文件分别排序;(3)将2个有序的子文件进行合并

2.1 自顶向下的归并排序

//自顶向下的merge sort
void merge_sort(Item a[], int l, int r)
{
    if(l >= r)
        return;
    //小文件改进
    // if(r-l < 10) {insertion_sort(a, l, r); return;}
    int m = (l+r)/2;
    merge_sort(a, l, m);
    merge_sort(a, m+1, r);

    mergeAB(a, l, m, r);
}

算法分析

(1)对长度为N的文件,需进行lgN(树的深度)趟二路归并,每趟归并的时间为O(N),故其时间复杂度无论是在最好情况下还是在最坏情况下均是O(NlgN)。

(2)需要一个辅助数组来暂存两有序子文件归并的结果,故其辅助空间复杂度为O(N),显然它不是就地排序。

(3) 如果使用的归并算法是稳定的,排序算法就是稳定的。

2.2 自底向上的归并排序

每一个递归程序都有一个非递归程序与之对应。

原理:先求出小问题的解,在把它组合成较大问题的解。

(1) 将文件中的所有元素看作大小为1的有序子表,接着便利这些表进行1-1的归并,产生大小为2的有序子表;

(2) 然后遍历文件列表进行2-2归并,产生大小为4的有序子表

(3) 然后进行4-4归并,产生大小为8的有序子表,以此类推,直至整个表有序

通过在整个文件中进行多遍的m-m的归并,完成自底向上的归并排序,在每一遍中m加倍;仅当文件大小是m的偶数倍时,最后一个子文件的大小是m,否则最后的归并是m-x归并,x <= m

//自底向上 merge_sort

void merge_sort_BU(Item a[], int l, int r)
{
    int i, m;
    for(m = 1; m <= r-l; m *= 2) //控制每层子问题的大小
    {
        for(i = l; i <= r-m; i+= 2*m) //从最左侧,依次递增 2m
            mergeAB(a, i, i+m-1, min(i+2*m-1, r));
    }
}

性质 1:除了最后一个子文件外,所有子文件的大小都是2的幂,

性质 2:对N个元素的自底向上归并排序执行的遍数k,是使 2k>=N成立的最小值,即 lgN,也是N用二进制表示的位数。

2.3 归并排序的性能特征

运行时间与 NlgN 成正比,与输入无关;

使用额外的内存空间;

可以稳定实现。

3 归并排序的链表实现

4 归并排序与快速排序对比

  1. 递归实现中: 快速排序,在一个递归实现中,大多数工作(划分过程)在递归递归调用前完成;而归并排序是在递归之后。
  2. 非递归实现中: 快速排序必须使用栈,因为其保存了大的子问题,然后根据数据情况进行分解;归并排序的划分与数据无关。
  3. 稳定性: 稳定的归并操作,就能够保证稳定的归并排序;对基于数组的快速排序实现,将数组稳定的划分是不容易的,在递归之前,稳定性已经破坏,然而基于链表的快速排序是稳定的。
时间: 2024-10-07 00:02:51

归并和归并排序的相关文章

归并与归并排序算法

源代码如下: #include <stdio.h> #define maxN 20 void printArray (int a[]){ int i; for(i=0;i<13;i++) printf("%2d ",a[i]); printf("\n"); } //将有二个有序数列a[first...mid]和a[mid...last]合并. void mergeArray(int a[], int first, int mid, int last

归并排序算法--java

归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的.然后再把有序子序列合并为整体有序序列. 归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法(Divide and Conquer)的一个非常典型的应用. 将已有序的子序列合并,得到完全有序的序列:即先使每个子序列有序,再使子序列段间有序.若将两个有序表合并成一个有序表,称为2-路归并. 归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))

算法_归并排序

归并排序的基本思想是:将两个已经有序的数组,归并成为更大的有序数组.这种操作称为归并排序.要让一个数组排序,可以先递归的把它分成两半分别排序,然后将结果归并起来.归并排序能够保证对一个任意长度为N的数组排序所需时间和NlogN成正比. 基本的归并方法代码如下:该方法先将所有的元素复制到aux[]中,然后再归并到a[]中.方法在归并的时候,进行了四次条件判断:左半边用尽(取右半边的元素),右半边用尽(取左半边的元素),右半边的当前元素小于左半边的当前元素(取右半边的元素)以及右半边的元素大于等于左

复习数据结构:排序算法(四)——归并排序

基本思想:基于分治法,即把待排序的数组序列,分为若干个子序列,对每个子序列排序,然后再把所有有序的子序列合并为一个整体有序的序列.分析可知,如果拿任何一个元素作为子序列,那么所有子序列就已经是有序的,而归并排序的关键就在于如何合并,也就是"归并". 归并排序是外排序,稳定排序,时间复杂度是O(nlogn). 详细说归并排序的过程:1个元素的表总是有序的.所以对n个元素的待排序列,每个元素可看成1个有序子表.对子表两两合并生成n/2个子表,所得子表除最后一个子表长度可能为1 外,其余子表

排序问题-归并排序

归并排序是递归的一个典型应用,是用空间换时间的典型例子(需要一个临时数组存放以排序的序列).在各个排序算法中其比较的次数是最少的,在最坏的情况下的运行时间是O(NlogN). 归并排序是一个递归-合并的过程,基本操作是将两个已经排好序的表扫描一遍依次有序的存放到第三个表中.在递归的过程中,每次递归都是一次划分,将一个序列从中划分成两个子序列(2路归并),直到子序列中元素数为1,然后开始两两合并成有序的序列.基于这个想法引申到多路归并. 归并排序的过程: 初始序列: 申请临时数组: 开始递归: 递

经典排序—归并排序思想及实现

归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的.然后再把有序子序列合并为整体有序序列. 归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法(Divide and Conquer)的一个非常典型的应用. 将已有序的子序列合并,得到完全有序的序列:即先使每个子序列有序,再使子序列段间有序.若将两个有序表合并成一个有序表,称为2-路归并.归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))的

【自考】排序算法-插入、交换、选择、归并排序

碎碎念: 记得当初第一年的时候.接触算法.有那么两个视频.跳舞的.讲的是冒泡排序跟选择排序.当时看了好多遍终于懂了.这次多了一些算法.学起来也还好吧.咱是有基础的人.找到了以前的视频.有的就发了.没找到的就没法.其实算法并不难.绕绕就明白了.先别看代码- - 思维导图 插入排序 从头到尾巴.从第二个开始.向左进行插入.这里说的插入是指作比较.直到比较出比自己小的就插入到他的前面. 例子 1 7 4 8 6 5 插入排序 [1]7 4 8 6 5 [1 7] 4 8 6 5 [1 4 7]  8

Java排序算法(九):归并排序

归并排序(Merge)是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的.然后再把有序子序列合并为整体有序序列. 归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法(Divide and Conquer)的一个非常典型的应用. 将已有序的子序列合并,得到完全有序的序列:即先使每个子序列有序,再使子序列段间有序.若将两个有序表合并成一个有序表,称为2-路归并. 归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))

C语言归并排序

这篇文章是学习了小甲鱼-数据结构与算法结合自考教材编写出的代码,希望自己逐渐在算法造诣上能更上一层楼. 归并排序(递归实现) “归并”一词在中文含义中就是合并的意思,而在数据结构中的定义是将两个或者两个以上的有序表组合成一个新的有序表,就叫归并. 归并排序(Merge Sort)就是利用归并的思想实现的排序方法.它的原理是假设初始序列有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到⌈n/2⌉个长度为2或1的有序子序列:再两两归并,……,如此重复,直至得到一个长度