归并排序算法实现思想个人理解

技术交流QQ群:1027579432,欢迎你的加入!

欢迎关注我的微信公众号:CurryCoder的程序人生

一.原理

  • 假设初始待排序数据有n个,可以将n个数据看成n个独立的子序列,因此每个子序列的长度为1,然后两两合并,得到[n/2]个长度为2或1(注意如果n为奇数时,就会出现多出一个元素无法与其他元素合并)的有序子序列;再两两合并,一种重复下去,直到得到一个长度为n的有序序列为止,这种排序方法为2路排序方法。
    归并排序过程.jpg

二.递归方法来实现归并排序

  • 原理:使用递归方法来实现归并排序时,核心思想是两个有序子序列的合并,注意这里是有序子序列的合并,因此下面要做两件事,整个过程如下图所示:
    • (1)将待排序序列从中间一分为二,对左右两边再进行递归分割操作,得到n个相互独立的子序列;
    • (2)对n个独立的子序列递归的执行合并操作,最终得到有序的序列。


      递归方法实现归并排序.jpg
  • 程序的伪代码如下图所示,其中函数Merge_Sort()是统一的函数接口,便于用户调用;函数Msort()首先递归的得到n个独立子序列,然后使用Merge()函数实现有序子序列归并。


    Merge函数.jpg

    Msort函数.jpg

    Merge_Sort函数.jpg

三.非递归方法实现归并排序

  • 递归方法的不足:递归方法实现归并排序代码容易理解,但是递归容易浪费空间,比如上述递归方法实现归并排序的第二步中,每一步合并都必须创建一个临时数组TmpA,此数组用来暂存已经排好序的中间序列。然后等中间序列排好序后,再将临时数组TmpA中的结果重新导回到数组A中。接下来递归的执行上面的合并操作,直到序列完全排好序。但是,整个归并排序过程中的空间复杂度太大为O(n)。
  • 解决方法:非递归方法实现归并排序时,在第二步时可以采用下面的思想,不用在每一次归并时都创建一个临时数组TmpA,临时数组TmpA只被创建一次,每次执行完归并操作后,将归并的结果暂存在数组TmpA中,然后进行下一次归并操作时,先将TmpA中的上一次归并好的结果重新导回到数组A中,再进行归并操作。整个过程的空间复杂度将变小,不用每次操作都创建一个临时数组TmpA。当归并操作执行到最后一次时,如果此时已经排好序的序列在数组A中,则不用再重新导回到数组A中;如果此时已经排好序的序列在数组TmpA中,则在执行一次导回操作,将数组TmpA中的结果重新导回到数组A中,完成整个排序。这个过程的伪代码如下:


    非递归方法实现归并排序.jpg

