数据结构--python第三章链表之双向链表
前面已经总结过了链表中的单向链表、环形链表,接下来我们继续谈谈链表中的另一种之双向链表。
单向链表和环形链表都属于拥有方向性的链表,只能单向遍历,万一不幸其中有一个链表断裂,那么后面的链表数据便会遗失而无法复原。因此我们可以将两个不同方向的链表结合起来,除了存放数据的字段,他还有两个指针变量,其中一个指针指向后面的节点,另一个指针则指向前面的节点,这样的链表被称为双向链表(Double Linked List).每个节点很轻松的能够找到其前后节点,同时任意的节点都可以找到其他节点,而不需要经过反转或对比节点等处理。
优点:有两个指针分别指向节点前后两个节点,所以能够轻松的找到前后节点,同时从双向链表的任意节点都可以找到其他任意节点,不需要经过反转或对比等处理。
缺点:由于双向链表中每个节点都有两个连接,因此在加入或删除节点时,需要更多的时间来调整指针,另外每个指针均含有两个指针变量,所以较为浪费空间。
环形双向链表:如果最后一个节点的右指针指向首节点(头部节点),而首节点的左指针指向尾节点,这样的链表就会环形双向链表。
3.3.1 双向链表的建立与遍历
例子:设计一个python程序,可以让用户输入数据来新增学生数据节点,并建立一个双向链表。当用户输入结束后,可遍历此链表并显示其内容。
# CH03-12.py
class student:
def __init_(self):
self.name=''
self.Math=0
self.Eng=0
self.no=''
self.rlink=None
self.llink=None
head=student()
head.llink=None
head.rlink=None
ptr=head # 设置存取指针开始的位置
select=0
while True:
select=int(input('(1)新增 (2)离开 =>'))
if select==2:
break
new_data=student()
new_data.name=input('姓名: ')
new_data.no=input('学号: ')
new_data.Math=eval(input('数学成绩: '))
new_data.Eng=eval(input('英语成绩: '))
# 输入节点结构中的数据
ptr.rlink=new_data
new_data.rlink = None # 下一个元素的next先设置为None
new_data.llink=ptr # 存取指针设置为新元素所在的位置
ptr=new_data
ptr = head.rlink # 设置存取指针从链表头的右指针字段所指的节点开始
print()
while ptr!= None:
print('姓名:%s\t学号:%s\t数学成绩:%d\t英语成绩:%d' \
%(ptr.name,ptr.no,ptr.Math,ptr.Eng))
ptr = ptr .rlink # 将ptr移往右边下一个元素
# 结果
(1)新增 (2)离开 =>1
姓名: daniel
学号: 1001
数学成绩: 100
英语成绩: 100
(1)新增 (2)离开 =>1
姓名: marry
学号: 1002
数学成绩: 99
英语成绩: 96
(1)新增 (2)离开 =>2
姓名:daniel 学号:1001 数学成绩:100 英语成绩:100
姓名:marry 学号:1002 数学成绩:99 英语成绩:96
例子:设计一个python程序,鲜香右遍历所建立双向链表并输出所有学生的数据节点,再向左遍历所有节点并输出信息。
# C03-13.py
select=0
class student:
def __init_(self):
self.name=''
self.Math=0
self.Eng=0
self.no=''
self.rlink=None
self.llink=None
head=student()
head.llink=None
head.rlink=None
ptr=head # 设置存取指针开始的位置
select=0
while True:
select=int(input('(1)新增 (2)离开 =>'))
if select==2:
break;
new_data=student()
new_data.name=input('姓名: ')
new_data.no=input('学号: ')
new_data.Math=eval(input('数学成绩: '))
new_data.Eng=eval(input('英语成绩: '))
# 输入节点结构中的数据
ptr.rlink=new_data
new_data.rlink = None # 下一个元素的next先设置为None
new_data.llink=ptr # 存取指针设置为新元素所在的位置
ptr=new_data
print('-----向右遍历所有节点-----')
ptr = head.rlink # 设置存取指针从链表头的右指针字段所指的节点开始
while ptr!=None:
print('姓名:%s\t学号:%s\t数学成绩:%d\t英语成绩:%d' \
%(ptr.name,ptr.no,ptr.Math,ptr.Eng))
if ptr.rlink==None:
break
ptr = ptr .rlink # 将ptr移往右边下一个元素
print('-----向左遍历所有节点-----')
while ptr != None:
print('姓名:%s\t学号:%s\t数学成绩:%d\t英语成绩:%d' \
%(ptr.name,ptr.no,ptr.Math,ptr.Eng))
if(ptr.llink==head):
break
ptr = ptr .llink
# 结果
(1)新增 (2)离开 =>1
姓名: danile
学号: 1002
数学成绩: 100
英语成绩: 100
(1)新增 (2)离开 =>1
姓名: marry
学号: 1003
数学成绩: 99
英语成绩: 99
(1)新增 (2)离开 =>1
姓名: jasmine
学号: 1001
数学成绩: 100
英语成绩: 100
(1)新增 (2)离开 =>2
-----向右遍历所有节点-----
姓名:danile 学号:1002 数学成绩:100 英语成绩:100
姓名:marry 学号:1003 数学成绩:99 英语成绩:99
姓名:jasmine 学号:1001 数学成绩:100 英语成绩:100
-----向左遍历所有节点-----
姓名:jasmine 学号:1001 数学成绩:100 英语成绩:100
姓名:marry 学号:1003 数学成绩:99 英语成绩:99
姓名:danile 学号:1002 数学成绩:100 英语成绩:100
3.3.2 在双向连链表中插入新节点
1)将新节点加入到双向链表的第一个节点之前:将新节点的右指针指向原链表的第一个节点,接着将原链表第一个节点的左指针指向新节点,将原链表表头指针指向新节点
2)将新节点加入双向链表的末尾:将原链表的最后一个节点的右指针指向新节点,将新节点的左指针指向原链表的最后一个节点,并将新节点的右指针指向None.
3)将新节点加入到链表中ptr节点之后:首先将ptr节点的右指针指向新节点,再将新节点的左指针指向ptr节点,接着将ptr节点的下一个节点的左指针指向新节点,最后将新节点的右指针指向ptr节点的下一个节点。
例子:设计一个python程序,建立员工数据的双向链表,并且允许可以在链表头部、尾部、链表中间3种不同的位置插入节点,最后离开时,列出链表所有节点的信息。
# CH03-14.py
class employee:
def __init__(self):
self.num=0
self.salary=0
self.name=''
self.llink=None # 左指针字段
self.rlink=None # 右指针字段
def findnode(head,num):
ptr=head
while ptr!=None:
if ptr.num==num:
return ptr
ptr=ptr.rlink
return ptr
def insertnode(head,ptr,num,salary,name):
newnode=employee()
newhead=employee()
newnode.num=num
newnode.salary=salary
newnode.name=name
if head==None: # 双向链表是空的
newhead.num=num
newhead.salary=salary
newhead.name=name
return newhead
else:
if ptr==None:
head.llink=newnode
newnode.rlink=head
head=newnode
else:
if ptr.rlink==None: # 插入链表末尾的位置
ptr.rlink=newnode
newnode.llink=ptr
else: # 插入中间节点的位置
newnode.rlink=ptr.rlink
ptr.rlink.llink=newnode
ptr.rlink=newnode
newnode.llink=ptr
return head
llinknode=None
newnode=None
position=0
data=[[1001,32367],[1002,24388],[1003,27556],[1007,31299], \
[1012,42660],[1014,25676],[1018,44145],[1043,52182], \
[1031,32769],[1037,21100],[1041,32196],[1046,25776]]
namedata=['Allen','Scott','Marry','John','Mark','Ricky', \
'Lisa','Jasica','Hanson','Amy','Bob','Jack']
print('员工编号 薪水 员工编号 薪水 员工编号 薪水 员工编号 薪水')
print('-------------------------------------------------------')
for i in range(3):
for j in range(4):
print('[%2d] [%3d] ' %(data[j*3+i][0],data[j*3+i][1]),end='')
print()
head=employee() # 建立链表头
if head==None:
print('Error!! 内存分配失败!!')
sys.exit(0)
else:
head.num=data[0][0]
head.name=namedata[0]
head.salary=data[0][1]
llinknode=head
for i in range(1,12): # 建立链表
newnode=employee()
newnode.num=data[i][0]
newnode.name=namedata[i]
newnode.salary=data[i][1]
llinknode.rlink=newnode
newnode.llink=llinknode
llinknode=newnode
while True:
print('请输入要插入其后的员工编号,如输入的编号不在此链表中,')
position=int(input('新输入的员工节点将视为此链表的链表头,要结束插入过程,请输入-1:'))
if position==-1: # 循环中断条件
break
else:
ptr=findnode(head,position)
new_num=int(input('请输入新插入的员工编号:'))
new_salary=int(input('请输入新插入的员工薪水:'))
new_name=input('请输入新插入的员工姓名:')
head=insertnode(head,ptr,new_num,new_salary,new_name)
print('\t员工编号 姓名\t薪水')
print('\t==============================')
ptr=head
while ptr!=None:
print('\t[%2d]\t[ %-10s]\t[%3d]' %(ptr.num,ptr.name,ptr.salary))
ptr=ptr.rlink
# 结果
员工编号 薪水 员工编号 薪水 员工编号 薪水 员工编号 薪水
-------------------------------------------------------
[1001] [32367] [1007] [31299] [1018] [44145] [1037] [21100]
[1002] [24388] [1012] [42660] [1043] [52182] [1041] [32196]
[1003] [27556] [1014] [25676] [1031] [32769] [1046] [25776]
请输入要插入其后的员工编号,如输入的编号不在此链表中,
新输入的员工节点将视为此链表的链表头,要结束插入过程,请输入-1:1046
请输入新插入的员工编号:1050
请输入新插入的员工薪水:45000
请输入新插入的员工姓名:marry
请输入要插入其后的员工编号,如输入的编号不在此链表中,
新输入的员工节点将视为此链表的链表头,要结束插入过程,请输入-1:-1
员工编号 姓名 薪水
==============================
[1001] [ Allen ] [32367]
[1002] [ Scott ] [24388]
[1003] [ Marry ] [27556]
[1007] [ John ] [31299]
[1012] [ Mark ] [42660]
[1014] [ Ricky ] [25676]
[1018] [ Lisa ] [44145]
[1043] [ Jasica ] [52182]
[1031] [ Hanson ] [32769]
[1037] [ Amy ] [21100]
[1041] [ Bob ] [32196]
[1046] [ Jack ] [25776]
[1050] [ marry ] [45000]
3.3.3 在双向链表中删除节点
删除同插入类似,同样有三种情况:
1)删除双向链表的第一个节点:将链表指针head指向原链表的第二个节点,再将新链表的左指针指向None.
2)删除双向链表的最后一个节点:将原链表最后一个节点之前的一个节点的右指针指向None.
X.llink.rlink = None
3)s删除双向链表的中间节点X:将X节点的前一个节点的右指针指向X节点的下一个节点,再将X节点的下一个节点的左指针指向X节点的上一个节点。
例子:设计一个Python程序,建立一个员工数据的双向链表,并且允许在链表头部、尾部和链表中间3种不同位置删除借点情况。最后离开时,列出链表所有节点的数据字段的内容。
# CH03-15.py
class employee:
def __init__(self):
self.num=0
self.salary=0
self.name=''
self.llink=None # 左指针字段
self.rlink=None # 右指针字段
def findnode(head,num):
ptr=head
while ptr!=None:
if ptr.num==num:
return ptr
ptr=ptr.rlink
return ptr
def deletenode(head,del_node):
if head==None: # 双向链表是空的
print('[链表是空的]')
return None
if del_node==None:
print('[错误:不是链表中的节点]')
return None
if del_node==head:
head=head.rlink
head.llink=None
else:
if del_node.rlink==None: # 删除链表末尾的节点
del_node.llink.rlink=None
else: # 删除中间节点
del_node.llink.rlink=del_node.rlink
del_node.rlink.llink=del_node.llink
return head
llinknode=None
newnode=None
position=0
data=[[1001,32367],[1002,24388],[1003,27556],[1007,31299], \
[1012,42660],[1014,25676],[1018,44145],[1043,52182], \
[1031,32769],[1037,21100],[1041,32196],[1046,25776]]
namedata=['Allen','Scott','Mary','John','Mark','Ricky', \
'Lisa','Jasica','Hanson','Amy','Bob','Jack']
print('员工编号 薪水 员工编号 薪水 员工编号 薪水 员工编号 薪水')
print('-------------------------------------------------------')
for i in range(3):
for j in range(4):
print('[%2d] [%3d] ' %(data[j*3+i][0],data[j*3+i][1]),end='')
print()
head=employee() # 建立链表头
if head==None:
print('Error!! 内存分配失败!!')
sys.exit(0)
else:
head.num=data[0][0]
head.name=namedata[0]
head.salary=data[0][1]
llinknode=head
for i in range(1,12): # 建立链表
newnode=employee()
newnode.num=data[i][0]
newnode.name=namedata[i]
newnode.salary=data[i][1]
llinknode.rlink=newnode
newnode.llink=llinknode
llinknode=newnode
while True:
position=int(input('\n请输入要删除的员工编号,要删除插入过程,请输入-1:'))
if position==-1: # 循环中断条件
break
else:
ptr=findnode(head,position)
head=deletenode(head,ptr)
print('\t员工编号 姓名\t薪水')
print('\t==============================')
ptr=head
while ptr!=None:
print('\t[%2d]\t[ %-10s]\t[%3d]' %(ptr.num,ptr.name,ptr.salary))
ptr=ptr.rlink
# 结果
员工编号 薪水 员工编号 薪水 员工编号 薪水 员工编号 薪水
-------------------------------------------------------
[1001] [32367] [1007] [31299] [1018] [44145] [1037] [21100]
[1002] [24388] [1012] [42660] [1043] [52182] [1041] [32196]
[1003] [27556] [1014] [25676] [1031] [32769] [1046] [25776]
请输入要删除的员工编号,要删除插入过程,请输入-1:1001
请输入要删除的员工编号,要删除插入过程,请输入-1:-1
员工编号 姓名 薪水
==============================
[1002] [ Scott ] [24388]
[1003] [ Mary ] [27556]
[1007] [ John ] [31299]
[1012] [ Mark ] [42660]
[1014] [ Ricky ] [25676]
[1018] [ Lisa ] [44145]
[1043] [ Jasica ] [52182]
[1031] [ Hanson ] [32769]
[1037] [ Amy ] [21100]
[1041] [ Bob ] [32196]
[1046] [ Jack ] [25776]
到此为止,链表的总结就结束了,总的来说三种链表结构(单向链表、环形链表、双向链表)均有着统一的建立与遍历、查找、删除操作;单向和环形还有链接操作。单向还有独有的反转操作,并且可以更高效的表示多项式;环形具有高效表示稀疏矩阵的特点。
单向链表 | 环形链表 | 双向链表 | |
建立与遍历 | Yes | Yes | Yes |
插入 | Yes | Yes | Yes |
删除 | Yes | Yes | Yes |
链接 | Yes | Yes | No |
反转 | Yes | No | No |
else | 多项式的链表表示 | 稀疏矩阵的链表表示 | No |