这是一个具有额外功能的堆节点;基于模2的“负载平衡”:
#! /usr/bin/env python
class Lifo:
def __init__(self):
# repr using tuple
self.lifo = ()
def push(self, data):
# pack data using tuple
self.lifo = (data, self.lifo)
def pop(self):
# unpack data using tuple
# raise ValueError when empty
data, self.lifo = self.lifo
return data
def __len__(self):
return len(self.lifo)
def __repr__(self):
return str(self.lifo)
class HeapNode:
def __init__(self, value, left=None, right=None):
self.data = value
self.left = left
self.right = right
def __repr__(self):
if self.left is None and self.right is None:
return '(%s)'%(self.data)
repr_left = '*' if self.left is None else repr(self.left)
repr_right = '*' if self.right is None else repr(self.right)
return '(%s L%s R%s)'%(self.data, repr_left, repr_right)
def add(self,data,count):
# do traversal with stack
lifo = Lifo()
lifo.push(self)
print 'push\'d self is %s, lifo is %s'%(self,lifo)
while len(lifo) > 0 :
# self's modified here
self = lifo.pop()
print 'popp\'d self is %s, lifo is %s'%(self,lifo)
if count % 2 == 0 :
if self.right is None:
self.right = HeapNode(data)
else:
lifo.push(self.right)
else:
if self.left is None:
self.left = HeapNode(data)
else:
lifo.push(self.left)
print 'returning self is %s'%(self,)
return self
if __name__ == '__main__':
heap = HeapNode(11)
heap.add(7,0).add(4,1).add(10,2)
输出:
push'd self is (11), lifo is ((11), ())
popp'd self is (11), lifo is ()
returning self is (11 L* R(7))
heap [(11 L* R(7))]
push'd self is (11 L* R(7)), lifo is ((11 L* R(7)), ())
popp'd self is (11 L* R(7)), lifo is ()
returning self is (11 L(4) R(7))
heap [(11 L(4) R(7))]
push'd self is (11 L(4) R(7)), lifo is ((11 L(4) R(7)), ())
popp'd self is (11 L(4) R(7)), lifo is ()
popp'd self is (7), lifo is ()
returning self is (7 L* R(10))
heap [(11 L(4) R(7 L* R(10)))]
上述情况如何发生,即self
(在添加函数中)是(7 L* R(10)
但是堆被引用到(11 L(4) R(7 L* R(10)))
?
据我所知,如果是heap = heap.add_stack(10,2)
,那么返回值就是堆。
但我无法理解的是 将元素11 L(4)
添加到(7 L* R(10))
,
这是引起这种情况的一些参考和价值传递吗?
有人可以清楚解释一下吗?
答案 0 :(得分:0)
您的add
方法似乎等同于此递归版本:
def add(self, data, count):
if count % 2:
if self.right is None:
self.right = HeapNode(data)
return self # note, returning the node before the new leaf
else:
return self.right.add(data, count) # recurse
else:
if self.left is None:
self.left = HeapNode(data)
return self
else:
return self.left.add(data, count) # recurse
由于这是“尾递归”,它可以很容易地转换成一个版本,看起来很像你已经拥有的版本。请注意,虽然不需要堆栈,因为在任何给定时间只有一个current
值(您从未调用push
两次,而中间没有pop
):
def add(self, data, count):
current = self # lets use a different variable name rather than rewriting self
while True: # loop until broken by a return
if count % 2:
if current.right is None:
current.right = HeapNode(data)
return current
else:
current = current.right
else:
if current.left is None:
current.left = HeapNode(data)
return current
else:
current = current.left
我避免了代码重新绑定self
变量名称的陷阱,这可能会非常混乱。将self
作为调用方法的对象,并为我们当前正在处理的对象使用不同的名称,这是一个更好的主意。
我怀疑这个实现中存在一个潜伏的错误,并且你想在每个递归步骤上修改count
(可能除以2?),但是没有更好地理解你的代码应该是什么做我真的不能确定。
我认为你感到困惑的是返回值如何与链式调用相互作用。返回的值始终是到达的最后一个非叶节点。因此,如果您拨打node.add(a, b)
,然后单独拨打node.add(c, d)
,则可能会获得与您一次拨打node.add(a, b).add(c, d)
时不同的结果。原因是在第一种情况下node
变量不会发生变化,而add
被调用的对象在链接版本中的两次调用之间可能不同。第二个版本相当于:
temp = node.add(a, b)
temp.add(c, d)
del temp
如果希望链接调用的结果与根节点上对函数的重复调用相同,则需要将代码更改为始终返回根元素,而不是递归或迭代遍历的结果。在递归版本的代码中,只需返回self
而不是返回递归调用的结果。在迭代版本中,返回self
而不是current
。
或者你可以放弃所有链接而不返回任何东西(在Python中相当于返回None
)。这可能是最“Pythonic”的解决方案(像list.append
和set.add
这样的类似方法不会返回任何内容。)
编辑:这是输出的解释。
您从一个节点(11)
开始。第一步是您使用参数add
和7
在其上调用0
。该调用负责输出的前三行:
push'd self is (11), lifo is ((11), ())
popp'd self is (11), lifo is ()
returning self is (11 L* R(7))
根节点将自己推入堆栈,然后再次弹出。创建了一个新节点(7)
,并将其添加为(11)
的正确子节点,将其转换为(11 L* R(7))
。
我不确定输出的下一行来自哪里:
heap [(11 L* R(7))]
它不是由您显示的任何代码打印出来的,所以我将忽略它(以及后面的类似行)。
第一个add
调用返回了被叫,然后您正在调用下一个add
。它仍然是同一节点,所以链接没有做任何棘手的事情。 add
调用具有参数4
和1
,并产生三行输出:
push'd self is (11 L* R(7)), lifo is ((11 L* R(7)), ())
popp'd self is (11 L* R(7)), lifo is ()
returning self is (11 L(4) R(7))
这次它将新节点(4)
添加为根节点的左子节点。否则它与上面相同。第三个add再次在同一节点上调用,但这次它会做更多的工作。我会逐行解释这个时间发生了什么。
push'd self is (11 L(4) R(7)), lifo is ((11 L(4) R(7)), ())
popp'd self is (11 L(4) R(7)), lifo is ()
前两行与我们到目前为止看到的相同,因为根节点被推送然后从堆栈中弹出。但是,这次代码看到self
节点上已有一个正确的子节点,因此它会推送该子节点并再次循环。请注意,第二个push
没有与之关联的任何print
语句,因此您可能看起来没有相同数量的推送和弹出。但是如果你完成了逻辑,那么每次循环都会有一个。
popp'd self is (7), lifo is ()
returning self is (7 L* R(10))
在弹出下一个节点后,self
现在是(7)
节点,而不是每个其他节点上的根节点。一个新的(10)
节点被添加为它的右子节点和节点(现在(7 L* R(10))
被返回。此时你的链接很可能开始表现得很奇怪。如果你链接了另一个添加,它会有已经应用于返回的节点,而不是像以前一样应用于根。所以如果你开始使用变量(0)
中的heap
节点,heap.add(1,0).add(2,1).add(3,2).add(4,3)
会表现得很奇怪:heap
会变成(0 L(2) R(1 L(4) R(3)))
,而不是(0 L(2 L(4) R*) R(1 L* R(3)))
,如果你没有链接电话,那就是你所得到的。