Heapify功能不起作用

时间:2013-02-15 20:18:58

标签: java data-structures heap

我一直在尝试编写一个递归的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();
}
 }

2 个答案:

答案 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)的两个子节点59都大于根值。 (实际上,你甚至不打电话给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:

  • 如果根值小于(或等于)其子项,则整个树已经是最小堆。
  • 否则,在根值与其子项中较小的子项交换后(不失左侧的一般性),另一个子树不变,因此仍然是最小堆。并且,由于左子节点是交换前左子树中的最小值,因此根节点的值是交换后整个树中的最小值。但是,交换可能已经破坏了左子的min-heap属性。但是左 - 右和右 - 右子树没有改变,所以它们仍然是最小的。并且新的左子树小于原始树,因此通过归纳假设,筛选其根值会从中创建一个最小堆。在筛选完成后,我们在根处有一个值最小的树,其子树都是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。