归并排序(Merge Sort)

归并排序算法详解(Merge Sort)

一、简介

归并排序(Merge Sort)是一种分治法(Divide and Conquer)思想的经典应用,是一种稳定的排序算法。它通过将数组递归地拆分为两个子数组,对子数组分别进行排序,然后将它们合并成一个有序数组。归并排序的时间复杂度为 O(nlog⁡n)O(n \log n)O(nlogn),其中 nnn 是待排序元素的数量。

二、算法原理

归并排序的基本思想是将数组不断分成两个小部分,直到每个部分只有一个元素,然后再通过合并操作把这些部分重新组合成一个有序的数组。其过程主要分为两个步骤:

  1. 拆分(分治):将数组从中间位置不断递归拆分,直到拆分到只有一个元素为止。
  2. 合并:将已经排好序的子数组两两合并,直到所有子数组合并完成,形成最终的有序数组。

整个过程可以通过递归的方式实现。因为归并排序将问题不断地分解成子问题,递归的深度为 log⁡n\log nlogn,每次合并两个子数组的时间为 O(n)O(n)O(n),因此总的时间复杂度为O(nlog⁡n)O(n \log n)O(nlogn)

三、归并排序的工作流程

  1. 分割数组:将一个未排序的数组不断地二分,直到无法继续分割(即每个子数组只有一个元素)。
  2. 合并数组:将两个已经排序的数组合并成一个有序的数组。合并时,依次比较两个数组的当前元素,取较小的元素放入结果数组中,直到其中一个数组元素全部取完,最后再将另一个数组中剩余的元素依次放入结果数组。
工作流程图解

假设我们有一个数组:[8, 3, 5, 4, 7, 1, 9, 2]。归并排序的流程如下:

  1. 将数组递归拆分:
    [8, 3, 5, 4, 7, 1, 9, 2]
    拆分成
    [8, 3, 5, 4] 和 [7, 1, 9, 2]
    继续拆分:
    [8, 3] [5, 4] 和 [7, 1] [9, 2]
    最终拆分为:
    [8] [3] [5] [4] [7] [1] [9] [2]
    
  2. 从最小的子数组开始合并:
    合并 [8] 和 [3] 得到 [3, 8]
    合并 [5] 和 [4] 得到 [4, 5]
    合并 [7] 和 [1] 得到 [1, 7]
    合并 [9] 和 [2] 得到 [2, 9]
    
  3. 继续合并:
    合并 [3, 8] 和 [4, 5] 得到 [3, 4, 5, 8]
    合并 [1, 7] 和 [2, 9] 得到 [1, 2, 7, 9]
    
  4. 最后合并两个大数组:
    合并 [3, 4, 5, 8] 和 [1, 2, 7, 9] 得到 [1, 2, 3, 4, 5, 7, 8, 9]
    

最终,排序后的数组为 [1, 2, 3, 4, 5, 7, 8, 9]

四、代码实现

这里的代码实现我们分为了python实现与C++实现

  • 实现包括就地排序和非就地排序
就地排序非就地排序
直接对传入数组本身进行排序,且不反回新的数组重新开辟空间保存并返回排序后的数组

以下是用 Python 实现归并排序的代码:

就地排序
def merge(be,en,mid,arr):
    i = be
    j = mid + 1
    t = []
    while i <= mid and j <= en:
        if arr[i] <= arr[j]:
            t.append(arr[i])
            i += 1
        else:
            t.append(arr[j])
            j += 1
    if i <= mid:
        t.extend(arr[i:mid+1])
    if j <= en:
        t.extend(arr[j:en+1])
    i = be
    j = 0
    while j < len(t):
        arr[i] = t[j]
        i += 1
        j += 1

def merge_sort(be, en, arr):
    if be < en:
        mid = (be + en) // 2
        merge_sort(be,mid,arr)
        merge_sort(mid+1,en,arr)
        merge(be,en,mid,arr)

arr = [4,5,6,1,2,3,9,6,3,65,12,4,54,3]
merge_sort(0, len(arr)-1, arr)
print(arr)
非就地排序
import os
import sys

# 请在此输入您的代码
def merge(arr1,arr2):
  len1 = len(arr1)
  len2 = len(arr2)
  res = []
  i = j = 0
  while i < len1 and j < len2:
    if arr1[i] < arr2[j]:
      res.append(arr1[i])
      i += 1
    else:
      res.append(arr2[j])
      j += 1
  if i < len1:
    res.extend(arr1[i:])
  elif j < len2:
    res.extend(arr2[j:])
  return res 

def m_sort(l,r,arr):
  if l < r:
    # 取中点并切出新的数组
    mid = int((l+r)/2)
    arr1 = arr[l:mid+1]
    arr2 = arr[mid+1:r+1]
    # print(l,r,mid)
    # print(arr1,arr2)
    # 新分配的数组重新分割,这里要注意下标是从0-len(new_arr)
    left_arr = m_sort(0,len(arr1)-1,arr1)
    right_arr = m_sort(0,len(arr2)-1,arr2)
    return merge(left_arr,right_arr)
  else:
    # print(f"now is l >=r and arr is:{arr}")
    return arr

