我正在尝试了解合并排序的空间要求,O(n)。
我发现时间要求基本上是级别数量(logn)*合并(n),这样就可以得到(n log n)。
现在,我们仍然在每个级别分配 n 个,在 2 个不同的数组中,左和右。
我确实明白这里的关键是当递归函数返回时空间被释放,但我没有看到它太明显。
此外,我找到的所有信息都只是说明所需的空间是 O(n) 但没有解释它。
有什么提示吗?
function merge_sort(m)
if length(m) ≤ 1
return m
var list left, right, result
var integer middle = length(m) / 2
for each x in m up to middle
add x to left
for each x in m after middle
add x to right
left = merge_sort(left)
right = merge_sort(right)
result = merge(left, right)
return result
编辑
好的,感谢@Uri,这就是窍门
我一开始没看到的是,时间只是加法,而内存是加减法,所以时间最大是在执行结束时,但是内存最大是在递归栈的底部。
所以,如果我们继续添加 n + n/2 + n/4 + n/8....无论添加多少次,它都不会大于 2n,当我们达到递归时堆栈底部并开始向上,我们不保留前一个分支使用的内存,因此最大时,2n 将是使用的内存量,O(n)。
有一些版本的合并排序可以就地工作。
但是,在大多数实现中,空间与数组的大小呈线性关系。这意味着第一级为 n,第二级为 n/2,第三级为 n/4,等等。当您到达递归底部时,该级数加起来约为 2n,这是线性的。
这是我对代码空间复杂度的解释。基本上,当算法达到结果时,我们的内存表现如何。
1)您进行的每个递归调用都分配了恒定大小的堆栈帧以及不是“n”函数的任何变量。 我们称这个常数为“c”。因为,你要深入 lg(n) 层,结果是 c*lg(n) ,即 O(lg(n))。
2) 现在,当我们计算结果时,我们为 n/(2^k) 个元素分配空间,其中 k 是您所在的级别。
left = merge_sort(left)
right = merge_sort(right)
对于那些可能想知道我们如何得出 n/(2^k) 的人,请注意,首先我们在求解数组的前半部分时分配内存,即 left=merge_sort(left)。一旦这个递归树结束,我们最终会释放所有内存并回到起点,然后再求解右侧。因此,它是 n/(2^k)。 这将是 O(n)。
3)最后,合并过程也可能会分配额外的空间(如果使用链表,可能不需要这个空间),这是 O(n)
最终答案 = O(lg(n)) + O(n) + O(n),即 O(n)。
如果运行下面的Java代码,您可以看到分配的空间。当然这不包括函数调用堆栈空间。
import java.util.Arrays;
public class MergeSort {
public static int[] sort(int[] nums) {
System.out.println("Input array = " + Arrays.toString(nums));
if (nums == null) return null;
int n = nums.length;
if (n == 0) return new int[] {};
int[] space = new int[1];
int[] result = sort(nums, 0 , n-1, space);
System.out.println("Output array = " + Arrays.toString(result));
return result;
}
private static int[] sort(int[] nums, int left, int right, int[] space) {
if (left == right) {
space[0]++;
System.out.println("Allocating a single entry. space = " + space[0]);
return new int[] {nums[left]};
}
int mid = (left + right)/2;
int[] sortedLeft = sort(nums, left, mid, space);
int[] sortedRight = sort(nums, mid + 1, right, space);
int[] merged = merge(sortedLeft, sortedRight, space);
space[0] = space[0]-sortedLeft.length - sortedRight.length;
System.out.println("Relinquishing left and right subarrays. Space = " + space[0]);
return merged;
}
private static int[] merge(int[] arr1, int[] arr2, int[] space) {
int n = arr1.length;
int m = arr2.length;
int[] arr = new int[m+n];
space[0] = space[0]+m+n;
System.out.println("Allocating merged array. Space = " + space[0]);
int i = 0, j = 0, k = 0;
for (; i < n && j < m; k++) {
arr[k] = arr1[i] <= arr2[j] ? arr1[i++] : arr2[j++];
}
while (i < n) {
arr[k++] = arr1[i++];
}
while (j < m) {
arr[k++] = arr2[j++];
}
return arr;
}
}