针对以下问题,我对当前的解决方案并不完全确定:
问题:
给定一个最大堆,有 n
个元素,存储在数组 A
中,是否可以用 O(K*log(K))
的时间复杂度打印出最大的 K
个元素?
我的答案:
可以,因为查找一个元素需要 O(log(K))
的时间,因此对于 K
个元素这样做将需要 O(K * log(K))
的运行时间。
针对以下问题,我对当前的解决方案并不完全确定:
问题:
给定一个最大堆,有 n
个元素,存储在数组 A
中,是否可以用 O(K*log(K))
的时间复杂度打印出最大的 K
个元素?
我的答案:
可以,因为查找一个元素需要 O(log(K))
的时间,因此对于 K
个元素这样做将需要 O(K * log(K))
的运行时间。
我发现其他答案很令人困惑,所以我决定用一个实际的堆例子来解释。假设原始堆的大小为N,您想要找到第k大的元素,这个解决方案需要O(klogk)的时间和O(k)的空间。
10
/ \
5 3
/ \ /\
4 1 2 0
Original Heap, N = 7
想要找到第五大的元素,k = 5。 注意:在新堆中,您需要存储原始堆的指针。 这意味着,您不会删除或更改原始堆。原始堆是只读的。因此,您永远不必执行任何需要O(logN)时间的操作。
设x'是原始堆中值为x的指针。
第一次迭代:将根节点的指针放入新堆中
步骤1:添加对节点10的指针
10'
New Heap, size = 1, root = 10', root->left = 5, root right->3
打印第一大的元素 = 10
第二轮迭代: 参考原堆,将其两个子节点插入新堆。(存储指向它们的指针而不是值本身)。之所以要存储指针是为了能够从原堆中以 O(1) 的时间复杂度访问它们,以便稍后搜索它们的子节点,而不是以 O(N) 的时间复杂度搜索那个值在原堆中的位置。
步骤2a:从原堆中查找新堆的根节点的左孩子。 将左孩子(在本例中为5')的指针添加到新堆中。
10'
/
5'
New Heap, size = 2, root = 10', root->left = 5, root right->3
10'
/ \
5' 3'
New Heap, size = 3, root = 10', root->left = 5, root right->3
10' swap 3' remove & bubble 5'
/ \ => / \ => /
5' 3' 5' 10' 3'
New Heap, size = 2, root = 5', root->left = 4, root right->1
打印第二大的元素 = 5
步骤3a:从原堆中查找新堆的根节点的左子节点。将左子节点(在这种情况下为4')添加到新堆中。
5'
/ \
3' 4'
New Heap, size = 3, root = 5', root->left = 4, root right->1
5'
/ \
3' 4'
/
1'
New Heap, size = 4, root = 5', root->left = 4, root right->1
第三步c:从新堆中删除根节点。 (将新堆的最大节点(5')与原始堆的最右侧叶子节点(1')交换,删除根节点并向下移动当前根以维护堆属性)
5' Swap 1' remove & bubble 4'
/ \ => / \ => / \
3' 4' 3' 4' 3' 1'
/ /
1' 5'
New Heap, size = 3, root = 4', root->left = NULL, root right->NULL
打印第三大的元素 = 4
在这个原始堆中,步骤4a和步骤4b不起作用,因为根节点没有任何子节点。
步骤4c:从新堆中删除根节点。 (将最大节点与最右侧叶子交换,删除根节点并向下移动当前根以维护新堆中的堆属性)
4' Swap 1' remove & bubble 3'
/ \ => / \ => /
3' 1' 3' 4' 1'
New Heap, size = 2, root = 3', root->left = 2, root right->0
打印第四大的元素 = 3
步骤5a:从原堆中查找新堆的根节点左子节点。在新堆中添加指向左子节点(在此例中为2')的指针。
3'
/ \
1' 2'
New Heap, size = 3, root = 3', root->left = 2, root right->0
3'
/ \
1' 2'
/
0'
New Heap, size = 4, root = 3', root->left = 2, root right->0
步骤5c:从新堆中移除根节点。 (在新堆中将最大节点(3')与原堆中最右侧的叶子节点(0')交换,移除根节点并将当前根节点向下移动以维护新堆的堆属性)
3' Swap 0' Remove & Bubble 2'
/ \ => / \ => / \
1' 2' 1' 2' 1' 0'
/ /
0' 3'
New Heap, size = 3, root = 2', root->left = NULL, root->right = NULL
打印第五大的元素 = 2
最后,因为我们已经进行了k次迭代,所以k = 5。现在,我们可以从新堆中提取根元素的值。在这种情况下,该值为2。 因此,我们已经找到了原始堆中第k大的值。
时间复杂度T(N,k)= O(klogk) 空间复杂度S(N,k)= O(k)
希望这有所帮助!
祝好, Soon Chee Loong, 多伦多大学。
在最大堆中,这是可能的,因为您只是从树中打印元素,而不是提取它们。
首先要找到位于根节点的最大元素。形成一个指向节点的指针,并将其添加到另外一个空的“最大值”列表中。然后,对于每个k
值,在循环中执行以下步骤。
因此,总运行时间为 O(klog(k)), 正如所期望的一样。
It is a simple and elegant algorithm to get first k elements of a max heap in k log(k) time.
steps:-
1.construct another max heap name it auxiliary heap
2.add root element of main heap to auxiliary heap
3.pop out the element from auxiliary heap and add it's 2 children to the heap
4.do step 2 and 3 till k elements have been popped out from auxiliary heap. Add the popped element's children to the auxiliary heap.
确实,提取最大元素太容易了,其时间复杂度为O(log(N))
,其中N
是堆的大小,且N≠K
。
我想补充一点,在堆中搜索随机元素的时间复杂度为O(N)
而不是O(Log(N))
,但是在这种情况下我们需要提取最大值。