美文网首页
归并排序

归并排序

作者: _Cappuccino_ | 来源:发表于2019-08-23 16:11 被阅读0次

综述

归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.
将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序.
若将两个有序表合并成一个有序表,称为2-路归并.

算法描述

  1. 把长度为n的输入序列分成两个长度为n/2的子序列;
  2. 对这两个子序列分别采用归并排序;
  3. 将两个排序好的子序列合并成一个最终的排序序列.

示意图

归并排序动态示意图 归并排序静态示意图

原理

归并排序采用分而治之的原理
1.将一个序列从中间位置分成两个序列;
2.在将这两个子序列按照第一步继续二分下去;
3.直到所有子序列的长度都为1,也就是不可以再二分截止.
4.最后将两两合并成一个有序序列即可.

性质

排序类别:非交换排序
是否是稳定排序:稳定
是否是原地排序:否
时间复杂度:O(n*log2^n)
空间复杂度:递归方式为O(n), 非递归方式为O(1)
归并排序:递归方式牺牲空间换取时间的算法


详细分析

分组

归并

详解

补充说明

对于归并排序有几点说明:

  • 归并排序的时间复杂度是O(NLogN),空间复杂度是O(N).
  • 辅助数组是一个共用的数组.如果在每个归并的过程中都申请一个临时数组会造成比较大的时间开销.
  • 归并的过程需要将元素复制到辅助数组,再从辅助数组排序复制回原数组,会拖慢排序速度.

优化方式

1.和快速排序一样,对于小数组可以使用插入排序或者选择排序,避免递归调用.
2.在merge()调用之前,可以判断一下a[mid]是否小于等于a[mid+1].如果是的话那么就不用归并了,数组已经是有序的.原因很简单,既然两个子数组已经有序了,那么a[mid]是第一个子数组的最大值,a[mid+1]是第二个子数组的最小值.当a[mid]<=a[mid+1]时,数组整体有序.
3.为了节省将元素复制到辅助数组作用的时间,可以在递归调用的每个层次交换原始数组与辅助数组的角色.
4.在merge()方法中的归并过程需要判断i和j是否已经越界,即某半边已经用尽.可以用另一种方式,去掉检测是否某半边已经用尽的代码.具体步骤是将数组a[]的后半部分以降序的方式复制到aux[],然后从两端归并.对于数组{1,2,3}和{2,3,5},第一个子数组照常复制,第二个则从后往前复制,最终aux[]中的元素为{1,2,3,5,3,2}.这种方法的缺点是使得归并排序变为不稳定排序.

Python实现方式

def merge_sort1(lists):
    if len(lists) <= 1:
        return lists
    num = len(lists) // 2
    temp_left = lists[:num]
    temp_right = lists[num:]
    print('temp_left:', temp_left, '--', 'temp_right:', temp_right)
    left = merge_sort1(temp_left)
    right = merge_sort1(temp_right)
    return merge1(left, right)


def merge1(left, right):
    left_index, right_index = 0, 0
    result = []
    while left_index < len(left) and right_index < len(right):
        if left[left_index] <= right[right_index]:
            result.append(left[left_index])
            left_index += 1
        else:
            result.append(right[right_index])
            right_index += 1
    result += list(left[left_index:])
    result += list(right[right_index:])
    return result

dest = [5, 2, 7, 4, 8, 1, 6, 3]
result = merge_sort1(dest)
print('最后的结果是:', result)

'''
temp_left: [5, 2, 7, 4] -- temp_right: [8, 1, 6, 3]
temp_left: [5, 2] -- temp_right: [7, 4]
temp_left: [5] -- temp_right: [2]
temp_left: [7] -- temp_right: [4]
temp_left: [8, 1] -- temp_right: [6, 3]
temp_left: [8] -- temp_right: [1]
temp_left: [6] -- temp_right: [3]
最后的结果是: [1, 2, 3, 4, 5, 6, 7, 8]
'''

