在Java中为O(Nlog(N))优化Mergesort
我和一位合作伙伴正在尝试用Java编写Mergesort。我们已经完成了算法,并且它运行正常。然而,在测试各种输入的算法时,我们注意到它的性能不在O(Nlog(N))的范围内。我们一直在尝试进一步优化算法,如有任何建议,我们将不胜感激。唯一的要求是我们不能更改方法头。我们的Java代码如下所示:在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
/**
* 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中的所有数组访问都是边界检查的,所以可能不会节省那么多。