在单向链表中,维持链表头指针是相当重要的事情,因为单向链表是由方向的,所以如果链表头指针被破坏或者遗失,整个链表就会遗失,并且会浪费真整个内链表的内存空间。
如果我们把链表的最后一个节点指针指向链表头部,而不是指向None,那么整个链表就成为了一个单方向的环形结构。
3.2.1 环形链表的建立与遍历
简单来说,环形链表(Circular Linked List)的特点就是在环形链表中的任何一个节点都可以达到此链表内的各个节点,建立的过程与单向链表的建立类似,唯一不同在于必须要将最后一个节点指向第一个节点。
优点:可以从任何一个节点开始遍历所有节点,而且回收整个链表所需要的时间时固定的,与长度无关。‘
缺点:需要多一个连接空间,而且插入一个节点需要改变两个链接。
环形链表的遍历与单向链表很相似,不过检查环形链表结束的条件是:ptr.next == head.
例子:设计一个python程序,可以让用户输入数据来新增学生数据节点,并建立一个环形链表,当用户输入结束时,可遍历此链表并显示相应节点的信息。
class student:
def __init__(self):
self.name=''
self.no=''
self.next=None
head=student() #新增链表头元素
ptr = head #设置存取指针位置
ptr.next = None #目前无一下个元素
select=0
while select!=2:
select=int(input('(1)新增 (2)离开 =>'))
if select ==2:
break
ptr.name=input('姓名 :')
ptr.no=input('学号 :')
new_data=student() #新增下一元素
ptr.next=new_data #连接下一元素
new_data.next = None #下一元素的next先设置为None
ptr = new_data #存取指针设置为新元素所在位置
ptr.next = head #设置存取指针从头开始,此处即是环形链表的关键之处。
print()
ptr=head
while True:
print('姓名:%s\t学号:%s\n' %(ptr.name,ptr.no))
ptr=ptr.next #将head移往下一元素
if ptr.next==head:
break
print('---------------------------------------------------------')
# 结果
(1)新增 (2)离开 =>1
姓名 :marry
学号 :1001
(1)新增 (2)离开 =>1
姓名 :bob
学号 :1002
(1)新增 (2)离开 =>1
姓名 :adam
学号 :1003
(1)新增 (2)离开 =>1
姓名 :aaa
学号 :1004
(1)新增 (2)离开 =>2
姓名:marry 学号:1001
姓名:bob 学号:1002
姓名:adam 学号:1003
姓名:aaa 学号:1004
---------------------------------------------------------
3.2.2 在环形链表中插入新节点
注:查找和删除都需要另建立一个findnode函数,用作寻找相应需要插入和删除位置。
对于环形链表的节点插入,与单向链表的插入方式有点不同,由于每一个节点的指针都是指向下一个节点,因此没有所谓从链表尾部插入的问题。
1)将新节点X插在第一个节点前称为链表头部:需要两方面的变化:链表头部的改变+链表尾部的指针指向新节点。
2)将新节点X插在链表中任意节点I之后:改变两个地方的连接。
例子:设计一个 python程序,建立一个员工数据的环形链表,并且允许在链表头和链表中间插入新节点。最后离开时,列出此链表的最后所有节点信息的数据字段的内容。
import sys
class employee:
def __init__(self):
self.num=0
self.salary=0
self.name=''
self.next=None
def findnode(head, num): # 找到num所对应的的ptr
ptr=head
while ptr.next !=head:
if ptr.num==num:
return ptr
ptr=ptr.next
return ptr
def insertnode(head,after,num,salary,name):
InsertNode=employee()
CurNode=None
InsertNode.num=num
InsertNode.salary=salary
InsertNode.name=name
InsertNode.next=None
if InsertNode==None:
print('内存分配失败')
return None
else:
if head==None: # 链表是空的
head=InsertNode
InsertNode.next=head
return head
else:
if after.next==head: # 新增节点在链表头的位置
#(1) 将新增节点的指针指向链表头
InsertNode.next=head
CurNode=head
while CurNode.next!=head:
CurNode=CurNode.next
# (2)找到链表末尾后,将它的指针指向新增节点
CurNode.next=InsertNode
# (3)将链表头的指针指向新增节点
head=InsertNode
return head
else: # 新增节点在链表头以外的地方
# (1)将新增节点的指针指向after的下一个节点
InsertNode.next=after.next
# (2)将节点after的指针指向新增节点
after.next=InsertNode
return head
position=0
namedata=['Allen','Scott','Marry','John','Mark','Ricky', \
'Lisa','Jasica','Hanson','Amy','Bob','Jack']
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]]
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 not head:
print('Error!! 内存分配失败!!')
sys.exit(0)
head.num=data[0][0]
head.name=namedata[0]
head.salary=data[0][1]
head.next=None
ptr=head
for i in range(1,12): # 建立链表
newnode=employee()
newnode.num=data[i][0]
newnode.name=namedata[i]
newnode.salary=data[i][1]
newnode.next=None
ptr.next=newnode # 将前一个节点指向新建立的节点
ptr=newnode # 新节点成为前一个节点
newnode.next=head # 将最后一个节点指向头节点就成了环形链表
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)
ptr=head # 指向链表的头
print('\t员工编号 姓名\t薪水')
print('\t==============================')
while True:
print('\t[%2d]\t[ %-10s]\t[%3d]' %(ptr.num,ptr.name,ptr.salary))
ptr=ptr.next# 指向下一个节点
if head ==ptr or head==head.next:
break
# 结果
员工编号 薪水 员工编号 薪水 员工编号 薪水 员工编号 薪水
-------------------------------------------------------
[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:1037
请输入新插入的员工编号:1039
请输入新插入的员工薪水:52000
请输入新插入的员工姓名:daniel
请输入要插入其后的员工编号,如果输入的编号不在此链表中,
则新输入的员工节点将视为此链表的第一个节点,要结束插入过程,请输入-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]
[1039] [ daniel ] [52000]
[1041] [ Bob ] [32196]
[1046] [ Jack ] [25776]
3.2.3 在环形链表中删除节点
同插入节点一样,删除也只有头部和任意中间节点,五链表尾部说法。
1)删除链表的第一个节点:首先将表头指针移动到下一节点,间最后一个节点的指针指向新的链表头部,新的链表头部是原链表的第二个头部节点。
2)删除环形链表的中间节点:先找到节点Y的前一个节点previous,将pre的指针指向节点Y的下一个节点。
例子:设计一个python程序,建立一个员工数据的环形链表,并且允许在链表头和链表中间删除节点。最后离开的时候,列出此链表最后所有节点的数据字段和内容。
import sys
class employee:
def __init__(self):
self.num=0
self.salary=0
self.name=''
self.next=None
def findnode(head,num):
ptr=head
while ptr.next!=head:
if ptr.num==num:
return ptr
ptr=ptr.next
ptr=None
return ptr
def deletenode(head,delnode):
CurNode=employee()
PreNode=employee()
TailNode=employee()
CurNode=None
PreNode=None
TailNode=None
if head==None:
print('[环形链表已经空了]')
return None
else:
if delnode==head: # 要删除的节点是链表头
CurNode=head
while CurNode.next!=head:
CurNode=CurNode.next
# 找到最后一个节点并记录下来
TailNode=CurNode
# (1)将链表头移到下一个节点
head=head.next
# (2)将链表最后一个节点的指针指向新的链表头
TailNode.next=head
return head
else: # 要删除的节点不是链表头
CurNode=head
while CurNode.next!=delnode:
CurNode=CurNode.next
# (1)找到要删除节点的前一个节点并记录下来
PreNode=CurNode
# 要删除的节点
CurNode=CurNode.next
# (2)将要删除节点的前一个指针指向要删除节点的下一个节点
PreNode.next=CurNode.next
return head
position=0
namedata=['Allen','Scott','Marry','John', \
'Mark','Ricky','Lisa','Jasica', \
'Hanson','Amy','Bob','Jack']
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]]
print('\n员工编号 薪水 员工编号 薪水 员工编号 薪水 员工编号 薪水')
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 not head:
print('Error!! 链表头建立失败!!')
sys.exit(1)
head.num=data[0][0]
head.name=namedata[0]
head.salary=data[0][1]
head.next=None
ptr=head
for i in range(1,12): # 建立链表
newnode=employee()
newnode.num=data[i][0]
newnode.name=namedata[i]
newnode.salary=data[i][1]
newnode.next=None
ptr.next=newnode # 将前一个节点指向新建立的节点
ptr=newnode # 新节点成为前一个节点
newnode.next=head # 将最后一个节点指向头节点就成了环形链表
while True:
position=int(input('请输入要删除的员工编号,要结束插入过程,请输入-1:'))
if position==-1:
break # 循环中断条件
else:
ptr=findnode(head,position) # 找到对应的员工编号在链表中的位置
if ptr==None:
print('-----------------------')
print('链表中没这个节点....')
break
else:
head=deletenode(head,ptr)
print('已删除第 %d 号员工 姓名:%s 薪资:%d' %(ptr.num,ptr.name,ptr.salary))
ptr=head # 指向链表的头
print('\t员工编号 姓名\t薪水')
print('\t==============================')
while True:
print('\t[%2d]\t[ %-10s]\t[%3d]' %(ptr.num,ptr.name,ptr.salary))
ptr=ptr.next # 指向下一个节点
if head==ptr or head==head.next:
break
# 结果:
员工编号 薪水 员工编号 薪水 员工编号 薪水 员工编号 薪水
-------------------------------------------------------
[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:1018
已删除第 1018 号员工 姓名:Lisa 薪资:44145
请输入要删除的员工编号,要结束插入过程,请输入-1:-1
员工编号 姓名 薪水
==============================
[1001] [ Allen ] [32367]
[1002] [ Scott ] [24388]
[1003] [ Marry ] [27556]
[1007] [ John ] [31299]
[1012] [ Mark ] [42660]
[1014] [ Ricky ] [25676]
[1043] [ Jasica ] [52182]
[1031] [ Hanson ] [32769]
[1037] [ Amy ] [21100]
[1041] [ Bob ] [32196]
[1046] [ Jack ] [25776]
3.2.4 环形链表的链接功能
单向链表的链接功能只需要改变一个指针就可以了,直接一个链表的尾部指针指向另一个链表即可。但是由于环形链表没有链表尾部的说法,则直接改变两个指针就可以把两个环形链表链接在一起。
例子:设计一个python程序,以两位学生学生成绩环形链表为例,把两个环形链表链接起来,显示连接后的新环形链表,同时显示出新环形链表中学生成绩与座号。
# ch03-11.py
import sys
import random
class student: # 声明链表结构
def __init__(self):
self.num=0
self.score=0
self.next=None
def create_link(data,num): # 建立链表子程序
for i in range(num):
newnode=student()
if not newnode:
print('Error!! 内存配置失败!!')
sys.exit(0)
if i==0: # 建立链表头
newnode.num=data[i][0]
newnode.score=data[i][1]
newnode.next=None
head=newnode
ptr=head
else: # 建立链表的其他节点
newnode.num=data[i][0]
newnode.score=data[i][1]
newnode.next=None
ptr.next=newnode
ptr=newnode
newnode.next=head
return ptr # 返回链表
def print_link(head): # 打印链表子程序
i=0
ptr=head.next
while True:
print('[%2d-%3d] => ' %(ptr.num,ptr.score),end='\t')
i=i+1
if i>=3 : # 每行打印三个元素
print()
i=0
ptr=ptr.next
if ptr==head.next:
break
def concat(ptr1,ptr2): # 连接链表的子程序
head=ptr1.next # 在ptr1和ptr2中,各找任意一个节点
ptr1.next=ptr2.next # 把两个节点的next对调即可
ptr2.next=head
return ptr2
data1=[[None] * 2 for row in range(6)]
data2=[[None] * 2 for row in range(6)]
for i in range(1,7):
data1[i-1][0]=i*2-1
data1[i-1][1]=random.randint(41,100)
data2[i-1][0]=i*2
data2[i-1][1]=random.randint(41,100)
ptr1=create_link(data1,6) # 建立链表1
ptr2=create_link(data2,6) # 建立链表2
i=0
print('\n原 始 链 表 数 据:')
print('学号 成绩 \t学号 成绩 \t学号 成绩')
print('==========================================')
print(' 链表 1 :')
print_link(ptr1)
print(' 链表 2 :')
print_link(ptr2)
print('==========================================')
print('连接后的链表:')
ptr=concat(ptr1,ptr2) # 连接两个链表
print_link(ptr)
# 结果
原 始 链 表 数 据:
学号 成绩 学号 成绩 学号 成绩
==========================================
链表 1 :
[ 1- 62] => [ 3- 79] => [ 5- 99] =>
[ 7- 51] => [ 9- 92] => [11- 85] =>
链表 2 :
[ 2- 82] => [ 4- 73] => [ 6- 75] =>
[ 8- 92] => [10- 55] => [12- 76] =>
==========================================
连接后的链表:
[ 1- 62] => [ 3- 79] => [ 5- 99] =>
[ 7- 51] => [ 9- 92] => [11- 85] =>
[ 2- 82] => [ 4- 73] => [ 6- 75] =>
[ 8- 92] => [10- 55] => [12- 76] =>
3.2.5 环形链表与系数矩阵表示法
在数组部分,我们用一个三项式(3-tuple)来表示系数矩阵,虽然节省时间,但是当非零项增删是会造成数组内大量数据的移动,不容易编写代码。
使用环形链表表示系稀疏矩阵的优点:在变更矩阵内的数据时,不需要大量移动数据。主要的技巧是用节点来表示非零项,每个节点除了必须有row、col、value、还有right和down这两个字段
value | 表示此非零项的值 |
row | 以i表示非零项元素所在的行数 |
col | 以j表示非零项元素所在的列数 |
right | 指向同一行中下一个非零项的指针 |
down | 指向同一列中下一个非零项的指针 |