详解自然合并排序

既然是详解自然合并排序,那就一步一步讲解算法: (‾◡◝)
学习自然合并排序,首先我们要先知道:

自然合并排序和合并排序的区别:

合并排序 :
是将两个(或两个以上)有序表合并成一个新的有序表
自然合并排序:
就是在合并排序时,建立子数组段,通过数组已排序的(自然排序,不重新排序)子数组段。

下面通过步骤讲解自然合并排序到底是怎样操作的:( ﹁ ﹁ ) ~→

自然合并排序步骤:

简单的说,自然分组排序就是:
(1)先检查数组内部有没有已经排好序的子数组段;
将数组划分成一个一个有序的子数组段,
子数组段个数最大为数组n  (´ー∀ー`)

即没有已经排好序的,比如要求升序数组,数组是{9,8,7,6,5},那么子数组段就为{9}、{8}、{7}、{6}、{5}

最小为1  (´ー∀ー`)

即已经按照要求有序了,比如{5,6,7,8,9}。

再来一个不极端的例子:

比如:{4,8,3,7,1,5,6,2},自然排好序的子数组段{4,8}、{3,7}、{1,5,6}、{2};

(2)分好子数组段后,从第一个开始将相邻的两个子数组段合并成一个数组。然后重新分段,再合并,直到子数组段变为1

比如子数组段{4,8}、{3,7}、{1,5,6}、{2}
第一次合并:{3,4,7,8}、{1,2,5,6}
第二次合并:{1,2,3,4,5,6,7,8}
结束

好了。这就是大概步骤了,是不是觉得思想很简单,但是实现起来还是没有头绪  ( ̄_, ̄ )
没关系,我们通过代码一点一点讲解

自然合并排序算法讲解:

(一)首先是第一步骤,先将原数组a进行分段,
我们需要新开辟一个数组,用这个数组来记录下每个子数组段的首位置,这样就可以达到分割数组的目的,给数组起名为temp:

        for(i=0;i<n-1;i++) //分段
        {
            if(a[i]>a[i+1])
            {
                temp[j++]=i+1;
                k++;//k是总共有多少段
            }
        }
        temp[j]=n;

因为循环里我是将i+1作为下标放进去,所以退出条件为n-1
用k来表示总共有多少段,作为一会儿判断子段数为1时合并结束的条件
最后多加一个n的目的是作为最后一段结束的位置,后面函数需要用,可以先往下看

(二)用一个while循环,循环退出条件为k>1,也就是子数组段为1的时候退出
i表示temp下标位置,j表示合并次数,应该小于段数k除以2,比如有6段就需要合并3次,3段合并1次,最后一段空出(我之前用i<k-2来表示结束条件,发现奇偶段会出现问题)

        int f=k;
        while(k>1)
        {
            for(j=0,i=0;j<k/2;j++,i+=2) //两个相邻的进行合并
            {
                hebing(temp[i],temp[i+1],temp[i+2]);
                f--;
            }
            newtemp(temp,k);
            k=f;
        }
        

f的作用是暂代一下新的k值,因为在重新标记子数组段函数里需要知道原来有多少段,所以k值在没有经过这个函数前不能减小。
for循环里合并子数组段函数传的参数是:第一个子数组段首位置、第二个子数组段首位置、第二个子数组段末位置(也就是第三个子数组段首位置减1,由于我后面函数是统一将下一个子数组段首位置设为前一个子数组段末位置,所以在这里没有减)

(三)合并子数组段函数
合并两个子数组的思想很简单,开辟一个新数组x,x的大小为最后一个数组末位置减去第一个数组首位置
然后将两个数组大小进行比较,按顺序插入
最后判断一下哪个数组有剩余,加入新数组里
再将新数组里已经排好的值赋给原来数组a。

    void hebing(int begin,int mid ,int end ) //合并子数组段函数
    {
        int i=0,j;
        int x[end-begin];
        int l1=begin,l2=mid;  //标记一下
        while(l1!=mid && l2!=end)//合并入新数组x
        {
            if(b[l1]<b[l2])
            {
                x[i++]=b[l1++];
            }
            else
            {
                x[i++]=b[l2++];
            }
        }
        if(l1==mid) //如果l1排完了,该l2
        {
            while(l2!=end)
            {
                x[i++]=b[l2++];
            }
        }
        if(l2==end) //如果l2排完了,该l1
        {
            while(l1!=mid)
            {
                x[i++]=b[l1++];
            }
        }

        for(i=0,j=begin;i<end-begin;i++)  //排好序的新数组值赋给原数组
        {
            b[j++]=x[i];
        }
    }

之前的给temp多赋值一个n,在这里就是作为最后一个子数组段的末位置传入的

