ArrayList的Java递归合并排序

Posted

技术标签:

【中文标题】ArrayList的Java递归合并排序【英文标题】:Java Recursive MergeSort for ArrayLists 【发布时间】:2016-04-19 10:31:04 【问题描述】:

我的 mergesort 函数出现问题,因为我无法在将一系列整数或字符串输入程序时对其进行排序。我有一个将项目调用到其中的外部类,但是它根本不对数字/字符串进行排序。两种方法如下,不知道问题出在哪里。数字是随机输入的。

代码:

/**
     * Takes in entire vector, but will merge the following sections together:
     * Left sublist from a[first]..a[mid], right sublist from a[mid+1]..a[last].
     * Precondition: each sublist is already in ascending order
     *
     * @param a
     *            reference to an array of integers to be sorted
     * @param first
     *            starting index of range of values to be sorted
     * @param mid
     *            midpoint index of range of values to be sorted
     * @param last
     *            last index of range of values to be sorted
     */
    private void merge(ArrayList<Comparable> a, int first, int mid, int last) 
        int x;
        int i;
        ArrayList<Comparable> left = new ArrayList<Comparable>();
        ArrayList<Comparable> right = new ArrayList<Comparable>();
        mergeSort(a,first,mid);
        for(i = 0; i < a.size() - mid; i++)
            left.add(i,a.get(i));
            a.remove(i);
        
        mergeSort(a,mid,last);
        for (x = mid; x < a.size(); x++) 
            right.add(x,a.get(x));
            a.remove(x);
        
        if ((left.get(i).compareTo(right.get(x))) > 0) 
            i++;
            a.add(i);
         else if (i < x) 
            x++;
            a.add(x);
        


        System.out.println();
        System.out.println("Merge");
        System.out.println();

    

    /**
     * Recursive mergesort of an array of integers
     *
     * @param a
     *            reference to an array of integers to be sorted
     * @param first
     *            starting index of range of values to be sorted
     * @param last
     *            ending index of range of values to be sorted
     */
    public void mergeSort(ArrayList<Comparable> a, int first, int last) 

        int mid = (first + last)/2;
        if(first == last)

        else if(last - first == 1)
            merge(a,first, mid ,last);              
        else
            last = mid;
        


                

【问题讨论】:

建议:从少量数字开始排序,跟踪一些战略步骤,然后迭代。 阅读wikien.wikipedia.org/wiki/Merge_sort,我觉得很清楚了。 【参考方案1】:

有几个问题,但一个重要的问题是你不应该在修改列表时迭代列表,即在:

for (i = 0; i < a.size() - mid; i++)
    left.add(i,a.get(i));
    a.remove(i);

因为一旦你删除了一个元素,其他元素的索引就不一样了......所以你添加了aleft元素,这不是你想的那样。

工作代码如下(带有一些 cmets):

 private static void merge(ArrayList<Comparable> a) 
    if (a.size()<=1) return; // small list don't need to be merged

    // SEPARATE

    int mid = a.size()/2; // estimate half the size

    ArrayList<Comparable> left = new ArrayList<Comparable>();
    ArrayList<Comparable> right = new ArrayList<Comparable>();

    for(int i = 0; i < mid; i++) left.add(a.remove(0)); // put first half part in left
    while (a.size()!=0) right.add(a.remove(0)); // put the remainings in right
    // Here a is now empty

    // MERGE PARTS INDEPENDANTLY

    merge(left);  // merge the left part
    merge(right); // merge the right part

    // MERGE PARTS

    // while there is something in the two lists
    while (left.size()!=0 && right.size()!=0) 
      // compare both heads, add the lesser into the result and remove it from its list
      if (left.get(0).compareTo(right.get(0))<0) a.add(left.remove(0));
      else                                       a.add(right.remove(0));
    

    // fill the result with what remains in left OR right (both can't contains elements)
    while(left.size()!=0)  a.add(left.remove(0));
    while(right.size()!=0) a.add(right.remove(0));
  

已经在一些输入上进行了测试...示例:

