前面已经总结过了链表中的单向链表、环形链表,接下来我们继续谈谈链表中的另一种之双向链表。
单向链表和环形链表都属于拥有方向性的链表,只能单向遍历,万一不幸其中有一个链表断裂,那么后面的链表数据便会遗失而无法复原。因此我们可以将两个不同方向的链表结合起来,除了存放数据的字段,他还有两个指针变量,其中一个指针指向后面的节点,另一个指针则指向前面的节点,这样的链表被称为双向链表(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 |