归并排序非递归——C语言讲解

之前发了一期归并排序递归版本 这期讲解一下非递归版本 用循环模拟

有没有人好奇 既然有了递归版本 为何还要搞非递归版本 这不是多此一举吗?

其实不然 正因为递归是基于栈帧的基础上实现的 是不断在栈上开辟空间 所以总归有消耗

既然是开辟空间 如果递归深度太深 总有空间被占据完的时候 这就会导致栈溢出(StakeOverFlow)^~^

那么递归在这种情况之下显得很乏力 这就引出了今天的主角非递归循环实现归并排序

就是模拟归并排序 也是分为很多区间段去比较合并 但是从思路图上来看

归并排序呈现给人的是一个总分总的结构 先将一段完整的区间递归分为许多小的单元 在将小的单元回溯 成为原来的区间段

非递归排序呈现给人的是 将递归版本的前半段去掉 (递归部分去掉)只保留回溯部分

这只是从思路图上 简单的提取 因为两种方法的实现是不相同的 非递归只是模仿递归的实现

具体见代码怎样实现(当然这个代码肯定不是完整正确的代码 判断思路是这样的 但是会有一些边界问题 你能否看的出哪里出来问题 当然 你也可以去调试 观察begin1 end1 begin2 end2看是否有越界的问题)^~^

void MergeSort(int* arr, int n)
{
    assert(arr);

    //开辟一个tmp临时数组 存储排序之后的数据
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL)
    {
        printf("failed to allocate memory.");
        exit(-1);
    }

    int gap = 1;//相当于从归并排序的最小单元向最大单元开始循环比较
    while (gap < n)
    {
        for (int i = 0; i < n; i += 2 * gap)
        {
            //[i, i+gap-1] [i+gap,i+2*gap-1]
            int begin1 = i, end1 = i + gap - 1;
            int begin2 = i + gap, end2 = i + 2 * gap - 1;

            

            int index = i;
            while (begin1 <= end1 && begin2 <= end2)
            {
                if (arr[begin1] > arr[begin2])
                {
                    tmp[index++] = arr[begin2++];
                }
                else
                {
                    tmp[index++] = arr[begin1++];
                }
            }
            while (begin1 <= end1)
            {
                tmp[index++] = arr[begin1++];
            }
            while (begin2 <= end2)
            {
                tmp[index++] = arr[begin2++];
            }
        }
         //将归并的数据拷贝回原数组
        for (int j = i;j < n; j++)
        {
            arr[j] = tmp[j];
        }
    }
    gap *= 2;
    free(tmp);
    tmp = NULL;
}

刚开始 gap == 1 为什么这样做?有没有人产生过这样的疑问。

在前面就大致介绍了递归和非递归的大致实现方式

相当于非递归就只模仿了递归回溯的那部分 仅此而已

由上图可以看出 会有越界现象 所以上面代码是不完整的

具体应该怎样修改呢?

首先我们应分析问题 再从问题出发解决问题

具体问题分为三种

1,end1越界,那么后面的[begin2,end2]肯定就不存在了

2,end1没越界,但区间[begin2,end2]不存在

3,区间[begin2,end2]内有数据 说明begin2存在 end2存不存在就带考究了 存在就不会出问题了

那只有不存在时才会出问题

有没有人问 难道begin1就不会出问题吗?当然不会 为什么呢?你也不看看begin1=i 那又怎么样呢

它是由for循环控制的 出界的话循环就会停止的 这就不需要你担心了

既然问题分析完了 就该解决问题了

void MergeSort(int* arr, int n)
{
    assert(arr);

    //开辟一个tmp临时数组 存储排序之后的数据
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL)
    {
        printf("failed to allocate memory.");
        exit(-1);
    }

    int gap = 1;
    while (gap < n)
    {
        for (int i = 0; i < n; i += 2 * gap)
        {
            //[i, i+gap-1] [i+gap,i+2*gap-1]
            int begin1 = i, end1 = i + gap - 1;
            int begin2 = i + gap, end2 = i + 2 * gap - 1;

            //end1越界 且 后面的区间不存在
            if (end1 >= n)
            {
                end1 = n - 1;
            }
            //说明[begin2,end2]里面有数据 但是end2越界了
            if (end2 >= n)
            {
                end2 = n - 1;
            }
            //既然你都不存在 那就让你这个区间不存在不就解决了吗
            if (begin2 >= n)
            {
                begin2 = n;
                end2 = n - 1;
            }

            int index = i;
            while (begin1 <= end1 && begin2 <= end2)
            {
                if (arr[begin1] > arr[begin2])
                {
                    tmp[index++] = arr[begin2++];
                }
                else
                {
                    tmp[index++] = arr[begin1++];
                }
            }
            //通过上面的循环 并不是所有的数据都被拷贝到 tmp数组 肯定有一区间段没有处理完
            while (begin1 <= end1)
            {
                tmp[index++] = arr[begin1++];
            }
            while (begin2 <= end2)
            {
                tmp[index++] = arr[begin2++];
            }
        }
            //将归并的数据拷贝回原数组
        for (int j = i; j < n; j++)
        {
            arr[j] = tmp[j];
        }
        gap *= 2;
    }
    free(tmp);
    tmp = NULL;
}

