分治法 —— 归并排序(递归和非递归)


归并排序又称为合并排序(合久必分,分久必和) 分而治之的策略

归并排序不是一下子将整个数组排序,而是不断的分离直到分为一个数,一个数本身就是有序的,最后不断的合

分而治之 大规模问题化为小规模问题,小规模问题的解合并即是大规模问题的解

合并排序之递归

图片未加载请刷新

Code 递归

#include <cstdio>
//归并排序采用递归方式
//先分 后比 再和
//自顶向下(递归)
//-------------------------------------------------------------
void MergeSort(int s[], int left, int mid, int right)
{
   int a[8] = {0};//定义一个临时数组,来存放交换后的数组值
   int i = left, j = mid;
   int k = 0;
   while (i < mid && j <= right)//
   {//进入MergeSort函数中的左右两个part依次比较,谁小谁放在a数组的前面
       if (s[i] < s[j])
         a[k++] = s[i++];
        else 
         a[k++] = s[j++];
   }
   while (i < mid)//a数组右part已经放完啦, 剩余的左part部分的数值都放在a数组后面
    a[k++] = s[i++];

   while (j <= right)//a数组左part已经放完啦, 剩余的右part部分的数值都放在a数组后面
    a[k++] = s[j++];

	k = 0;
    for (int t = left; t <= right; t++)//最后将临时的a数组里的数值再赋值给s数组
        s[t] = a[k++];
}

void Merge(int s[], int left, int right)
{
    if (left < right)
    {

        int middle = (left + right) / 2;//找出中心位置
        Merge(s, left, middle);//进入左边part
        Merge(s, middle + 1, right);//进入右边part
        MergeSort(s, left, middle + 1, right);//核心代码
//MergeSort执行的过程中是在递归回溯的过程中的执行的,看上面那张图片即可明白
    }
    //else return ;
//    可有可无
}
//---------------------------------------------------------------------
int main()
{
    int arry[8] = {8, 4, 5, 7, 1, 3, 6, 2};
    for (int i = 0; i < 8; i++)
        printf("%d   ", arry[i]);
    printf("\n");
    Merge(arry, 0, 7);//将arry数组传入到Merge中
    for (int i = 0; i < 8; i++)
        printf("%d   ", arry[i]);
    return 0;
}

合并排序之迭代

递归属于自顶向下, 那么迭代相当于自底向上

迭代是从左往右 一 一归并(元素本身有序,即元素位置基本不变动), 两 两归并,再四 四归并,再八 八归并 …一直往上加(不足2^k个数直接归并即可!如本例中的数值46) 最终使整个数组有序
图片未加载请刷新
但许多同学都说关于迭代的逻辑思路我都知道的,关键是如何实现呢?

的确, 思路很简单,许多同学卡在不知道如何用编程实现,说明编程能力还不够,需要多做题来加强,一起努力吧!

关于迭代其实有两种实现,大同小异,看你喜欢那种呀!主要是Merge函数中如何将临时数组b放到原数组中方法的区别

Code 迭代1(So easy)

#include <iostream>
#include <cstdio>
using namespace std;

void Merge(int a[], int n)
{
	int *b = new int[n];
   for (int i = 1; i < n; i = i * 2)//步长
   {
       int left_min, left_max, right_min, right_max;
       for (left_min = 0; left_min < n - i; left_min = right_max)
       // n- i 不满足步长的,无需继续循环
       {
           int Start = left_min;//因为left_min会变的,故应该用Start来保存初始值
           
           left_max = right_min = left_min + i;
           right_max = right_min + i;
           
           int index = 0;//临时数组b的下标索引

           if (right_max > n)//如果右边part不足步长的长度,直接和左边满足的比较
              right_max = n;//限定范围的
              
           int End = right_max;//与Start同理


           while (left_min < left_max && right_min < right_max)
           {
               if (a[left_min] < a[right_min])
               {
                   b[index++] = a[left_min++];
               }
               else
                   b[index++] = a[right_min++];
           }
          while (left_min < left_max)
          {
              b[index++] = a[left_min++];
          }
          while (right_min < right_max)
          {
              b[index++] = a[right_min++];
          }
//三个while的循环代码和递归的一样


          index = 0;
          for (int j = Start; j < End; j++)
          {//不要使用left_min,right_max,因为该两个变量实行了自加加
              a[j] = b[index++];//临时数组赋值给原数组a
          }
       
       }
   }
   delete[] b;
}
int main()
{
    int arry[9] = {63, 95, 84, 46, 18, 24, 27, 31, 46};
    for (int i = 0; i < 9; i++)
        printf("%d\t", arry[i]);
    printf("\n");
    Merge(arry, 9);
    for (int i = 0; i < 9; i++)
        printf("%d\t", arry[i]);
    return 0;
}

归并排序中两个for循环是如何进行归并排序的,流程如下:
图片未加载请刷新

Code 迭代2(有点绕)

主要区别在与Merge函数中的第一个 while (left_min < left_max && right_min < right_max)下面的两个while的写法,其他 地方一某一样!!!

开动你的脑袋,为什么可以这样写呢?

#include <iostream>
#include <cstdio>
using namespace std;
void Merge(int a[], int n)
{
    int *b = new int[n];
   for (int i = 1; i < n; i = i * 2)//步长
   {
       int left_min, left_max, right_min, right_max;
       for (left_min = 0; left_min < n - i; left_min = right_max)
       {
           left_max = right_min = left_min + i;
           right_max = right_min + i;
           int index = 0;
           if (right_max > n)//如果右边part不足步长的长度,直接和左边满足的比较
              right_max = n;//限定范围的
           while (left_min < left_max && right_min < right_max)
           {
               if (a[left_min] < a[right_min])
               {
                   b[index++] = a[left_min++];
               }
               else
                   b[index++] = a[right_min++];
           }
           
//----------------------------------------------------------------------
//              分两种情况讨论,一是left_min还没到,二是right_min没到
//          1, left_min没到直接使用a数组赋值(因为待会b仍旧是要赋值给a的)
//          2, right_min没到直接待在结尾即可,无需改动

             while (left_min < left_max)//此时说明right_min等于right_max
             {
                 a[--right_min] = a[--left_max];
             }
// 为什么不可以使用right_max ?
// 答:因为使用--right_max使下一轮中的left_min向已经做比阿尼移动好的元素偏移
             while (index > 0)
             {
                 a[--right_min] = b[--index];//这里必须使用right_min
             }
//-----------------------------------------------------------------------------

       }
   }
   delete[] b;
}

int main()
{
    int arry[9] = {63, 95, 84, 46, 18, 24, 27, 31, 46};
    for (int i = 0; i < 9; i++)
        printf("%d\t", arry[i]);
    printf("\n");
    Merge(arry, 9);
    for (int i = 0; i < 9; i++)
        printf("%d\t", arry[i]);
    return 0;
}

结语

归并排序无论是递归还是迭代,总是离不来分治策略

大规模问题转化为小规模问题,小规模问题的再进行合 即是规模问题

建议将以上三种方法的归并排序自己手动来实现一下!!!

©️2020 CSDN 皮肤主题: 岁月 设计师:pinMode 返回首页