a = [4,5,6,1,2,3,9,6,3,65,12,4,54,3]
print(m_sort(0,len(a)-1,a))

以下是用 C++ 实现归并排序的代码:

#include<bits/stdc++.h>
using namespace std;

int n,Sort_begin,Sort_end,cnt;

void merge(int *a,int s,int m,int t);
//void merge_pass(int *a,int len);
void merge_sort(int *a,int be,int en);

int main(){
    cin>>n;
    int a[n];
    for(int i=0;i<n;i++) cin>>a[i];
    merge_sort(a,0,n-1);
    for(int i=0;i<n;i++)
        cout<<a[i]<<" ";
    cout<<endl;
    cout<<cnt<<endl;
    return 0;
}
void merge(int *a,int s,int m,int t){
    int p[t-s+1];
    int i=s,j=m+1,index=0;
    while(i<=m&&j<=t){
        cout<<"mid is:"<<m<<endl;
        if(a[i]<=a[j]){p[index++]=a[i]; i++;}
        else{p[index++]=a[j]; j++; cout<<"mid is:"<<m<<" i is:"<<i<<endl; cnt+=m-i+1;}
    }
    //这里为什么是mid-i+1呢,因为前后的序列已经是有序的了,如果前面的序列出现某一位大于后面的序列,那么从这一位开始
    //后面的都将大于后面的序列,所以个数自然而然的就出来了,mid-i+1即个数
    while(i<=m){p[index++]=a[i]; i++;}
    while(j<=t){p[index++]=a[j]; j++;}
    for(int i=s,j=0;i<=t;i++,j++) a[i]=p[j];
}
//void merge_pass(int *a,int len){
//    int i;
//    for(i=0;i+2*len-1<n;i=i+2*len){
//        merge(a,i,i+len-1,i+2*len-1);
//    }
//    if(i+len<n) merge(a,i,i+len-1,n-1);
//}
void merge_sort(int *a,int be,int en){
   if(be<en){
       int m=(be+en)/2;
       merge_sort(a,be,m);
       merge_sort(a,m+1,en);
       merge(a,be,m,en);
   }
}
/*
10
123 345 56765 2342 1231 4564 2342 34564 23432 456
 */
解释:
  1. xxx_sort 函数首先通过递归将数组分割为更小的部分,然后调用 merge 函数来合并两个有序的数组。
  2. merge 函数中使用了双指针来逐一比较两个子数组中的元素,将较小的元素放入结果数组中。

五、复杂度分析

  • 时间复杂度:归并排序的时间复杂度是 O(nlog⁡n)O(n \log n)O(nlogn)。在每次递归中,数组被分成两半,递归的深度是 log⁡n\log nlogn,而在每一层递归中,合并两个子数组的时间是线性的 O(n)O(n)O(n),因此总的时间复杂度是 O(nlog⁡n)O(n \log n)O(nlogn)

  • 空间复杂度:归并排序需要额外的空间来存储中间结果,空间复杂度为 O(n)O(n)O(n),其中 nnn 是数组的长度,因为需要开辟额外的数组来存放合并后的结果。

六、归并排序的优缺点

优点:
  1. 稳定性:归并排序是一种稳定的排序算法,能够保持相同元素之间的相对位置不变。
  2. 时间复杂度优良:在所有情况下(无论是最优、最坏还是平均情况),归并排序的时间复杂度始终是 O(nlog⁡n)O(n \log n)O(nlogn)
  3. 适用于大数据排序:由于时间复杂度较低且具有稳定性,归并排序在处理大规模数据集时表现优越。
缺点:
  1. 空间复杂度较高:归并排序需要额外的 O(n)O(n)O(n) 空间来存储合并后的结果,这在一些内存敏感的环境下可能是一个问题。
  2. 递归实现可能导致栈溢出:在某些编程语言中,如果递归深度过大,可能会导致栈溢出。

七、案例分析

案例 1:已排序数组

假设输入数组是已经排序的:[1, 2, 3, 4, 5, 6, 7, 8]。归并排序会依然进行分割和合并,虽然每次合并时数组已经是有序的,但算法的整体复杂度仍然是 (O(n \log n))。

案例 2:逆序数组

如果输入是一个逆序的数组:[8, 7, 6, 5, 4, 3, 2, 1],归并排序也会首先将数组分成小的子数组,然后逐步进行合并。由于归并过程在合并时依然可以逐个比较元素,算法的时间复杂度依然是 O(nlog⁡n)O(n \log n)O(nlogn)

案例 3:随机数组

如果输入的是随机的数组:[4, 1, 6, 7, 3, 2, 8, 5],归并排序会先将数组拆分为 [4, 1, 6, 7][3, 2, 8, 5],再分别进行合并排序,最终得到有序的数组 [1, 2, 3, 4, 5, 6, 7, 8]。整个过程也是按照分治法的思想进行。

八、总结

归并排序是一种基于分治思想的稳定排序算法,它具有时间复杂度 O(nlog⁡n)O(n \log n)O(nlogn),在所有情况下表现一致,适合处理大规模数据集。尽管它的空间复杂度较高,但在某些大数据处理任务中,它依然是非常有效的选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SweetCode

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值