二进制堆插入,不了解循环

时间:2014-11-05 12:58:13

标签: algorithm binary-heap

在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;是什么意思?看来他正在覆盖根值?这似乎是一段非常人为的代码。他在做什么?

2 个答案:

答案 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值确保循环终止。

然后我们将值xhole / 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)类型循环。