(四)重新标记子数组段函数
这个函数思想也很简单,因为是相邻的两两合并,所以直接去掉temp里除了0外的偶数下标值
但是因为每次i是加2的,所以容易跳过最后的单数的值,所以在最后判断一下,如果没有给加上,要不然最后一个子数组段没有末位置会出错

    void newtemp(int *temp,int f)  //重新标记子数组段函数
    {
        int i,j=0;
        for(i=0;i<=f;i+=2)    //赋新值,每次加2,跳过已经被合并的数组下标值
        {
            temp[j++]=temp[i];
        }
        if(temp[j-1]!=n)
        {
            temp[j]=n;
        }
    }

好了,主要核心代码和思想就解释完了,
完整代码:(我写的感觉不太好,大家尽量自己改进啊) ( -’`-; )

#include<iostream>
#include<vector>
using namespace std;
class solution
{
private:
    int *b;int n,k=1;
public:
    int* ziran(int *a,int s)
    {
        b=a;n=s;
        int temp[n];
        temp[0]=0;
        int i,j=1;
        for(i=0;i<n-1;i++) //分段
        {
            if(a[i]>a[i+1])
            {
                temp[j++]=i+1;
                k++;//k是总共有多少段
            }
        }
        temp[j]=n;
        for(i=0;i<k;i++)//输出子段
        {
            cout<<temp[i];
        }
        cout<<endl;
        int f=k;
        while(k>1)
        {
            for(j=0,i=0;j<k/2;j++,i+=2) //两个相邻的进行合并
            {
                hebing(temp[i],temp[i+1],temp[i+2]);
                f--;
            }
            newtemp(temp,k);
            k=f;
        }
        return a;
    }
    
    void newtemp(int *temp,int f)
    {
        int i,j=0;
        for(i=0;i<=f;i+=2)
        {
            temp[j++]=temp[i];
        }
        if(temp[j-1]!=n)
        {
            temp[j]=n;
        }
    }

    void hebing(int begin,int mid ,int end )
    {
        int i=0,j;
        int x[end-begin];
        int l1=begin,l2=mid;
        while(l1!=mid && l2!=end)//合并入新数组x
        {
            if(b[l1]<b[l2])
            {
                x[i++]=b[l1++];
            }
            else
            {
                x[i++]=b[l2++];
            }
        }
        if(l1==mid) //如果l1排完了,该l2
        {
            while(l2!=end)
            {
                x[i++]=b[l2++];
            }
        }
        if(l2==end) //如果l2排完了,该l1
        {
            while(l1!=mid)
            {
                x[i++]=b[l1++];
            }
        }

        for(i=0,j=begin;i<end-begin;i++)
        {
            b[j++]=x[i];
        }
    }

};

int main()
{
    int n,i;
    cout<<"数组个数:";
    cin>>n;
    int a[n];
    for(i=0;i<n;i++)
    {
        cin>>a[i];
    }
    int *b;
    solution k;
    b=k.ziran(a,n);
    for(i=0;i<n;i++)
    {
        cout<<b[i];
    }
}

后记

可能我指针学的不太好,所以这个题在写的时候有很多段错误的bug,而且不停徘徊在值和数组下标里很晕,太菜了...  ┭┮﹏┭┮
自然合并排序在已知数组已经有很多有序的子段时,效率还是非常高的
开始写这个的时候很懵
然后csdn搜了半天,发现就是简单的几句话带过思想,然后直接是完整代码,感觉这样慢慢去研究代码又没有注释啥的很烦 (>﹏<)
所以想写一篇比较详细的,之后也会更一些碰见比较烦的算法题。
︿( ̄︶ ̄)︿

  • 17
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java排序算法是用于对一系列数据进行排列顺序的一种算法。在Java中,常见的排序算法包括冒泡排序、选择排序、插入排序、快速排序、归并排序等。 冒泡排序是一种比较简单的排序算法,它通过对相邻的元素进行比较和交换来实现排序。该算法的时间复杂度为O(n^2),属于比较低效的排序算法。选择排序是一种简单直观的排序算法,它通过选择最小的元素并放置在已排序的部分来实现排序。该算法的时间复杂度也是O(n^2)。 插入排序是一种比较高效的排序算法,它通过将未排序的元素插入到已排序的部分来实现排序。该算法的时间复杂度也是O(n^2)。快速排序是一种递归的排序算法,它通过选取一个基准值来对数组进行分区,并对每个分区进行排序来实现最终的排序。该算法的时间复杂度为O(nlogn),是比较高效的排序算法之一。 归并排序是一种分治的排序算法,它将数组分成两个子数组,并对每个子数组进行排序,最后将两个子数组合并成一个有序数组。该算法的时间复杂度也是O(nlogn)。在实际应用中,我们通常会选择合适的排序算法来应对不同的排序需求,比如对于小规模数据可以选择简单的排序算法,对于大规模数据可以选择高效的排序算法。总之,了解Java排序算法的原理和性能表现对于编程人员来说是非常重要的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值