"""
非递归版本
非递归版本不需要额外的空间.直接在原数组上进行切割合并
"""


def merge2(seq, low, mid, high):
    left = seq[low: mid]
    right = seq[mid: high]
    left_index = right_index = 0
    result = []
    while left_index < len(left) and right_index < len(right):
        if left[left_index] <= right[right_index]:
            result.append(left[left_index])
            left_index += 1
        else:
            result.append(right[right_index])
            right_index += 1
    result += left[left_index:]
    result += right[right_index:]
    seq[low: high] = result


def merge_sort2(seq):
    i = 1  # i是步长
    while i < len(seq):
        low = 0
        while low < len(seq):
            mid = low + i  # mid前后均为有序
            high = min(low + 2 * i, len(seq))
            if mid < high:
                merge2(seq, low, mid, high)
            low += 2 * i
        i *= 2

dest = [5, 2, 7, 4, 8, 1, 6, 3]
result = merge_sort2(dest)
print('最后的结果是:', dest)

'''
最后的结果是: [0, 1, 2, 3, 4, 5, 6, 7, 8]
'''

C语言实现

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

//分组归并
void _Merge(int *a, int begin1, int end1, int begin2, int end2, int *tmp)
{
    int index = begin1;
    int i = begin1, j = begin2;
    //注意:当划分的区间足够小时,begin1==end1,begin2==end2
    while (i <= end1&&j <= end2){
        if (a[i]<=a[j])
            tmp[index++] = a[i++];
        else
            tmp[index++] = a[j++];
    }
    //将左边元素填充到tmp中
    while (i <= end1)
        tmp[index++] = a[i++];
    //将右边元素填充的tmp中
    while (j <= end2)
        tmp[index++] = a[j++];
    //将tmp中的数据拷贝到原数组对应的序列区间
    //注意:end2-begin1+1
    memcpy(a + begin1, tmp + begin1, sizeof(int)*(end2 - begin1 + 1));
}
//归并排序
void MergeSort(int *a, int left, int right, int *tmp)
{
    if (left >= right)
        return;
    assert(a);
    //mid将数组二分
    int mid = left + ((right - left) >> 1);
    //左边归并排序,使得左子序列有序
    MergeSort(a, left, mid, tmp);
    //右边归并排序,使得右子序列有序
    MergeSort(a, mid + 1, right, tmp);
    //将两个有序子数组合并
    _Merge(a, left, mid, mid + 1, right, tmp);
}
//打印数组
void PrintArray(int *a, int len)
{
    assert(a);
    for (int i = 0; i < len; i++)
        printf("%d ", a[i]);
    printf("\n");
}
int main()
{
    int a[] = { 10, 6, 7, 1, 3, 9, 4, 2 };
    int *tmp = (int *)malloc(sizeof(int)*(sizeof(a) / sizeof(int)));
    memset(tmp, -1, sizeof(a) / sizeof(int));
    MergeSort(a, 0, sizeof(a) / sizeof(int)-1, tmp);
    PrintArray(a, sizeof(a) / sizeof(int));
    return 0;
}




// 递归方式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
函数功能:合并
函数参数:
 arr: 目标数组
 start: 待合并段开始下标
 mid: 待合并段中间下标
 end: 待合并段结束下标
 */
void merge(int* arr, int start, int mid, int end)
{
    int len_l, len_r; //左右待合并区间的长度
    len_l = mid - start + 1;
    len_r = end - mid;
    int l[len_l], r[len_r]; //gcc, 两个临时数组,分别保存待合并的两个区间
    //int l[100], r[100]; //vc
    memcpy(l, arr + start, sizeof(int) * len_l);
    memcpy(r, arr + mid + 1, sizeof(int) * len_r);

    int i = 0, j = 0, k = start;
    while(i < len_l && j < len_r)
    {
        arr[k++] = l[i] < r[j] ? l[i++] : r[j++];
    }

    while(i < len_l)
    {
        arr[k++] = l[i++];
    }
}
/*
函数功能:归并排序
函数参数:
 arr: 待排序的数组
 start: 待排序数组开始下标
 end: 待排序数组结束下标
 */
