我一直在尝试编写一个递归的heapify方法,将整数数组转换为最小堆。 Main和Heap类如下所示。 Main中显示的大多数数组已经是最小堆,但子树[11,4,5]不是最小堆。但是,heapify函数似乎没有达到该子树。我无法弄清问题是什么,非常感谢任何帮助。
public class Heap {
public Heap(int[] array) {
heap = array;
}
public void heapify() {
heapifyHelper(0);
}
public void heapifyHelper(int rootIndex) {
if(isLeafIndex(rootIndex)) {
return;
}
else {
int leftChildIndex = getLeftChildIndex(rootIndex);
int rightChildIndex = getRightChildIndex(rootIndex);
int leftChildValue = heap[leftChildIndex];
int rightChildValue = heap[rightChildIndex];
int rootValue = heap[rootIndex];
if(leftChildValue < rootValue && leftChildValue < rightChildValue) {
swap(rootIndex, leftChildIndex);
heapifyHelper(leftChildIndex);
heapifyHelper(rightChildIndex);
}
else if(rightChildValue < rootValue && rightChildValue < leftChildValue) {
swap(rootIndex, rightChildIndex);
heapifyHelper(leftChildIndex);
heapifyHelper(rightChildIndex);
}
}
}
public int getLeftChildIndex(int parentIndex) {
return 2 * parentIndex + 1;
}
public int getRightChildIndex(int parentIndex) {
return 2 * parentIndex + 2;
}
public int getParentIndex(int childIndex) {
if(childIndex == 0) {
throw new IllegalArgumentException("Cannot get the parent index of the root.");
}
else {
return (childIndex / 2) - 1;
}
}
public boolean isLeafIndex(int index) {
int leftIndex = getLeftChildIndex(index);
int rightIndex = getRightChildIndex(index);
if(leftIndex >= heap.length && rightIndex >= heap.length) {
return true;
}
else {
return false;
}
}
public void swap(int index1, int index2) {
int temp = heap[index1];
heap[index1] = heap[index2];
heap[index2] = temp;
}
public void printHeap() {
System.out.println(Arrays.toString(heap));
}
int[] heap;
}
public class Main {
public static void main(String[] args) {
int[] x = {0, 5, 2, 9, 11, 6, 12, 21, 32, 4, 5};
Heap heap = new Heap(x);
heap.printHeap();
heap.heapify();
heap.printHeap();
}
}
答案 0 :(得分:4)
heapifyHelper
中有几个问题:
public void heapifyHelper(int rootIndex) {
if(isLeafIndex(rootIndex)) {
return;
}
else {
int leftChildIndex = getLeftChildIndex(rootIndex);
int rightChildIndex = getRightChildIndex(rootIndex);
int leftChildValue = heap[leftChildIndex];
int rightChildValue = heap[rightChildIndex];
如果leftChildIndex == heap.length - 1
怎么办?然后rightChildValue
会产生ArrayIndexOutOfBoundsException
。
int rootValue = heap[rootIndex];
if(leftChildValue < rootValue && leftChildValue < rightChildValue) {
swap(rootIndex, leftChildIndex);
heapifyHelper(leftChildIndex);
heapifyHelper(rightChildIndex);
}
else if(rightChildValue < rootValue && rightChildValue < leftChildValue) {
如果两个孩子都平等,并且比父母小?在这种情况下,你根本不会互换。
swap(rootIndex, rightChildIndex);
heapifyHelper(leftChildIndex);
heapifyHelper(rightChildIndex);
}
}
}
而且未达到子树[11, 4, 5]
的原因是因为如果其中一个孩子小于父母,但是当你打电话给{{{}}时,你只会为孩子调用heapifyHelper
1}},节点heapifyHelper(1)
的两个子节点5
和9
都大于根值。 (实际上,你甚至不打电话给11
,因为heapifyHelper(1)
已经比其子女小了。)
但是,通过无条件重复(对存在的孩子)来纠正这一点并不能使你的heap[0]
正确无误。如果您从根到叶子重复,每个值最多可以冒出一个级别。您必须从叶子到根(1)重复,并且您需要完全筛选值,而不仅仅是一个级别。
如果您只与其中一个子项交换值,则每个位置最多被考虑两次。一旦将它与其父母进行比较,一旦将其与其子女进行比较。当你从根到叶子时,当你将一个位置与它的子节点进行比较时,它上面没有任何位置(没有更小索引的位置,甚至)都不能再被改变了。
因此每个值最多可以冒出一个级别。如果最小元素低于root的直接子元素,则root不会成为树中的最小元素。如果你从叶子(或者更确切地说是叶子的父母)开始,那么这些值可以根据需要冒出来。但是如果你只用一个较小的子项交换一个值(如果它小于该值),那么每个值仍然只能向下冒一个级别,这仍然不需要创建一个堆。
让我们考虑树
heapify
如果你从根到叶子,你首先交换 7
/ \
/ \
2 6
/ \ / \
1 3 4 5
和2
,给予
7
前两个级别现在是一个小堆。
然后你处理左子树,最后是右子树,产生
2
/ \
/ \
7 6
/ \ / \
1 3 4 5
完全。现在底部的两个级别由min-heaps组成,但堆属性在上面的级别中被销毁。要再次成为堆, 2
/ \
/ \
1 4
/ \ / \
7 3 6 5
必须进一步筛选(在这种情况下,只有一个级别)。
如果你从树叶到根,你首先要对待正确的子树,
1
制造
6
/ \
4 5
为此,然后是左子树
4
/ \
6 5
制造
2
/ \
1 3
那里。这两个子树现在都是最小的堆。总而言之,你有
1
/ \
2 3
然后你交换 7
/ \
/ \
1 4
/ \ / \
2 3 6 5
和7
,生成
1
现在root是最小的值,但最后一次交换破坏了左子树的heap属性。要再次成为堆, 1
/ \
/ \
7 4
/ \ / \
2 3 6 5
必须进一步筛选。
因此,您需要7
方法(和/或siftDown
方法),根据需要向下调整值(向上)。
siftUp
然后正确的private void siftDown(int index) {
int leftChildIndex = getLeftChildIndex(index);
if (leftChildIndex >= heap.length) {
// a leaf, no further sifting down possible
return;
}
int rightChildIndex = getRightChildIndex(index);
if ((heap[leftChildIndex] < heap[index])
&& (rightChildIndex >= heap.length || heap[rightChildIndex] >= heap[leftChildIndex)) {
// left child is smallest or only, and smaller than parent
swap(index, leftChildIndex);
siftDown(leftChildIndex);
} else
// left child not smaller than parent, or right child exists and is smaller than parent
if (rightChildIndex < heap.length && heap[rightChildIndex] < heap[index]) {
swap(index, rightChildIndex);
siftDown(rightChildIndex);
}
// otherwise, this one has no smaller child, so no more sifting needed
}
将是
heapify
这是有效的,因为如果你有一个(二元)树,其中两个子树都是min-sheaps,那么筛选根值会构造一个min-heap:
由于每个叶子都是一个小堆,对于public void heapify() {
// last index that has a child:
int lastNonLeafIndex = heap.length/2 - 1;
for(int index = lastNonLeafIndex; index >= 0; --index) {
siftDown(index);
}
}
中处理的每个索引,以该索引为根的子树变为最小堆。
替代方案,使用heapify
:
siftUp
private void siftUp(int index) {
if (index == 0) return; // root, nothing to do
int parentIndex = getParentIndex(index); // see Note below
if (heap[index] < heap[parentIndex]) {
swap(index, parentIndex);
siftUp(parentIndex);
}
}
public void heapify() {
for(int index = 1; index < heap.length; ++index) {
siftUp(index);
}
}
的代码比siftUp
短得多,因为这里只涉及两个节点,并且不需要检查是否有任何子索引落在数组之外。但siftDown
的效率较低(见脚注(1))。
heapify
是用于将新值插入堆中的方法。所以这个通过将所有值(根值除外)插入到现有的min-heap中来构建堆[在调用siftUp
时,siftUp(index)
之前的数组部分已经是最小堆]
注意:您的index
不正确,
getParentIndex
表示索引return (childIndex / 2) - 1;
的父级为1
,索引-1
的父级为3
,正确为
0
(1)实际上,如果你根据需要筛选每个值,你可以从根到叶子。从[叶子的父母]到根目录的堆积化效率更高。如果您从根到叶子,在return (childIndex - 1) / 2;
级别,您有k
个值可能需要冒出2^k
个级别,这会使k
的复杂度变为O(n*log n)
堆。如果您从[父母]向上离开,则您有2^(log n - 1 - k)
个值可能需要降低k
级别,这会使构建堆的复杂度为O(n)
。< / p>
答案 1 :(得分:0)
所以我想我弄清楚问题是什么。
您的heapify帮助程序会在您找到root小于leftChild和rightChild的根目录时停止。
在运行你的情况时......你会遇到root(5)小于11和9的情况。但是11没有堆积..
解决此问题的方法很多。我留给你了。
EDIT 因此,heapify理想情况下只是将rootIndex中的第一个元素放在正确的位置。不要创建堆。
如果要创建正确的堆,则需要插入新元素并在每个此类插入上调用heapify。