在Java中为O(Nlog(N))优化Mergesort

在Java中为O(Nlog(N))优化Mergesort,java,algorithm,sorting,big-o,mergesort,Java,Algorithm,Sorting,Big O,Mergesort,我和一位合作伙伴正在尝试用Java编写Mergesort。我们已经完成了算法,并且它运行正常。然而,在测试各种输入的算法时,我们注意到它的性能不在O(Nlog(N))的范围内。我们一直在尝试进一步优化算法,如有任何建议,我们将不胜感激。唯一的要求是我们不能更改方法头。我们的Java代码如下所示: /** * MergeSort algorithm driver. * * @param arr - input ArrayList of objects */ public stat

我和一位合作伙伴正在尝试用Java编写Mergesort。我们已经完成了算法,并且它运行正常。然而,在测试各种输入的算法时,我们注意到它的性能不在O(Nlog(N))的范围内。我们一直在尝试进一步优化算法,如有任何建议,我们将不胜感激。唯一的要求是我们不能更改方法头。我们的Java代码如下所示:

    /**
 * MergeSort algorithm driver.
 * 
 * @param arr - input ArrayList of objects
 */
public static <T extends Comparable<? super T>> void mergesort(
        ArrayList<T> arr)
{
    // Pre-allocation of a temporary ArrayList
    // for merge space.
    ArrayList<T> temp = new ArrayList<T>(arr.size());
    temp.addAll(arr);

    // Call the recursive mergesort method.
    mergesort(arr, temp, 0, arr.size() - 1);
}

/**
 * Main mergeSort method. Makes recursive calls. 
 * 
 * @param arr - input ArrayList of objects
 * @param temp - temporary ArrayList to hold the merged result 
 * @param left - start of the subarray 
 * @param right - end of the subarray
 */
private static <T extends Comparable<? super T>> void mergesort(
        ArrayList<T> arr, ArrayList<T> temp, int left, int right)
{

    // If the size of the subcollection is less than a given threshold,
    // then perform an insertion sort rather than a mergesort.
    //if ((right - left) < threshold)
    //  insertionsort(arr, left, right);


    // If the size of the subcollection was not less than our threshold and 
    // the left end is less than the right end of subcollection, then we are 
    // done performing the sort.
    if(left < right)
    {
        int center = (left + right) / 2;
        mergesort(arr, temp, left, center);
        mergesort(arr, temp, center + 1, right);
        merge(arr, temp, left, right);
    }
}

/**
 * Internal method for merging two sorted subarrays. This is to be used with the 
 * mergesort algorithm.
 * 
 * @param arr - input ArrayList of objects
 * @param temp - temporary ArrayList in  which the result with be placed
 * @param currentLeft - start of the subarray 
 * @param rightEnd - end of the subarray
 */
private static <T extends Comparable<? super T>> void merge(
        ArrayList<T> arr, ArrayList<T> temp, int leftStart, int rightEnd)
{
    int currentLeft = leftStart;
    int leftEnd = (currentLeft + rightEnd) / 2;
    int rightStart = leftEnd + 1;


    // Main loop - compares the value in the left position
    // to the value in the right position.  
    while( currentLeft <= leftEnd &&  rightStart <= rightEnd)
    {
        // If the value in the left position is less than the right, 
        // place the left position value in the temporary collections.
        if(arr.get(currentLeft).compareTo(arr.get(rightStart)) <= 0)
        {
            temp.add(arr.get(currentLeft++));

        }


        // Otherwise, place the value in the rightStart position in
        // the temporary collection.
        else
        {
            temp.add(arr.get(rightStart++));

        }
    }

    // Copy the remaining left half.
    while( currentLeft <= leftEnd )
        temp.add(arr.get(currentLeft++));


    // Copy the remaining right half.
    while( rightStart <= rightEnd )
        temp.add(arr.get(rightStart++));


    // Loop through the temporary collection and for each element
    // currently in the collection, copy the contents back into the
    // original collection.
    for (int i = leftStart, count = 0; i <= rightEnd; i++, count++)
        arr.set(i, temp.get(count));

    // After the above operation has been completed for this particular
    // call, clear the temporary collection.
    temp.clear();

}
/**
*合并排序算法驱动程序。
* 
*@param arr-输入对象的ArrayList
*/

公共静态将我的评论转换为答案-

说算法有运行时O(n logn)并不意味着函数的运行时将与函数f(n)=n logn的绘图完全匹配。相反,这意味着函数的运行时以与函数n log n的运行时相同的速度增长。因此,对于较大的n,如果将输入的大小增加一倍,则运行时应该略多于两倍

您提到函数的运行时大约是n logn值的三倍,这一事实实际上有力地证明了您有一个O(n logn)运行时-您的函数的运行时大约是3n logn,这意味着它的运行时是O(n logn),因为大O忽略了常量因子。更准确地说,你得到的常数可能不完全是3,因为n的值是无量纲的(它测量一个量),而运行时间是以秒为单位的,所以这里有一些单位转换


希望这有帮助

既然@templatetypedef已经转换了BigO部分,让我们转到优化部分。我不懂Java语言,但方法是自我解释的。我注意到,在合并时,您会不断添加并清除临时列表

temp.add(arr.get(currentLeft++));
// ...
// ...
temp.add(arr.get(rightStart++));
// ...
// ...
temp.clear();

将项目追加到数组中并不需要固定的时间。

是什么让您认为这不会在O(n log n)时间内运行?您有任何证据吗?我们多次计时,输入大小从20K开始,每个数据点增加20K。然后,我们在MS Excel中用Nlog(N)图绘制了结果时间,很明显,我们的数据更接近O(N^1.35),平均比Nlog(N)大3倍。@brandoko-如果运行时间是O(N logn),这并不意味着你的绘图应该与函数N*logn完全匹配。它只是意味着函数应该以与O相同的速度增长(n日志n),这意味着如果你将输入大小增加一倍,你应该得到大约两倍的运行时间。你能发布你的数据点吗?@darijan是的,这就是算法在“合并”方法中所做的。第一种方法是驱动程序。第二种方法导致数据结构的递归分解。第三种方法是合并,它完成比较事实上,如果你得到的东西大约是n logn的3倍,但它始终是n logn的3倍,那么你几乎肯定有一个运行时是O(n logn)-隐藏常量因子计算为3。@invisal-将元素追加到数组需要摊销常量时间-任何单个操作可能需要O(n)个时间,但任何一系列n个操作都需要O(n)时间。不清除列表可能会快一个常数,但这不会带来渐进的改善。此外,许多动态数组的实现即使清除元素也会保留数组,因此不清楚这是否实际是一个优化。这取决于
ArrayList
是如何实现的。是的,我t不会进行渐进改进,但不会添加项(至少需要进行边界检查,增加长度计数器),也不会进行清除(减少长度计数器)可以稍微提高性能。分配一个固定大小的数组并重用比依赖于
ArrayList
的实现要好。无论如何,我完全同意你的观点。在Java中这样做实际上有点混乱,因为在编写使用泛型的函数时,你必须做一些残酷和不寻常的事情来创建一个适当的泛型另外,Java中的所有数组访问都是边界检查的,所以可能不会节省那么多。