归并排序非递归——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 为什么这样做?有没有人产生过这样的疑问。

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

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

归并排序非递归——C语言讲解_第1张图片

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

具体应该怎样修改呢?

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

具体问题分为三种

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

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

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

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

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

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

归并排序非递归——C语言讲解_第2张图片

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

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
#include
#include

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;
}

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

你可能感兴趣的:(C排序,数据结构,c语言)