发现书中的一点点的小失误
在本书的 3.44 链表反转的实现代码中,q._next = p
应该是写错了,如果你按着书上的写,会出现以下情况:
>>>m1 = LList()
>>>for i in range(i):
... m1.prepend(i)
...
>>>m1.printall()
9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 , 0
>>>m1.rev() # 调用反转方法
>>>m1.printall()
0
解决方法是将q._next = p
,改成q.next = p
下面是我的问题分析,可略过,欢迎指正,谢谢!
为什么会出现这种情况呢?(建议大家像书上一样,画出链表图)
我们逐条分析一下这个rev方法
:
while self._head is not None:
# 当self._head为空时,即当表内所有结点都已经处理完毕时,结束循环
q = self._head
# 将当前的首端结点赋值给 p
self._head = q.next
# 更新self._head,使其指向下一个结点
q._next = p
# 重点来了,这条语句创建了一个新的“结点属性” self._next,
# 并指向新表 p 的原首端结点
p = q
#p 指向新的首端结点,至此,一次插入操作完成。
self._head = p
# 旧表的所有结点处理完毕后,self._head为None,
# 将新表 p 赋值给 self._head
看到没?我们新创建了一个结点属性_next
,这个属性里保存了反转链表后的正确顺序,我们并没有更新结点的self.next
属性;也就是说,self.next
属性仍然保存着没反转的顺序!
我们找到了问题的源头,修改正确就可以了;
But,这完全不能满足我们的好奇心啊
我们更想知道的是,为啥问题会以这种形式表现出来呢?
上面说了,
self.next
依旧保持着原来的样子,原来是啥,反转完之后就还是啥;
那么原表的尾端结点,也就是最后一个取下来的结点,它的next域
依然还是None
。
而当所有原来表的结点都操作完成后,原表的尾端结点就变成了新表p
的首端结点,其next域
为None
。
rev方法
的最后语句,将新表p
赋值给self._head
,现在我们知道了,self._head
指向的首端结点,它的next
是None
。
以这个为例,原来的顺序是从小到大:
使用printall方法
时出现的显示不正确,那么我们看一下这个方法:
def printall(self):
p = self._head # 将首端结点赋值给 变量p
while p is not None: # 如果p不为None,则循环继续;否则,退出
print(p.elem, end = " ")
if p.next is not None: # 如果当前p的next域不为None
print(", ", end = " ")
p = p.next # 将p的next域所代表的下一个结点赋值给 p
print(" ")
可以来走一下这个流程:
1. 首先拿到首端结点;我们判断p
是否为None,显然不是,那么程序继续。
2. 打印了p.elem
的值。
3. 判断p.next
是否为None
,当然是None
,那么跳过if块
的内容。
4. 将p.next
所代表的值赋值给p
,现在p
为None
。
5. 回到第一步,判断p
是否为None
;至此循环结束。
看一下我们思考了什么:
- 当结果不符合我们的要求时,首先看一下出错的函数部分:
printall方法
(定位错误); - 如果
pythonall方法
没有错误,那么就是我们添加的方法有错误,经过梳理程序,我们发现原来next
没有更新。 - 将这种情况保留,通过模拟带入的方式,得到错误出现的原因(不止这一种方法)。
总结
本来这篇文章不应该这么长,到解决方法那里就可以结束了,可是能学到什么呢?大家一看:“哦,原来这么改就行了”;错误为什么是那样依旧不清楚,这样得到的东西很少很少,只要有时间,就多琢磨琢磨,当你知道为什么后,你可以想出其他的解决办法,说不定会比这个解决方法更高效。
初学者也要写出自己的想法,能写出来,或者给别人讲清楚,你就真的熟悉了;在接受别人提问和质疑的时候,是深入了解你所学的东西的好方法,所以大胆的写出来,别觉得没什么可写的,把你看的东西复述出来也行。