排序算法之归并排序递归实现和非递归实现

这个算法写起来真的很简单,并且思路也好理解。

我们看附图

这就是我们的分治算法模型图 。

我们这么理解,我们知道当一个列表越长我们进行排序消耗就越大,那么我们把这个列表分为无数个小的列表,然后对他们进行排序,然后再组合,那么也会节省不少精力

我们直接看代码

首先我们的第一步是

分:

merger_sort


    public static void merger_sort(int[] a,int[]temp,int start,int end){
        /**
        *@MethodName:merger_sort
        *@Params:[a, temp, first, end]
         * @param a:传入的原始数组,因为我们要通过原始数组获取分成小段的值
         * @param temp:临时数组,用来保存拼接而成数组
         * @param start:开始的头下表
         * @param end:的尾下表
        *@Return:void
        *@Description:递归的方法
        */

        if (start<end){//如果还没有分成最小的部分
            int middle=(start+end)/2;//每次递归从中间分割
            merger_sort(a,temp,start,middle);//左边的部分进行分割
            merger_sort(a,temp,middle+1,end);//右边的部分进行分割
            merger(a,temp,start,middle,end);//当执行到这个方法时我们将上面当前层的部分进行比较并拼接
        }
    }

我们首先将列表分为最小的部分,我们就像上图一样想成一个层,merger方法就会将每层的两个数组进行排序并且拼接

我们看

归:

merger方法:

private static void merger(int[] a, int[] temp, int start, int middle, int end) {
       int startRecord=start;
       int middleTemp=middle+1;
//        int endTemp=end;

        int count=0;

        while (start<=middle&&middleTemp<=end){//当两个列表都没比较完大小
              if (a[start]<=a[middleTemp]){//如果左侧当前下标的值小
                   temp[count++]=a[start++];//我们将左侧当前下标的值存入temp临时数组
              }
              else//如果右侧当前下标的值小
                  temp[count++]=a[middleTemp++];//我们将右侧当前下标的值存入temp临时数组
        }

        while(start<=middle){//当右边数组全存入而左边的数组未存完
            temp[count++]=a[start++];
        }

        while(middleTemp<=end){//当左边数组全存入而右边的数组未存完
            temp[count++]=a[middleTemp++];
        }

        for (int i=0;i<count;i++){
            //将相应的tmp的值存入a数组,这
            // 里不用担心打乱a的顺序而导致后面的操作无法进行,我们当前的操作都是操作先这层原本在a中区域的下标的

            a[startRecord++]=temp[i];
        }
    }

最后循环或许难以理解为什么不会打乱顺序看图:

我们来看非递归的算法实现:

非递归有点像反向的递归

递归的思路是先分成一段一段的,而非递归是我们直接将数组的每个元素都当做一个序列,然后从跨度为1开始依次合并最后成为排好序的序列

è¿éåå¾çæè¿°

我们看代码 :

package merge2;
//非递归方法
/*
* 非递归的方法和递归的方法有点像反向的
* 思路:我们一开始就将列表中每个元素当成一部分,我们将其依次合并最后返回一个排好序的数组
*
* */
class Merger {
    public static void main(String[] args) {
      int[] a=new int[]{4,23,15,3,52,1,52,3,542,35,42};
      merge_in(a);
      for (int i:a){
          System.out.println(i);
      }
    }

   public static void  merge_in(int[] a){
        //从跨度为1开始进行合并,每次合并完再给跨度乘2进行合并直到停止合并
       int span =1;
       int[] temp=new int[a.length];
       while (span<a.length){//停止条件是当跨度大于我们的长度时停止
       merge_method(a,temp,span,a.length);
       span*=2;
   }
    }

    private static void merge_method(int[] a, int[] temp, int span, int length) {
        int index =0;//存储当前的下标
        int j;
        while(index<length-2*span+1){//如果坐标符合要求,我们就两两归并,这里的条件其实是i+2s-1<=n,
           merger(a,temp,index,index+span-1,index+2*span-1); //两两归并
            index=index+2*span;//到下一个要归并的两个序列的首序列
        }
        if (index<length+1-span-1) {//如果剩下的序列还大于一个间隔我们也归并,最后减1是因为从0开始索引
            merger(a,temp,index,index+span-1,length-1); //两两归并
        }
        else //如果只剩单个序列,我们就把它放最后,就算一直他是单个,在最后一次归并时只会剩余它和另一个,
        // 然后进行归并通过上面的if语句
        for (j=index;j<length;j++){
            temp[j]=a[j];
        }
    }


    private static void merger(int[] a, int[] temp, int start, int middle, int end) {
        int startRecord=start;
        int middleTemp=middle+1;
//        int endTemp=end;

        int count=0;

        while (start<=middle&&middleTemp<=end){//当两个列表都没比较完大小
            if (a[start]<=a[middleTemp]){//如果左侧当前下标的值小
                temp[count++]=a[start++];//我们将左侧当前下标的值存入temp临时数组
            }
            else//如果右侧当前下标的值小
                temp[count++]=a[middleTemp++];//我们将右侧当前下标的值存入temp临时数组
        }

        while(start<=middle){//当右边数组全存入而左边的数组未存完
            temp[count++]=a[start++];
        }

        while(middleTemp+1<=end){//当左边数组全存入而右边的数组未存完
            temp[count++]=a[middleTemp++];
        }

        for (int i=0;i<count;i++){
            //将相应的tmp的值存入a数组,这
            // 里不用担心打乱a的顺序而导致后面的操作无法进行,我们当前的操作都是操作先这层原本在a中区域的下标的

            a[startRecord++]=temp[i];
        }
    }

}

排序归并的方法并没有不同,

我们的核心思路:从一开始每次乘2合并知道跨度大于列表长度时就完成了,

merge_method就是用来寻找要组合的下标并进行合并

这个方法有三个情况

第一种:

当前索引的序列能够和下个序列长度进行合并,我们合并

 

第二种“:

当第一种情况不够,但是剩余的序列长度大于一个跨度,也就是有一个长度为跨度的序列和一个长度不足跨度的序列,当最后时,如果不能直接两两合并,也就是出现了这种情况

第三种:

 只剩下一个序列,这里的序列不一定为1,注意是不足跨度长度的序列,我们把他放到最后,就算一直没法合并,等到最后时,也会处理。

 

时间复杂度:稳定算法

递归排序

它就像一个完全二叉树,因此最外层的递归进行了logn次(完全二叉树的深度为logn),因此递归的时间复杂度为O(logn),里面还要进行排序比对,比对的时间复杂度为O(n),由于比对在递归中嵌套,所以时间复杂度为O(nlogn)

非递归排序:

首先每次乘二找跨度因此这里的时间复杂度为O(logn),然后进行比对的时间复杂度为O(n),而且比对是嵌套在跨度乘二这个方法里的,因为时间复杂度为O(nlogn)

但是非递归避免了递归申请深度为logn的栈空间,也就是temp数组,非递归只申请了一个,因为非递归更好

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
归并排序是一种基于分治思想的排序算法非递归实现归并排序可以通过迭代来完成。下面是一个使用 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` 方法用于归并两个子数组。 希望这个示例代码能够帮助你理解归并排序非递归实现。如有疑问,请随时提出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值