在Weiss的“Java数据结构和算法”中,他解释了二进制堆的插入算法
public void insert( AnyType x )
{
if( currentSize == array.length -1)
enlargeArray( array.length * 2 + 1);
// Percolate up
int hole = ++currentSize;
for(array[0] = x; x.compareTo( array[ hole / 2 ]) < 0; hole /=2 )
array[ hole ] = array[ hole / 2 ];
array[ hole ] = x;
}
我得到了在树上移动一个洞的原则,但我不明白他是如何在for循环中使用这种语法完成它的...初始化程序array[0] = x;
是什么意思?看来他正在覆盖根值?这似乎是一段非常人为的代码。他在做什么?
答案 0 :(得分:1)
首先,我得到了Mark Weiss的回复,他的电子邮件基本上说代码是正确的(在这个答案的底部完全回复)。
他也说过:
因此,最小项目在数组索引1中,如findMin中所示。要进行插入,请遵循从底部到根的路径。
指数1?嗯......然后我不得不回去重新阅读本章的大部分内容,当我看到图6.3点击它时。
数组是从0开始的,但是被认为是堆的一部分的元素是从索引1开始存储的。图6.3看起来像这样:
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| | A | B | C | D | E | F | G | H | I | J | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
在元素0处放置值是一个sentinel值,以使循环终止。
因此,通过上面的树,让我们看看插入函数是如何工作的。下方H
标志着这个洞。
首先我们将x
放入第0个元素(堆外),并将孔放在数组中的下一个可用元素。
H
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| x | A | B | C | D | E | F | G | H | I | J | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
然后我们冒泡(渗透)洞,将值从“索引的一半”向上移动,直到找到放置x
的正确位置。
如果我们看一下图6.5和6.6,让我们将实际值放入数组中:
H/2 H
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 14 | 13 | 21 | 16 | 24 | 31 | 19 | 68 | 65 | 26 | 32 | | | |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
请注意,我们将14(要插入的值)放入索引0,但这是在堆之外,我们的sentinel值确保循环终止。
然后我们将值x
与hole / 2
的值进行比较,现在是11/2 = 5. x小于31,所以我们将值向上移动并移动洞:< / p>
H/2 H <---------------------------
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 14 | 13 | 21 | 16 | 24 | 31 | 19 | 68 | 65 | 26 | 32 | 31 | | |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
| ^
+--------- move 31 -----------+
我们再次比较,14再次小于21(5/2 = 2),所以再一次:
H/2 H <------------
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 14 | 13 | 21 | 16 | 24 | 21 | 19 | 68 | 65 | 26 | 32 | 31 | | |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
| ^
+-- move 21 ---+
然而,现在,14不小于13(洞/ 2 - > 2/1 = 1),所以我们找到了x
的正确位置:
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 14 | 13 | 14 | 16 | 24 | 21 | 19 | 68 | 65 | 26 | 32 | 31 | | |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
0 1 2 3 4 5 6 7 8 9 10 11 12 13
^
x
如您所见,如果您查看插图6.6和6.7,这符合预期的行为。
因此,虽然代码没有错,但是你有一个小问题可能超出了本书的范围。
如果要插入的x
类型是引用类型,则当前堆中将对刚刚插入的同一对象有2个引用。如果你然后立即从堆中删除该对象,它看起来(但看起来看起来像首先得到我们......)就像第0个元素仍将保留引用,禁止垃圾收集器从做它的工作。
为确保此处没有隐藏的议程,以下是Mark的完整答案:
Hi Lasse,
代码是正确的。
二进制堆是一个完整的二叉树,其中包含来自a的任何路径 从根到底,价值永远不会增加。因此最小化 item在根目录。数组表示将根放在 索引1,对于索引i处的任何节点,父节点为i / 2(舍入 (左)孩子在2i,右孩子在2i + 1,但那 这里不需要。)
因此,最小项目在数组索引1中,如图所示 findMin。要进行插入,请按照从底部到的路径进行操作 根。
在for循环中:
hole / = 2表示将洞移动到父级的想法。
x.compareTo(array [hole / 2])&lt; 0表达了我们留在的想法 只要x小于父级,就可以循环。
问题在于,如果x是新的最小值,那么你永远不会离开 安全循环(技术上你试图比较x和数组[0]崩溃)。 您可以进行额外的测试以处理角落情况。 或者,代码通过将x放在array [0]中来解决这个问题 开始,并且因为节点i的“父”是i / 2,所以是“父” 索引1中的根可以在索引0中找到。这保证了 如果x是新的最小值,则循环终止(然后放置x,即 是索引1的根中的新最小值。
书中有一个更长的解释......但这里的基本概念是 使用sentinel(或dummy)值来避免额外的代码 边界案件。
此致
Mark Weiss
答案 1 :(得分:0)
数组初始化程序看起来不对。如果是数组[hole] = x;那么整个事情就完全有道理了。
它首先将值放在树的最低等级(当前大小之后的条目),然后它在“它上面的条目”中查找&#39;通过观察(int)hole / 2。
它一直向上移动,直到比较器告诉它停止。我认为这是对for循环语法的轻微误用,因为它感觉它真的是一段时间(x.compare(hole / 2)&lt; 0)类型循环。