七大排序算法(4)------归并排序(递归和非递归)

         在本文中使用到的升序,降序,交换函数的代码实现见:这篇博客

         在单链表的基本操作中,有一个操作为合并两个有序的单链表使合并后的链表仍然有序。本文中归并排序与这个思想类似。不断使两两有序的序列进行合并。

归并排序(递归实现)

        例如,有一待排序序列:20 10  9  8  11  10   7  6。


        排序过程如上图所示,

(1)首先将待排序序列一分为二,两个红框框起来的序列。将这两个序列排好序后,利用合并算法进行合并。

(2)在对红框进行排序时,再将其一分为二,对应四个绿框。将绿框排好序后,利用合并算法合并成红框。

(3)再对绿框进行排序时,再将其一分为二,对应8个篮框。此时蓝框中只有一个元素,已经排好序,因此只需利用合并算法将两个蓝框合并成绿框。

        上述过程是一个递归过程,每一步对应的操作相同,先拆分。将一个大的序列分解为一个个小的序列。待排序序列中只有一个元素时,此时便排序完成。再进行一个个有序序列的合并。

        所以,利用递归调用解决该问题,将待排序序列一分为二,使这两部分分别有序后再进行合并。再对这两部分排序时也是一分为二进行排序后在进行合并。

        因此,使用递归函数来使待排序序列有序。先将待排序序列一分为二后分别调用递归函数使之有序,在进行有序序列的合并。在递归函数中,如果待排序序列的元素个数为1,表明已经有序,此时,直接返回即可。

        代码实现如下:

//递归进行归幷排序
void MergeSort(int arr[],uint64_t size)
{
    if(arr == NULL || size <= 1)
    {
        return;
    }   
    
    //[left,right)为待排序区间
    int left = 0;
    int right = size;
    //定义temp来保存合并两个已经排好序的区间
    int* temp = (int*)malloc(size*sizeof(int));
    //使用递归函数来对带排序序列进行排序                                                                        
    _MergeSort(arr,left,right,temp);
    free(tmp);
    return;

        递归函数的实现如下:

void _MergeSort(int arr[],int left,int right,int temp[])
{
    if(right - left <= 1)                                                                                       
    {
        //带排序区间中最多只有一个元素,此时不用在排序
        return;
    }

    //现将待排序序列一分为二,如果某一部分的元素个数小于等于1,则认为已经排好序
    int mid = left + (right - left)/2;
    //此时拆分的两个待区间为:[left,mid),[mid,right)

    //分别对这两部分进行排序
    _MergeSort(arr,left,mid,temp);
    _MergeSort(arr,mid,right,temp);
    //将排好序的两部分进行合并,即合并两个有序数组
    MergeArray(arr,left,mid,right,temp);
    return;
}

        下面就要来实现合并两个有序数组。与合并单链表类似,这里在定义一片内存来保存已经排好序的区间。

        定义两个变量index1,index2,分别指向两个有序数组的起始位置。定义变量temp_index遍历新定义的区间,初始为index1:

(1)如果index1处的值小于index2处的值,则将index1处的值赋值到temp_index处,此时index1和temp_index均后移;进行(1)~(2)

(2)不满足(1),则将index2处的值赋值到temp_index处,此时index2和temp_index均后移;进行(1)~(2)

(3)当index1或index2遍历完其对应的待排序区间后。此时判断哪个区间没有遍历完,则将该区间内的元素一一复制到新区间中。

(4)最后,将新区间已经排好序的区间再根据对应下标赋值到原区间中。

        代码实现如下:

//合并两个有序数组使之成为新的有序数组
void MergeArray(int arr[],int left,int mid,int right,int temp[])
{
    //使合并后的新的有序数组的下标与两个有序数组的下标对应
    int index1 = left;
    int index2 = mid;                                                                                           
    int temp_index = left;//新数组中的有序元素从left到right

    //如果两个有序子数组没有遍历完,就一直遍历直到某个数组遍历结束
    while(index1 < mid && index2 < right)
    {
        //使值小的元素先复制到新的数组
        if(arr[index1] < arr[index2])
        {
            temp[temp_index++] = arr[index1++];
        }
        else
        {
            temp[temp_index++] = arr[index2++];
        }
    }
    //将还未遍历完子数组的剩余元素一个个的赋值到新数组中
    while(index1 < mid)
    {
        temp[temp_index++] = arr[index1++];
    }
    while(index2 < right)
    {
        temp[temp_index++] = arr[index2++];
    }
    //将temp中排好序的元素复制到arr中
    //以便下一次接着对arr进行合并
    int i = left;
    for(;i < right;i++)
    {
        arr[i] = temp[i];
    }
    return;
}

归并排序(非递归实现)

        非递归的思想与递归的思想相同,

(1) 首先,取步长gap为1

 合并区间:[0*gap,1*gap),[1*gap,2*gap)

                  [2*gap,3*gap),[3*gap,4*gap)

                  [4*gap,5*gap),[5*gap,6*gap).......

(2)然后,取步长gap为2

合并区间:[0*gap,1*gap),[1*gap,2*gap)

                 [2*gap,3*gap),[3*gap,4*gap]......

(3)取步长gap为4,同理合并区间[0*gap,1*gap),[1*gap,2*gap).....

(4)步长的改变为:1,2,4,8,...,直到步长gap最大为size,此时就不需要在合并了

        上述过程中,需要两重循环来实现,第一重用于步长的改变,第二重用于区间的合并

void MergeSortByLoop(int arr[],int size)
{
    if(arr == NULL || size <= 1)                                                                                
    {
        //数组非法或者元素个数小于等于1,此时都不需要进行排序,直接返回即可
        return;
    }
    
    int* temp = (int*)malloc(size*sizeof(int));
    
    //步长的变化为:1,2,4,8,...后一次的步长是上一次的2倍
    int gap = 1;
    for(;gap < size;gap = 2*gap)
    {
        //每两个区间之间的间隔为:2*gap
        //即:0,2*gap,4*gap,8*gap,...
        //所以区间的增长差2*gap                                                                                 
        int index = 0;
        for(;index < size;index = index + 2*gap)
        {
            int left = index;
            int mid = index + gap;
            
            //防止最后只剩余一个区间,从而无法进行合并
            if(mid >= size)
            {
                mid = left;
            }
            int right = index + 2*gap;
            if(right >= size)
            {
                right = left;
            }
            MergeArray(arr,left,mid,right,temp);
        }
    }
    free(tmp);
}

        递归实现归并排序的时间复杂度为:O(nlogn),空间复杂度为:O(N),稳定性为:稳定




猜你喜欢

转载自blog.csdn.net/sandmm112/article/details/80527259
今日推荐