上面的处理是对越界的代码进行修正处理

将上面的代码进行的修正处理还可以简化 下面就是简化后的代码以及完整的代码


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

void Print(int* arr, int n)
{
    assert(arr);
    for (int i = 0; i < n; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

void MergeSort(int* arr, int n)
{
    assert(arr);

    //开辟一个tmp临时数组 存储排序之后的数据
    int* tmp = (int*)malloc(sizeof(int) * n);
    if (tmp == NULL)
    {
        printf("failed to allocate memory.");
        exit(-1);
    }

    int gap = 1;
    while (gap < n)
    {
        for (int i = 0; i < n; i += 2 * gap)
        {
            //[i, i+gap-1] [i+gap,i+2*gap-1]
            int begin1 = i, end1 = i + gap - 1;
            int begin2 = i + gap, end2 = i + 2 * gap - 1;

            end1越界 且 后面的区间不存在
            //if (end1 >= n)
            //{
            //    end1 = n - 1;
            //}
            说明[begin2,end2]里面有数据 但是end2越界了
            //if (end2 >= n)
            //{
            //    end2 = n - 1;
            //}
            既然你都不存在 那就让你这个区间不存在不就解决了吗
            //if (begin2 >= n)
            //{
            //    begin2 = n;
            //    end2 = n - 1;
            //}


            // 核心思想:end1、begin2、end2都有可能越界
            // end1越界 或者 begin2 越界都不需要归并
            if (end1 >= n || begin2 >= n)
            {
                break;
            }

            // end2 越界,需要归并,修正end2
            if (end2 >= n)
            {
                end2 = n - 1;
            }

            int index = i;
            while (begin1 <= end1 && begin2 <= end2)
            {
                if (arr[begin1] > arr[begin2])//改升序或降序
                {
                    tmp[index++] = arr[begin2++];
                }
                else
                {
                    tmp[index++] = arr[begin1++];
                }
            }
            //通过上面的循环 并不是所有的数据都被拷贝到 tmp数组 肯定有一区间段没有处理完
            while (begin1 <= end1)
            {
                tmp[index++] = arr[begin1++];
            }
            while (begin2 <= end2)
            {
                tmp[index++] = arr[begin2++];
            }
            //将归并的数据拷贝回原数组
            for (int j = i; j <= end2; ++j)
            {
                arr[j] = tmp[j];
            }
        }
        gap *= 2;
    }
    free(tmp);
    tmp = NULL;
}


int main()
{
    int arr[] = { 8,9,6,5,7,4,1,2,3 };
    int sz = sizeof(arr) / sizeof(arr[0]);

    Print(arr, sz);
    MergeSort(arr, sz);
    Print(arr, sz);


    return 0;
}

有什么问题可以留言私信~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
归并排序是一种基于分治思想的排序算法。非递归实现的归并排序可以通过迭代来完成。下面是一个使用 Java 实现的归并排序非递归版本的示例代码: ```java public class MergeSort { public static void mergeSort(int[] arr) { if (arr == null || arr.length <= 1) { return; } int n = arr.length; int[] temp = new int[n]; int size = 1; // 子数组的大小,初始为1 while (size < n) { mergePass(arr, temp, size, n); // 执行一趟归并排序 size *= 2; // 子数组大小翻倍 } } private static void mergePass(int[] arr, int[] temp, int size, int n) { int i = 0; while (i + 2 * size <= n) { merge(arr, temp, i, i + size - 1, i + 2 * size - 1); // 归并相邻的两个子数组 i += 2 * size; } if (i + size < n) { merge(arr, temp, i, i + size - 1, n - 1); // 归并剩余的两个子数组 } } private static void merge(int[] arr, int[] temp, int left, int mid, int right) { int i = left; // 左边子数组的起始位置 int j = mid + 1; // 右边子数组的起始位置 int k = left; // 归并后数组的起始位置 while (i <= mid && j <= right) { if (arr[i] <= arr[j]) { temp[k++] = arr[i++]; } else { temp[k++] = arr[j++]; } } while (i <= mid) { temp[k++] = arr[i++]; } while (j <= right) { temp[k++] = arr[j++]; } for (int m = left; m <= right; m++) { arr[m] = temp[m]; } } public static void main(String[] args) { int[] arr = {5, 2, 8, 4, 9, 1}; System.out.println("Before sorting:"); for (int num : arr) { System.out.print(num + " "); } mergeSort(arr); System.out.println("\nAfter sorting:"); for (int num : arr) { System.out.print(num + " "); } } } ``` 这个示例代码中,`mergeSort` 方法用于调用非递归归并排序算法。`mergePass` 方法用于执行一趟归并排序,`merge` 方法用于归并两个子数组。 希望这个示例代码能够帮助你理解归并排序非递归实现。如有疑问,请随时提出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

菜鸡爱玩

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

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

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

打赏作者

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

抵扣说明:

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

余额充值