实现书中给出的几个功能,方便对照书籍发现自己的错误
书上实现了大概以下几个功能:
prepend功能:建立新结点,并插入到首端。
append功能 :建立新结点,并插入到尾端。
pop功能 :前端弹出。
printall功能:输出表元素。
首先建立ADT模型
,把要实现的功能和大概用到的数据写出来,整理下思路,下面是建立ADT模型
之前的考虑:
需不需要从我们实现过的链表类中派生LCList类
?
可以发现,重复用到的只有尾指针self._rear
,和计数器self._len
,其他的方
法都需要覆盖重写,那么就不需要从其他链表类中派生。
下面是用XMind
做的,类似于ADT模型
:
下面我们按着书中实现的顺序对比我自己写的功能进行分析(略过一目了然的功能)。
我是实现的prepend功能
:
def prepend(self, elem):
if self._rear is None: #表是空滴
p = LNode(elem) # 建立新结点
self._rear = p # 更新尾结点
p.next = p # 将结点next域指向寄己
self._len += 1
else:
p = self._rear # p 为尾结点
p.next = LNode(elem, p.next)
self._len += 1
书上实现的prepend功能
:
def prepend(self, elem):
p = LNode(elem) # 将重复建立结点的语句抽出来
if self._rear is None:
p.next = p # 建立一个结点的环,结点的next域指向寄己
self._rear = p
self._len += 1 # 计数器,__init__中创建
else:
p.next = self._rear.next #新建结点的next域,指向原来的首端结点
self._rear.next = p
self._len += 1
我实现的append功能
:
def append(self, elem):
if self._rear is None:
self.prepend(elem) # 这里这么写,是因为空表添加的代码与prepend方法空表添加时,是一样的
else:
p = self._rear
p.next = LNode(elem, p.next)
self._rear = p.next # 更新尾结点,哪个是尾结点,self._rear说了算
self._len += 1
前方高能!!!
书上实现的append功能
:
def append(self, elem):
self.prepend(elem)
self._rear = self._rear.next
很简洁有没有,我想过这么写,但有点担心给别人看的话可能不那么直观;现在看来,只需要模拟一下整个流程,完全可以搞懂的。
这里看一下书中91页
的说明:
由于循环链表里的结点连成一个圈,哪个结点算是表头或表尾,主要是概念问题,从表的内部形态上无法区分。
表中唯一辨识尾结点的就是尾指针self._rear
,而无论是将结点插入表头还是表尾,过程完全是一样的,区别仅仅在于self._rear
的指向,它指谁,谁就是表尾,所以完全可以将插入结点部分交给prepend方法
来做,append方法
只需要修改self._rear
指向。
跳过pop功能
,比较简单,当然我实现的比书上罗嗦点。
我实现的printall功能
:
def printall(self):
if self._rear is None:
return None
p = self._rear.next # 首端结点赋值给变量P
end = self._rear # end为尾端结点
while p is not end: # 当p 是尾端结点时,结束循环
print(p.elem, end="")
p = p.next
print(p.elem)
比较罗嗦,如果别人读这样的代码可能就要花点时间理顺。
书上实现的printall功能
:
def printall(self):
if self.is_empty():
return
p = self._rear.next # 首结点
while True:
print(p.elem)
if p is self._rear: # 如果 p 是最后的结点
break # break直接结束循环
p = p.next
将 p 设置为尾结点也可以,改动判断条件就行了。
总结
我们看一下这里的思想:
面向对象编程具有封装性:实现与调用分离,无论内部怎么实现,只要满足功能,提供调用的接口就可以。
那么,就这样,欢迎指正和提问,谢谢!