目录
1. 单链表
1.1 普通单链表(分析)
【表结点类】
元素域(elem) & 链接域(next)
class LNode(object):
"""结点"""
def __init__(self, elem, next_=None): #使用next_是为了避免与标准函数next重名
self.elem = elem
self.next = next_ #初始设置下一节点为空
【基本链表操作】
创建空链表:把表头指针设置为空链接,Python中用None表示空.
删除链表:把表头指针赋值为None.
判断表是否为空:检查表头变量的值是否为None.
判断表是否满:当存储空间足够时,链表不会满.
【加入元素】
表首端插入:
1)创建一个新结点并存入数据;
2)把原链表首结点的链接存入新结点的链接域next;
3)修改表头指针,使之指向新结点.
q = LNode('data') #创建新结点
q.next = head #连接新结点与原链表
head = q #修改表头指针
一般情况插入:
1)创建一个新结点并存入数据;
2)将pre所指结点next域的值存入新结点的链接域next;
3)修改pre的next域,使之指向新结点.
q = LNode('data') #创建新结点
q.next = pre.next #新结点与原表后段连接
pre.next = q #原表前段与新结点连接
【删除元素】
以下均假设表非空.
删除表首元素:
直接将表头指针指向表中第二个结点.
head = head.next
一般情况的元素删除:
找到被删除元素所在结点的前一结点,将它的next域指向被删除结点的下一结点.
pre.next = pre.next.next
【扫描、定位和遍历】
基本扫描模式:
p = head
while p is not None and '其他条件':
'相关操作'
p = p.next
按下标定位:
确定第 i 个元素所在结点,注意,链表首结点的下标为0.
p = head
while p is not None and i > 0 :
i -= 1
p = p.next
循环结束时,要么没有找到(p指向None),要么找到,此时检查p的值是否为None即可.
按元素定位:
pred为相应条件的谓词.
p = head
while p is not None and not pred(p.elem):
p = p.next
【求表的长度】
def length(head):
p = head
n = 0
while p is not None:
n += 1
p = p.next
return n
【后端操作】
后端操作必须先找到链表的最后一个结点.
后端插入元素:
这里需要区分两种情况:
1)原表为空,引用新结点的应该是表头指针的next域;
2)原表不为空,引用新结点的应该是已有最后结点的next域.
由于插入的元素位于后端,其结点的next域为默认的None,无需再指明.
def append(self, elem):
if self._head is None:
self._head = LNode(elem)
return
p = self._head
while p is not None:
p = p.next
p.next = LNode(elem)
后端删除元素:
后端操作必须先找到链表的最后一个结点,而若欲删除后端元素,则需找到它的前一个结点,即表的倒数第二个结点,也就是找到 p.next.next 为None的p.
此外,当表中只有一个元素时需要特殊处理,这是需要修改表头指针.
def pop_last(self):
if self._head is None: #表为空,引发异常
raise LinkedListUnderflow("in pop_last")
p =self._head
if p.next is None: #表中只有一个元素
e = p.elem
self._head = None
return e
while p.next.next is not None
p = p.next
e = p.next.elem
p.next = None
return e
【查找元素】
def find(self, pred):
p = self._head
while p is not None:
if pred(p.elem):
return p.elem
p = p.next
【打印所有元素信息】
def printall(self):
p = self._head
while p is not None:
print(p.elem, end='')
if p.next is not None:
print(', ', end='')
p = p.next
print('')
1.2 普通单链表(实现)
class LList(object):
"""单链表"""
def __init__(self):
self._head = None
def is_empty(self):
return self._head is None
def prepend(self, elem): #表头插入元素,这里三步合为一步
self._head = LNode(elem, self._head) #elem来自于stdin,head来自于原表
def pop_first(self): #删除表头结点并返回其元素
if self._head is None: #表为空,引发异常
raise LinkedListUnderflow("in pop")
e = self._head.elem
self._head = self._head.next
def append(self, elem):
if self._head is None:
self._head = LNode(elem)
return
p = self._head
while p is not None:
p = p.next
p.next = LNode(elem)
def pop_last(self):
if self._head is None: #表为空,引发异常
raise LinkedListUnderflow("in pop_last")
p =self._head
if p.next is None: #表中只有一个元素
e = p.elem
self._head = None
return e
while p.next.next is not None
p = p.next
e = p.next.elem
p.next = None
return e
def find(self, pred):
p = self._head
while p is not None:
if pred(p.elem):
return p.elem
p = p.next
def printall(self):
p = self._head
while p is not None:
print(p.elem, end='')
if p.next is not None:
print(', ', end='')
p = p.next
print('')
def length(head):
p = head
n = 0
while p is not None:
n += 1
p = p.next
return n
1.3 带有尾结点引用的单链表
将表对象增加一个表尾结点引用域,只需常量时间就能找到尾结点.
此 class 以 LList 为基类.
【初始化和变动操作】
在LList1的初始化函数中,应该首先初始化LList对象的那些数据域,对此可直接对self对象调用LList类的初始化函数.现在还需要一个尾结点引用域,将它初始化为None.
def __init__(self):
LList.__init__(self)
self._rear = None
前端插入操作:
一般情况下的操作与LList相同,但当表为空时则不同.如果原来是空表,则新加入的第一个结点也是最后一个结点.
def prepend(self, elem):
if self._head is None:
self._head = LNode(elem,self._head)
self._rear = self._head
else:
self._head = LNode(elem,self._head)
后端插入操作:
def append(self, elem):
if self._head is None:
self._head = LNode(elem,self._head)
self._rear = self._head
else:
self._rear.next = LNode(elem) #创建新结点
self._rear = self._rear.next #更新尾结点引用
弹出末元素操作:
删除尾结点之后还需更新 _rear.
def pop_last(self):
if self._head is None: #空表
raise LinkedListUnderflow("in pop_last")
p = self._head
if p.next is None: #表中只有一个元素
e = p.elem
self._head = None
return e
while p.next.next is not None: #p.next是尾结点
p = p.next
e = p.next.elem
p.next = None
self._rear = p
return e
1.4 循环单链表
最后一个结点的next域不用None,而是指向表的第一个结点.但仔细考虑,会发现在链表对象里记录表尾结点更合适,这样可以同时支持O(1)时间的表头/表尾插入和O(1)时间的表头删除.
class LCList:
def __init__(self):
self._rear = None
def is_empty(self):
return self._rear is None
def prepend(self,elem):
p = LNode(elem)
if self._rear is None:
p.next = p
self._rear = p
else:
p.next = self._rear.next
self._rear.next = p
def append(self,elem):
self.prepend(elem)
self._rear = self._rear.next
def pop(self):
if self._rear is None:
raise LinkedListUnderflow("in pop of CLList")
p = self._rear.next
if self._rear is p:
self._rear = None
else:
self._rear.next = p.next
return p.elem
def printall(self):
if self.is_empty():
return
p = self._rear.next
while True:
print(p.elem)
if p is self._rear():
break
p = p.next
2. 双链表
【结点操作】
结点删除:
p.prev.next = p.next
p.next.prev = p.prev
结点加入:
【双链表类】
1)双链表的结点在LNode类的基础上派生,多了一个反向引用域prev.
2)首端/尾端的相同操作几乎是对称的,其中的_head与_rear对应,next与prev对应.
class DLNode(LNode):
def __init__(self,elem,prev=None,next_=None):
LNode.__init__(self,elem,next_)
self.prev = prev
class DLList(LList1):
def __init__(self):
LList1.__init__(self)
def prepend(self,elem):
p = DLNode(elem,None,self._head)
if self._head is None:
self._rear = p
else:
p.next.prev = p
self._head = p
def append(self,elem):
p = DLNode(elem,self._rear,None)
if self._head is None:
self._head = p
else:
p.prev.next = p
self._rear = p
def pop_first(self):
if self._head is None:
raise LinkedListUnderflow("in pop_first of DLList")
e = self._head.elem
self._head = self._head.next
if self._head is not None:
self._head.prev = None
return e
def pop_last(self):
if self._head is None:
raise LinkedListUnderflow("in pop_last of DLList")
e = self._rear.elem
self._rear = self._rear.prev
if self._rear is None:
self._head = None
else:
self._rear.next = None
return e
3. 两个链表操作
【链表反转】
对于单链表而言,如果不断向一个表的首端插入结点,最早放进去的结点将在表的最后.相应的,从表的首端取下结点,最后取下的是尾结点.也就是说,从一个表的首端不断取下结点,将其加入另一个表的首端,就形成了一个反转过程.
def rev(self):
p = None #创建一个新链表
while self._head is not None:
q = self._head #取下首结点
self._head = q.next #更新表头指针
q.next = p #取下的结点与新表连接
p = q #新表对象更新
self._head = p #反转后的表头指针
【链表排序】
基于移动元素的单链表排序:
1)大循环:扫描指针crt每次处理一个元素x并前进一步;
2)小循环:从头开始到指针位置扫过小于或等于x的元素,直到找到第一个大于x的元素;
3)做一系列“倒换”,将x放入正确位置,并将其它元素后移.
def sort_1(self):
if self._head is None:
return
crt = self._head.next
while crt is not None:
x = crt.elem
p = self._head
while p is not crt and p.elem <= x:
p = p.next
while p is not crt:
y = p.elem #取出p当前元素
p.elem = x #将x放入当前p中
x = y #更新x为之前p中元素
p = p.next #p前进一步
crt.elem =x #crt元素更新为crt前一结点元素
crt = crt.next #crt前进一步
基于调整链接的单链表排序:
1)表头为空或表只有一个元素,自然处于有序状态,无序排序;
2)用rem记录除第一个元素之外的结点段,通过循环把这些结点逐一插入_head关联的排序段;
3)p指针用于从表头扫描到rem,直到找到第一个大于rem.elem的元素,此时p指向该元素所在结点,q指针用于指向该结点之前的那个结点,那么rem应插入q和p之间;
4)注意区分表头插入和一般插入;
5)连接好q,rem,p后,将rem前进一步.
def sort_2(self):
p = self._head
if p is None or p.next is None:
return
rem = p.next
p.next = None
while rem is not None:
p = self._head
q = None
while p is not None and p.elem <= rem.elem:
q = p
p = p.next
if q is None: #表头插入
self._head = rem
else: #一般插入
q.next = rem #只是将q与rem连接而未将rem与后面部分连接
q = rem #用q暂存已排序的rem结点
rem = rem.next #rem结点前进一步
q.next = p #更新已排序的rem结点的next域