void merge_sort(int* arr, int start, int end)
{
    if(start < end)
    {
        int mid = (start + end) / 2;
        //归
        merge_sort(arr, start, mid);
        merge_sort(arr, mid + 1, end);
        //并
        merge(arr, start, mid, end);
    }
}

int main()
{
    int arr[11] = {-1, 2, 4, -12, 4, 0, 0, 12, 23, -4, 7000};
    merge_sort(arr, 0, 10);
    for(int i = 0; i < 11; ++i)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}


// 非递归方式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*
函数功能:归并排序
函数参数:
 arr: 待排序的数组
 length: 该数组的长度
 */
void merge_sort(int* arr, int length)
{
    int step = 1; //归并区间步长
    int l[length], r[length]; //gcc, 两个临时数组,分别保存待归并的两个区间
    //int l[100], r[100]; //vc
    while(step < length)
    {
        int start = 0; //归并区间的开始下标
        while(start < length - step)
        {
            //归
            int len_l, len_r; //左右待归并区间的长度
            len_l = len_r = step;
            memcpy(l, arr + start, sizeof(int) * len_l);
            if(start + 2 * step > length)
            {
                len_r = length - start - step;
            }
            memcpy(r, arr + start + step, sizeof(int) * len_r);
            //并
            int i = 0, j = 0, k = start;
            while(i < len_l && j < len_r)
            {
                arr[k++] = l[i] < r[j] ? l[i++] : r[j++];
            }
            while(i < len_l)
            {
                arr[k++] = l[i++];
            }

            start += 2 * step;
        }
        step *= 2;
    }
}

int main()
{
    int arr[11] = {-1, 2, 4, -12, 4, 0, 0, 12, 23, -4, 7000};
    merge_sort(arr, 11);
    for(int i = 0; i < 11; ++i)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

相关文章

  • 排序算法

    约定 选择排序 冒泡排序 插入排序 希尔排序 归并排序1. 归并方法2. 自顶向下归并排序3. 自底向上归并排序 ...

  • 排序二:归并、快排

    文章结构 归并排序 快速排序 源码 1. 归并排序 1.1 什么是归并排序 归并排序的思想是:将待排序的区间平分成...

  • java归并排序

    归并排序什么是归并排序:图解归并排序归并排序有两种实现方式,一是基于递归,而是基于迭代1)基于递归的归并排序: 基...

  • 算法—排序篇2

    1、归并排序(Merging Sort) 归并排序(Merging Sort): 就是利用归并的思想实现排序⽅法....

  • 常见的排序算法(2)

    要点 快速排序 归并排序 1.快速排序 2.归并排序

  • 排序算法之归并排序

    归并排序(Merge Sort) 归并排序是利用归并的思想实现排序的方式,该算法采用的是经典的分治算法 归并排序过...

  • 算法 第二章第二部分笔记

    各种排序算法的性能特点 选择排序 插入排序 希尔排序 归并排序 本地归并排序 自底向上的归并排序 快速排序 三向切...

  • 归并排序(二路归并排序)

    归并排序的思路 归并排序是通过“归并”操作完成排序的,将两个或者多个有序子表归并成一个子表。归并排序是“分治法”的...

  • 算法排序之归并排序和快速排序

    归并排序和快速排序用的都是分治的思想,用递归的编程技巧来实现.咱们先来看归并排序. 归并排序 归并排序的核心思想就...

  • 基于左闭右开的乱序数组归并排序 2020-04-24(未经允许,

    归并排序代码模板 递归形式思路:二分nums数组后对nums的归并排序 = 对左侧数组归并排序+对右侧数组归并排序...

网友评论

      本文标题:归并排序

      本文链接:https://www.haomeiwen.com/subject/srqlsctx.html