[4, 7, 1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11]
[0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

为了提高效率,您可以使用subList 方法来避免显式构建过多的子列表,它需要注意索引。

【讨论】:

谢谢!这真的很有帮助【参考方案2】:
public class MergeSort
    public void sort(List<Integer> list)
        sortAndMerge(list, 0, list.size()-1);
    

    public void sortAndMerge(List<Integer> list, int start, int end)
        if((end - start) >= 2)
            int mid = (end - start)/2;
            sortAndMerge(list, start, start + mid);
            sortAndMerge(list, start + mid +1, end);

            int i=start;
            int j=start + mid +1;
            while(i<j && j<=end)
                if(list.get(i) > list.get(j))
                    list.add(i, list.remove(j));
                    i++;
                    j++;
                else if(list.get(i) == list.get(j))
                    list.add(i+1, list.remove(j));
                    i++;
                    j++;
                else
                    i++;
                
              

        else
            if(end > start)
                if(list.get(start) > list.get(end))
                    int endValue = list.remove(end);
                    list.add(start, endValue);
                                
            
        
    

【讨论】:

请解释这段代码如何解决OP的问题。【参考方案3】:

关于 Kraal 的实施的警告,已获得复选标记。这是一个很好的实现,但是 Kraal 的合并排序不保留具有相同值的项目的相对顺序,这在某些情况下,例如在对对象进行排序时,是合并排序具有其他排序算法(如快速排序)的重要优势, 没有。我修改了 Kraal 的代码以保留相对顺序。

private static List<Object> merge(final List<Object> left, final List<Object> right) 
            printArr("left", left);
            printArr("Right", right);
            final List<Object> merged = new ArrayList<>();
            while (!left.isEmpty() && !right.isEmpty()) 
                if(left.get(0).getValue()-right.get(0).getValue() <= 0)
                    merged.add(left.remove(0));
                 else 
                    merged.add(right.remove(0));
                
            
            merged.addAll(left);
            merged.addAll(right);
            return merged;
     

 public static void mergeSort(final List<Object> input) 
     if (input.size() > 1) 
         final List<Object> left = new ArrayList<Object>();
         final List<Object> right = new ArrayList<Object>();
         boolean logicalSwitch = true;

         while (!input.isEmpty()) 
             if (logicalSwitch) 
                 left.add(input.remove(0));
              else 
                 right.add(input.remove(input.size()/2));
             
             logicalSwitch = !logicalSwitch;
         
         mergeSort(left);
         mergeSort(right);
         input.addAll(merge(left, right));
     
 

【讨论】:

【参考方案4】:

如果你想使用合并排序对数组进行排序,而不是自己实现排序算法, 我推荐使用标准 Java 排序算法,因为它为非原始类型实现了“Merge sort”算法。

Collections.sort();

如果您想实现自己的合并排序版本,那么您应该首先查看this implementation。

如果您有兴趣更好地理解排序算法,我推荐this book。

【讨论】:

【参考方案5】:

我有一个外部类将项目调用到其中,但是它根本不会对数字/字符串进行排序。下面是这两种方法,不知道问题出在哪里。

第一个问题是,如果您使用 first = 0last = a.size() 调用您的 mergeSort 方法,您将不会对任何内容进行排序,因为您只在 last-first == 1 时调用 merge

public void mergeSort(ArrayList<Comparable> a, int first, int last) 
    int mid = (first + last)/2;
    if(first == last)
    else if(last - first == 1)
        // you only merge if last - first == 1...
        merge(a,first, mid ,last);              
    else
        last = mid;
    

从这一点开始,我不明白您是如何尝试实现合并排序算法的。它既不是自上而下的,也不是自下而上的实现。您在合并方法中进行拆分,这也很奇怪。如果您提供了伪代码 + 调用 public 方法的方式,那么帮助您会更容易。恕我直言,您的算法确实存在问题。

事实上,归并排序算法实现起来非常简单。为了说明这一点,我使用Deque 而不是List 对象编写了这个top down implementation of the merge sort algorithm:

import java.util.Deque;
import java.util.LinkedList;

public class Example 

    private LinkedList<Comparable> merge(final Deque<Comparable> left, final Deque<Comparable> right) 
        final LinkedList<Comparable> merged = new LinkedList<>();
        while (!left.isEmpty() && !right.isEmpty()) 
            if (left.peek().compareTo(right.peek()) <= 0) 
                merged.add(left.pop());
             else 
                merged.add(right.pop());
            
        
        merged.addAll(left);
        merged.addAll(right);
        return merged;
    

    public void mergeSort(final LinkedList<Comparable> input) 
        if (input.size() != 1) 
            final LinkedList<Comparable> left = new LinkedList<Comparable>();
            final LinkedList<Comparable> right = new LinkedList<Comparable>();
            // boolean used to decide if we put elements
            // in left or right LinkedList
            boolean logicalSwitch = true;
            while (!input.isEmpty()) 
                if (logicalSwitch) 
                    left.add(input.pop());
                 else 
                    right.add(input.pop());
                
                logicalSwitch = !logicalSwitch;
            
            mergeSort(left);
            mergeSort(right);
            input.addAll(merge(left, right));
        
    

我使用了Deque,因为peek()/pop() 恕我直言比get(0)remove(0) 更漂亮,但这取决于你。如果您绝对要使用ArrayList,请遵循相应的实现。

import java.util.ArrayList;
import java.util.List;

public class Example 

    private List<Comparable> merge(final List<Comparable> left, final List<Comparable> right) 
        final List<Comparable> merged = new ArrayList<>();
        while (!left.isEmpty() && !right.isEmpty()) 
            if (left.get(0).compareTo(right.get(0)) <= 0) 
                merged.add(left.remove(0));
             else 
                merged.add(right.remove(0));
            
        
        merged.addAll(left);
        merged.addAll(right);
        return merged;
    

    public void mergeSort(final List<Comparable> input) 
        if (input.size() != 1) 
            final List<Comparable> left = new ArrayList<Comparable>();
            final List<Comparable> right = new ArrayList<Comparable>();
            boolean logicalSwitch = true;
            while (!input.isEmpty()) 
                if (logicalSwitch) 
                    left.add(input.remove(0));
                 else 
                    right.add(input.remove(0));
                
                logicalSwitch = !logicalSwitch;
            
            mergeSort(left);
            mergeSort(right);
            input.addAll(merge(left, right));
        
    

这两种实现都适用于IntegerString或其他Comparable

希望对你有帮助。

【讨论】:

这真是太好了!但是我需要帮助来了解如何使用 Arraylists 而不是 deque 创建它,因为我还没有在我的课堂上介绍过这个 答案的第二部分使用了ArrayLists。缺少什么? 哦抱歉,我错过了代码的第二部分,现在看起来更有帮助

以上是关于ArrayList的Java递归合并排序的主要内容,如果未能解决你的问题,请参考以下文章

归并排序思想及java模板代码

Java实现合并排序(归并)详细代码

递归与分治-合并排序快速排序以及循环赛问题

算法导论中,为啥合并排序的递归树的高度为lgn?

Python 归并排序(递归非递归自然合并排序)

在递归合并排序期间中止(核心转储)