四.两种方法实现的归并排序代码如下:

  #include <iostream>
  #include <malloc.h>

  using namespace std;

  #define N 9
  #define MAXSIZE 10

  typedef struct
  {
      int r[MAXSIZE + 1];
      int len;
  } Sqlist;

  void show(Sqlist L)
  {
      for (int i = 1; i <= L.len; i++)
          cout << L.r[i] << " ";
      cout << endl;
  }

  // 归并操作
  void Merge(int A[], int TempA[], int L, int LeftEnd, int RightEnd)
  {                // L:左边子序列的起点;LeftEnd:左边子序列的终点
      int k, j; // k是数组tempA左边起点,j是右边子序列的的起点,RightEnd:右边子序列的终点
      for (j = LeftEnd + 1, k = L; L <= LeftEnd && j <= RightEnd; k++)
      {
          if (A[L] < A[j])
              TempA[k] = A[L++];
          else
              TempA[k] = A[j++];
      }
      if (L <= LeftEnd)
      {
          for (int l = 0; l <= LeftEnd - L; l++)
              TempA[k + l] = A[L + l];
      }
      if (j <= RightEnd)
      {
          for (int r = 0; r <= RightEnd - j; r++)
              TempA[k + r] = A[j + r];
      }
  }
  // 递归方法实现
  // 归并排序整个操作
  void Msort(int A[], int TempA[], int L, int RightEnd)
  {
      int center;
      int TempA2[MAXSIZE + 1];
      if (L == RightEnd)
          TempA[L] = A[L];
      else
      {
          center = (L + RightEnd) / 2;
          Msort(A, TempA2, L, center);
          Msort(A, TempA2, center + 1, RightEnd);
          Merge(TempA2, TempA, L, center, RightEnd);
      }
  }

  // 统一函数接口
  void MergeSort(Sqlist *L)
  {
      Msort(L->r, L->r, 1, L->len);
  }

  // 非递归方法实现
  void MergePass(int A[], int TempA[], int length, int n)
  {
      // length:当前子序列的长度  n:待排序列中的元素个数
      int i = 1;
      while (i <= n - 2 * length - 1)
      { // 子序列的个数是偶数个
          Merge(A, TempA, i, i + length - 1, i + 2 * length - 1);
          i = i + 2 * length;
      }
      if (i < n - length + 1) // 归并最后两个子序列
          Merge(A, TempA, i, i + length - 1, n);
      else
      { // 最后只剩下一个子序列
          for (int j = i; j <= n; j++)
              TempA[j] = A[j];
      }
  }

  // 统一函数接口
  void MergeSort1(Sqlist *L)
  {
      int *TempA = (int *)malloc(L->len * sizeof(int));
      int length = 1; // 初始子序列的长度
  
      while (length < L->len)
      {
          MergePass(L->r, TempA, length, L->len);
          length *= 2;
          MergePass(TempA, L->r, length, L->len);
          length *= 2;
      }
  }

  int main()
  {
      Sqlist L;
      int d[N] = {50, 10, 90, 30, 70, 40, 80, 60, 20};
      for (int i = 0; i < N; i++)
          L.r[i + 1] = d[i];
      L.len = N;
      cout << "归并排序前(递归方法): ";
      show(L);
      cout << "归并排序后(递归方法): ";
      MergeSort(&L);
      show(L);
      cout << "-------------------------------------------------\n";
      cout << "归并排序前(非递归方法): ";
      show(L);
      cout << "归并排序后(非递归方法): ";
      MergeSort1(&L);
      show(L);

      return 0;
  }
  • STL实现
#include <iostream>

using namespace std;

// 归并排序:把数据分为两段,从两段中逐个选最小的元素移入新数据段的末尾。可从上到下或从下到上进行。

// 迭代方法实现
template<typename T>
void MergeSort(T arr[], int len){
    T* a = arr;  // 指针a指向原始数组
    T* b = new T[len];  // 指针b指向新创建的一个数组
    for(int seg=1; seg < len; seg+=seg){
        for(int start=0; start < len; start += seg + seg){
            int low = start;
            int mid = min(start+seg, len);
            int high = min(start+seg+seg, len);
            int k = low;
            int start1 = low, end1 = mid;
            int start2 = mid+1, end2 = high;
            while(start1 < end1 && start2 < end2){
                b[k++] = a[start1] < a[start2] ? a[start1++] : a[start2++];
            }

            while(start1 < end1){
                b[k++] = a[start1++];
            }

            while(start2 < end2){
                b[k++] = a[start2++];
            }
        }
        T* temp = a;
        a = b;
        b = temp;
    }
    if(a != arr){
        for(int i=0; i < len; i++){
            b[i] = a[i];
        }
        b = a;
    }
    delete[] b;
}

// 递归方法实现
template<typename T>
void MergeSortRecursiveCore(T arr[], T reg[], int start, int end){
    if(start >= end){
        return;
    }
    int len = end - start;
    int mid = start + (len >> 1);
    int start1 = start, end1 = mid;
    int start2 = mid+1, end2 = end;
    MergeSortRecursiveCore(arr, reg, start1, end1);
    MergeSortRecursiveCore(arr, reg, start2, end2);
    int k = start;
    while(start1 <= end1 && start2 <= end2){
        reg[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
    }

    while(start1 <= end1){
        reg[k++] = arr[start1++];
    }

    while(start2 <= end2){
        reg[k++] = arr[start2++];
    }

    for(k = start; k<=end; k++){
        arr[k] = reg[k];
    }
}

template<typename T>
void MergeSortRecursive(T arr[], const int len){
    T* reg = new T[len];
    MergeSortRecursiveCore(arr, reg, 0, len-1);
    delete[] reg;
}

五、归并排序时间复杂度总结:

  • 平均时间复杂度:O(nlogn)
  • 最好情况:O(nlogn)
  • 最坏情况:O(nlogn)
  • 空间复杂度:O(n)
  • 稳定性:稳定
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351