我已经实现了以下快速排序算法。在线我已经读过它的空间要求为O(log(n))。为什么会这样?我没有创建任何额外的数据结构。
是因为我的递归会在堆栈上使用一些额外的空间吗?如果是这种情况,是否可以通过不使用递归(而是使其迭代)来减少内存?
private static void quickSort (int[] array, int left, int right) {
int index = partition(array, left, right);
//Sort left half
if (left < index - 1)
quickSort(array, left, index - 1);
//Sort right half
if (index < right)
quickSort(array, index , right);
}
private static int partition (int array[], int left, int right) {
int pivot = array[(left + right) / 2]; //Pick pivot point
while (left <= right) {
//Find element on left that should be on right
while (array[left] < pivot)
left++;
//Find element on right that should be on left
while (array[right] > pivot)
right--;
//Swap elements and move left and right indices
if (left <= right) {
int temp = array[left];
array[left] = array[right];
array[right] = temp;
left++;
right--;
}
}
return left;
}
答案 0 :(得分:39)
正确,额外的空间是log(n)堆栈帧。来自Wikipedia article of Quicksort:
有一个更复杂的版本使用就地分区 算法并且可以使用O(log n)空间实现完整的排序(不是 计算输入)平均(对于调用堆栈)。
虽然你可以迭代地实现快速排序(即使用循环而不是递归),但是你需要维护一个辅助堆栈,因为Quicksort有两个递归调用而不只是一个。
最后,正如其他答案所指出的那样,O(log(n))几乎适用于所有实际应用,非常小。每个常量因素(如数据结构的开销)都会对内存使用产生更大的影响。
答案 1 :(得分:4)
要摆脱递归调用,您必须在代码中使用堆栈,它仍然会占用log(n)
空间。
答案 2 :(得分:3)
如果您在维基百科文章中进一步阅读,您会发现更多thorough discussion of space complexity。特别是,他们写道:
具有就地和不稳定分区的Quicksort在进行任何递归调用之前仅使用不变的额外空间。 Quicksort必须为每个嵌套的递归调用存储恒定数量的信息。由于最佳情况最多使O(log n)嵌套递归调用,因此它使用O(log n)空间。但是,如果没有Sedgewick限制递归调用的技巧,在最坏的情况下,快速排序可以进行O(n)嵌套递归调用并需要O(n)辅助空间。
实际上,O(log n)内存不算什么。例如,如果你要排序10亿个int,那么存储它们需要4 GB,但是堆栈只需要大约30个堆栈帧,大概是40个字节,所以总共大约1200个字节。
答案 3 :(得分:1)
是的,这是因为堆栈帧,是的,有可能通过做一些非常聪明的事情将它转换为迭代算法(虽然没有任何东西立即出现在我身上)。但为什么? O(log(n))空间几乎没有。作为参考,即使你有一个Java允许的最大大小的数组,也就是2 ^ 31个元素,大约是8 GB。 Quicksort需要31个堆栈帧。棒球场,每帧可能100个字节?总共3 KB,与实际数组的内存相比没什么用。
实际上,几乎任何时候都是O(log(n)),它与常数几乎相同。
答案 4 :(得分:1)
很抱歉恢复这个旧问题,但我刚刚在planetmath.org上找到了一个完全不同(但略显愚蠢)的问题答案:
在连续数组上运行的任何排序算法都需要 O (log n)额外空间,因为这是bite [ sic ]需要表示数组的索引。