【问题标题】:Merge Sort Recursion合并排序递归
【发布时间】:2023-07-19 10:53:02
【问题描述】:

这是来自 Java 编程简介中关于合并排序的代码。此方法使用递归实现。

public class MergeSort {
  2    /** The method for sorting the numbers */
  3    public static void mergeSort(int[] list) {
  4      if (list.length > 1) {
  5        // Merge sort the first half
  6        int[] firstHalf = new int[list.length / 2];
  7        System.arraycopy(list, 0, firstHalf, 0, list.length / 2);
  8        mergeSort(firstHalf);
  9  
 10        // Merge sort the second half
 11        int secondHalfLength = list.length - list.length / 2;
 12        int[] secondHalf = new int[secondHalfLength];
 13        System.arraycopy(list, list.length / 2,
 14          secondHalf, 0, secondHalfLength);
 15        mergeSort(secondHalf);
 16  
 17        // Merge firstHalf with secondHalf into list
 18        merge(firstHalf, secondHalf, list);
 19      }
 20    }

我的问题:是在第 8 行将递归方法调用回“mergeSort”吗?如果从方法的开头运行,将再次创建“firstHalf”数组,长度将减半。我认为“firstHalf”不能再次创建,如果已经定义了数组,则不应更改长度。

这里是完整的代码链接:Merge Sort Java

【问题讨论】:

  • firstHalf 将被再次创建,因为它属于(list.length > 1) 的范围,因此每次使用MergeSort 调用list.length > 1,您都会得到另一个int[] firstHalf。那是你的问题还是?
  • 我鼓励你看看这个链接algs4.cs.princeton.edu/22mergesort

标签: java algorithm mergesort


【解决方案1】:

这是初学者的思维方式。是的,我之前遇到这个的时候也是这么想的。我无法相信相同的数组大小可以动态变化。了解这一点,在下面的代码中,array l and array revery recursive call 创建了不同的大小。不要对此感到困惑。

是的,对于像你我这样的初学者来说,相同的数组大小是不可能动态变化的。但是,有一个例外,嗯,有例外。在我们前进的过程中,我们会经常看到它们。

它的递归,在递归中,事物会动态变化,而这一切 更改存储在调用堆栈中。

它令人困惑,但如果你仔细思考它真的很有趣。其深刻。合并排序可以以完全不同的方式实现,但递归的基本概念是相同的。在这里不要混淆,最好按照另一种方式来做,video:

归并排序首先采用列表或数组。让我们想象一下

a.length; #lenght of an array is 8

现在的最终目标是递归地拆分数组,直到它到达没有元素(only-one)的点。还有一个single element is always sorted

请参阅以下代码中的基本情况:

if(a.length<2) /*Remember this is the base case*/
        {
            return;
        }

一旦达到单个元素,将它们排序并合并回来。这样你就得到了一个很容易合并的完整的排序数组。我们做这些无意义的事情的唯一原因是为了获得一个更好的运行时算法,即 O(nlogn)。

因为,所有其他排序算法 (insertion, bubble, and selection) 将占用 O(n2),这确实太多了。因此,人类必须找出更好的解决方案。它是人类的需要,非常重要。我知道这很烦人,我经历过这种废话。

在尝试此操作之前,请先对递归进行一些研究。清楚地理解递归。远离这一切。举一个简单的递归示例并开始研究它。举一个阶乘的例子。这是一个不好的例子,但它很容易理解。

自上而下的合并排序

查看我的代码,它既好又简单。同样,在您第一次尝试时,两者都不容易理解。在你试图理解这些东西之前,你必须接触到递归。一切顺利。

public class MergeSort 
{
    private int low;
    private int high;
    private int mid;
    public static int[] a;

    public MergeSort(int x)
    {
        a = new int[x];
        a[0]=19;
        a[1]=10;
        a[2]=0;
        a[3]=220;
        a[4]=80;
        a[5]=2000;
        a[6]=56001;
        a[7]=2;

    }

    public void division(int[] a)
    {
        low=0;
        int p;
        high = a.length;
        mid = (high+low)/2;
        if(a.length<2) /*Remember this is the base case*/
        {
            return;
        }
        else
        {
            int[] l = new int[mid];
            int[] r = new int[high-mid];
            /*copying elements from a into l and r*/
            for(p=0;p<mid;p++)
                l[p]=a[p];
            for(int q=0;q<high-mid;q++, p++)
                r[q]=a[p];
            /*first recursive call starts from here*/
            division(l);
            division(r); 
            sortMerge(a, l, r);
        }
    }

    public void sortMerge(int[] a, int[] l, int[] r)
    {

        int i=0, j=0, k=0;
        /*sorting and then merging recursively*/
        while(i<l.length && j<r.length)
            {
                if(l[i]<r[j])
                    {
                        a[k] = l[i]; /*copying sorted elements into a*/ 
                        i++;
                        k++;
                    }
                else
                    {
                        a[k] = r[j];
                        j++;
                        k++;
                    }
            }

        /*copying remaining elements into a*/
        while(i<l.length)
            {
                a[k] = l[i];
                i++;
                k++;
            }

        while(j<r.length)
            {
                a[k] = r[j];
                j++;
                k++;
            }

    }
    /*method display elements in an array*/
    public void display()
    {
        for(int newIndex=0;newIndex<a.length;newIndex++)
        {
        System.out.println(a[newIndex]);
        }
    }


    public static void main(String[] args) 
    {
        MergeSort obj = new MergeSort(8);
        obj.division(a);
        obj.display();
    }

}

【讨论】:

    【解决方案2】:

    正如 Emz 所指出的:这是由于范围原因。局部变量是一个新对象。 [

    局部变量由局部变量声明语句声明 (§14.4)。

    每当控制流进入块(第 14.2 节)或 for 语句时 (§14.14),为每个声明的局部变量创建一个新变量 在立即包含的局部变量声明语句中 该块或 for 语句。

    一个局部变量声明语句可能包含一个表达式 初始化变量。带有初始化的局部变量 但是,直到局部变量才初始化表达式 声明它的声明语句被执行。 (规则 明确赋值(§16)防止局部变量的值 在它被初始化或以其他方式分配一个之前被使用 值。)当局部变量有效地停止存在时 块或for语句执行完毕。]1

    【讨论】:

      【解决方案3】:

      这是合并排序的另一种实现,这是bottom-up MergeSort

      public class MergeSort {
      public static void merge(int[]a,int[] aux, int f, int m, int l) {
      
          for (int k = f; k <= l; k++) {
              aux[k] = a[k];
          }
      
          int i = f, j = m+1;
          for (int k = f; k <= l; k++) {
              if(i>m) a[k]=aux[j++];
              else if (j>l) a[k]=aux[i++];
              else if(aux[j] > aux[i]) a[k]=aux[j++];
              else a[k]=aux[i++];
          }       
      }
      public static void sort(int[]a,int[] aux, int f, int l) {
          if (l<=f) return;
          int m = f + (l-f)/2;
          sort(a, aux, f, m);
          sort(a, aux, m+1, l);
          merge(a, aux, f, m, l);
      }
      public static int[] sort(int[]a) {
          int[] aux = new int[a.length];
          sort(a, aux, 0, a.length-1);
          return a;
      }
      }
      

      【讨论】: