链表(linked List)是由许多相同数据类型的数据项按特定顺序排列而成的线性表。链表的特性是其各个数据项在计算机内存中的位置是不连续且随机的,“动态数据结构,动态分配空间”。
优点:数据的插入和删除都相当方便,有新的数据加入就向系统申请一块内存空间,而数据被删除之后,就可以把这块内存空间还给系统,加入和删除都不需要移动大量的数据(但是静态数据线性结构的数组的插入和删除需要移动大量的数据)。
缺点:设计数据结构时较为麻烦,另外在查找数据时,也无法像静态数据(如数组)那样可以可以随机访问数据,链表的随机访问必须按顺序查找直到该数据为止。
本章的结构如下:
单向链表:建立、遍历、插入、删除、反转、链接、多项式的链表表示法
环形链表:建立、遍历、插入、删除、链接、环形链表与稀疏矩阵表示法
双向链表:建立、向左向右遍历、插入、删除、 ---- 环形双向链表1
3.1 单向链表
在动态分配空间时,最常用的就是“单向链表(Single Linked List)".一个单向链表节点基本上由两个元素(数据字段和指针)所组成的,而指针将会指向下一个元素在内存中的地址。第一个节点四号”链表头指针“,最后一个节点的指针设为”None“,表示链尾,不指向任何地方。
在单向链表中,所有节点都知道节点本身的下一个节点在哪里,但是对于前一个节点缺无法得知,因此链表头很重要,只要存在链表头指针,就可以遍历整个链表,进行加入和删除节点等操作。
3.3.1 建立单向链表
在python中,如果以动态分配产生链表节点的节点,可以先行定义一个类,接着在该类中定义一个指针字段(next)和数据字段。首先指针字段的作用是指向下一个链表节点,如在声明学生成绩链表结构时,除了next字段,还有包含姓名(name)和成绩(score)两个属性的数据字段。
class student:
def __init__(self,):
self.name = ""
self.score = 0
self.next = None
完成节点类的声明后,就可以动态建立链表中的每个节点了。假设现在要新增一个节点到链表的末尾,且ptr指向链表的第一个节点,在程序上必须设计4个步骤:
1.动态分配内存空间给新节点使用
2.将原链表尾部的指针(next)指向新元素所在的内存位置
3.将ptr指针指向新节点的内存位置,表示这是新的链表尾部
4.由于新节点当前为链表的最后一个元素,因此将它的指针(next)指向None。
3.1.2 遍历单向链表
例子:设计一个python程序,可以让用户输入数据来新增学生数据节点,并建立一个单向链表。当用户输入结束后,可遍历此链表并显示内容,并求出当前链表中所有学生的数学和英语的平均成绩。
# CH03-01.py
import sys
# 建立节点类
class student:
def __int__(self,):
self.name = ""
self.Math = 0
self.Eng = 0
self.no = ''
self.next = None
head =student() # 建立链表头部
head.next = None # 当前的下一个元素
ptr = head # 设置存取指针的位置
Msum = Esum = num = student_no = 0
select = 0
while select != 2:
print("1:新增 2:离开=>")
try:
select = int(input("请输入一个选项:"))
except ValueError:
print("输入错误")
print("请重新输入\n")
if select == 1:
new_data = student() # 新增下一元素
new_data.name = input("姓名:")
new_data.no = input("学号:")
new_data.Math = eval(input("数学成绩:"))
new_data.Eng = eval(input("英语成绩"))
ptr.next = new_data
new_data.next = None
ptr = ptr.next
num = num+1
#以上是建立链表的固定格式,下面是遍历,首先设置从头部开始,依次遍历打印
ptr = head.next # 设置存取指针从链表头部开始
while ptr != None:
print('姓名:%s\t 学号:%s\t 数学成绩:%d\t 英语成绩:%d'\
%(ptr.name, ptr.no, ptr.Math, ptr.Eng))
Msum=Msum + ptr.Math
Esum = Esum + ptr.Eng
student_no = student_no + 1
ptr = ptr.next # 将ptr往下移动一位元素。
if student_no != 0:
print("-----------------------------")
print("本链表中学生的数学平均成绩:%.2f,英语的平均成绩:%.2f"%(Msum/student_no,Esum/student_no))
# 结果:
1:新增 2:离开=>
请输入一个选项:1
姓名:andy
学号:1
数学成绩:98
英语成绩97
1:新增 2:离开=>
请输入一个选项:1
姓名:may
学号:2
数学成绩:95
英语成绩96
1:新增 2:离开=>
请输入一个选项:2
姓名:andy 学号:1 数学成绩:98 英语成绩:97
姓名:may 学号:2 数学成绩:95 英语成绩:96
-----------------------------
本链表中学生的数学平均成绩:96.50,英语的平均成绩:96.50
3.1.3 在单向链表中插入新节点
在单向链表中插入新节点,如同在一列火车中加入新的一节车厢。
1)加到第一个节点前:称为此链表的首节点,只需要把新节点的指针指向链表原来的第一个节点,再把链表头指针指向新节点即可。
newnode.next = first
first = newnode
2)插入到最后一个节点之后,只需要把最后一个节点的指针指向新节点,然后新节点的指针指向None
ptr.next = newnode
newnode.next = None
3)将新节点插入中间位置:例如插入节点在X于Y之间,只要将X节点的指针指向新节点,新节点的指针指向Y节点即可。
newnode.next = X.next
x.next = newnode
例子:设计一个python程序,建立一个员工数据的单向链表,并且允许在链表头部、链表末尾、链表中间3种不同的位置插入新节点。最后离开时,列出链表的最后所有节点的数据字段的内容。
import sys
# 建立节点类
class employee:
def __int__(self,):
self.num = 0
self.salary = 0
self.name = ''
self.next = None
# 查找要插入的节点位置并插入
def find(head, num):
ptr = head
while ptr != None:
if ptr.num == num:
return ptr
ptr = ptr.next
return ptr
def insertnode(head, ptr, num, salary, name):
InsertNode = employee()
if not InsertNode:
return None
InsertNode.num = num
InsertNode.salary = salary
InsertNode.name = name
InsertNode.next = None
if ptr == None: # 插入第一个节点
InsertNode.next = head
return InsertNode
else:
if ptr.next == None: # 插入最后一个节点
ptr.next = InsertNode
else: # 插入中间位置
InsertNode.next = ptr.next
ptr.next = InsertNode
return head
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('[%4d] $%5d'%(data[j*3+i][0],data[j*3+i][0]),end='')
print()
print('----------------------------------------------')
head = employee()
head.next = None
if not head:
print("Error!!内存分配失败!!\n")
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.next = None
newnode.num = data[i][0]
newnode.name = namedata[i]
newnode.salary = data[i][1]
newnode.next = None
ptr.next = newnode
ptr = ptr.next
while(True):
print("请输入要插入其后的员工编号,如输入的编号不在此链表中:")
position = int(input('新输入的员工节点将视为此链表的链表头部,要结束过程,请输入-1:'))
if position == -1:
break
else:
ptr = find(head, position)
new_num = int(input('请输入新插入的员工编号:'))
new_salary = int(input('请输入新插入员工的员工薪水:'))
new_name = input('请输入新插入员工的姓名:')
head = insertnode(head, ptr, new_num, new_salary, new_name)
print()
ptr = head
print('\t 员工编号 姓名\t薪水')
print('\t--------------------------------------')
while ptr != None:
print('\t[%2d]\t[%-7s]\t[%3d]'%(ptr.num, ptr.name, ptr.salary))
ptr = ptr.next
# 结果
员工编号 薪水 员工编号 薪水 员工编号 薪水 员工编号 薪水
---------------------------------------------
[1001] $ 1001[1007] $ 1007[1018] $ 1018[1037] $ 1037
[1002] $ 1002[1012] $ 1012[1043] $ 1043[1041] $ 1041
[1003] $ 1003[1014] $ 1014[1031] $ 1031[1046] $ 1046
----------------------------------------------
请输入要插入其后的员工编号,如输入的编号不在此链表中:
新输入的员工节点将视为此链表的链表头部,要结束过程,请输入-1:1041
请输入新插入的员工编号:1088
请输入新插入员工的员工薪水:6800
请输入新插入员工的姓名:jane
请输入要插入其后的员工编号,如输入的编号不在此链表中:
新输入的员工节点将视为此链表的链表头部,要结束过程,请输入-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]
[1088] [jane ] [6800]
[1046] [Jack ] [25776]
3.1.4 在单向链表中删除节点
在单向链表类型的数据结构中,要在链表中删除一个节点,如同在一列火车中拿掉原有的一节车厢,根据位置不同不同的删法。
1)删除链表的第一节点:
top = head
head= head.next
2)删除链表的最后一个节点:
ptr.next = tail
ptr.next= = None
3)删除链表内的中间节点(Y):
Y = ptr.next
ptr.next = Y.next
例子:设计一个python程序,在员工数据的链表中删除节点,并且允许所删除的节点有在链表头部,链表尾部、链表中间3种不同的位置情况。最后在离开时,列出此链表的最后节点的数据字段的内容。
## CH03-03.py
import sys
class employee:
def __innt__(self):
self.num = 0
self.salary = 0
self.name = ''
self.next = None
def del_ptr(head,ptr):
top =head
if ptr.num == head.num: # 要删除的节点在链表头部
head= head.num
print('已删除第%d号员工 姓名:%s 薪资:%d'%(ptr.num, ptr.name, ptr.salary))
else:
while top.next != ptr: # 找到删除节点第前一个位置
top = top.next
if ptr.next == None: # 删除在链表末尾的节点
top.next = None
print('已删除第%d号员工 姓名:%s 薪资:%d' % (ptr.num, ptr.name, ptr.salary))
else:
top.next = ptr.next # 要删除的节点在列表中的任一节点
print('已删除第%d号员工 姓名:%s 薪资:%d' % (ptr.num, ptr.name, ptr.salary))
return head #返回链表
def main():
# findword = 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.num = data[i][0]
newnode.next = None
ptr.next = newnode
ptr = ptr.next
while(True):
findword = int(input('请输入要删除的员工编号,要结束删除过程,请输入-1:'))
if (findword == -1):
break
else:
ptr = head
find =0
while ptr != None:
if ptr.num == findword:
ptr = del_ptr(head, ptr)
find = find +1
head = ptr
ptr = ptr.next
if find == 0:
print('####没有找到####')
ptr = head
print('\t 员工编号 姓名\t薪水')
print('\t ===========================')
while (ptr != None):
print('\t[%2d] \t[%-10s] \t[%3dd]'%(ptr.num, ptr.name, ptr.salary))
ptr = ptr.next
main()
## 结果:
员工编号 薪水 员工编号 薪水 员工编号 薪水 员工编号 薪水
-------------------------------------------------------
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:1041
已删除第1041号员工 姓名:Bob 薪资:32196
请输入要删除的员工编号,要结束删除过程,请输入-1:-1
员工编号 姓名 薪水
===========================
[1001] [Allen ] [32367d]
[1002] [Scott ] [24388d]
[1003] [Marry ] [27556d]
[1007] [John ] [31299d]
[1012] [Mark ] [42660d]
[1014] [Ricky ] [25676d]
[1018] [Lisa ] [44145d]
[1043] [Jasica ] [52182d]
[1031] [Hanson ] [32769d]
[1037] [Amy ] [21100d]
[1046] [Jack ] [25776d]
3.1.5 单向链表的反转(can't fully understand)
例子:设计一个python程序,延续范例3.1.3 将员工数据的链表节点按照员工号反转打印
# ch03-04.py
import sys
class employee:
def __init__(self):
self.num=0
self.salary=0
self.name=''
self.next=None
findword=0
namedata=['Allen','Scott','Marry','Jon', \
'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]]
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=ptr.next
ptr=head
i=0
print('反转前的员工节点数据:')
while ptr != None:
print('[%2d %6s %3d] => '%(ptr.num, ptr.name, ptr.salary),end ='')
i = i+1
if i >= 3:
print()
i = 0
ptr = ptr.next
ptr = head
before = None
print('\n 反转后的链表节点数据')
while ptr != None:
last = before
before = ptr
ptr = ptr.next
before.next = last
ptr = before
while ptr != None:
print('[%2d %6s %3d] => '%(ptr.num, ptr.name, ptr.salary),end='')
i = i+1
if i >= 3:
print()
i =0
ptr = ptr.next
## 结果
反转前的员工节点数据:
[1001 Allen 32367] => [1002 Scott 24388] => [1003 Marry 27556] =>
[1007 Jon 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] =>
反转后的链表节点数据
[1046 Jack 25776] => [1041 Bob 32196] => [1037 Amy 21100] =>
[1031 Hanson 32769] => [1043 Jasica 52182] => [1018 Lisa 44145] =>
[1014 Ricky 25676] => [1012 Mark 42660] => [1007 Jon 31299] =>
[1003 Marry 27556] => [1002 Scott 24388] => [1001 Allen 32367] =>
3.1.6 单向链表的链接功能
对于两个或两个以上的链表的链接(级联),只需要将链表的首尾相连即可。
例子:设计一个python程序,将两组学生成绩的链表连接起来,并输出新的学生成绩链表
# ch03-05.py
# 单向链表的连接功能
import sys
import random
# 单向链表的链接功能
def concatlist(ptr1,ptr2):
ptr = ptr1
while ptr.next != None:
ptr = ptr.next
ptr.next = ptr2
return ptr1
class employee:
def __init__(self):
self.num=0
self.salary=0
self.name=''
self.next=None
findword = 0
data = [[None]*2 for row in range(12)]
namedata1=['Allen','Scott','Marry','Jon', \
'Mark','Ricky','Lisa','Jasica', \
'Hanson','Amy','Bob','Jack']
namedata2=['May','John','Michael','Andy', \
'Tom','Jane','Yoko','Axel', \
'Alex','Judy','Kelly','Lucy']
for i in range(12):
data[i][0] = i+1
data[i][1] = random.randint(51,100)
head1=employee() #建立第一组链表的头部
if not head1:
print('Error!! 内存分配失败!!')
sys.exit(0)
head1.num=data[0][0]
head1.name=namedata1[0]
head1.salary=data[0][1]
head1.next=None
ptr=head1
for i in range(1,12): #建立第一组链表
newnode=employee()
newnode.num=data[i][0]
newnode.name=namedata1[i]
newnode.salary=data[i][1]
newnode.next=None
ptr.next=newnode
ptr=ptr.next
for i in range(12):
data[i][0] = i + 13
data[i][1] = random.randint(51, 100)
head2 = employee() # 建立第二组链表的头部
if not head2:
print('Error!! 内存分配失败!!')
sys.exit(0)
head2.num = data[0][0]
head2.name = namedata2[0]
head2.salary = data[0][1]
head2.next = None
ptr = head2
for i in range(1, 12): # 建立第二组链表
newnode = employee()
newnode.num = data[i][0]
newnode.name = namedata2[i]
newnode.salary = data[i][1]
newnode.next = None
ptr.next = newnode
ptr = ptr.next
i=0
ptr=concatlist(head1,head2) #将链表相连
print('两个链表相连的结果为:')
while ptr!=None: #打印链表的数据
print('[%2d %6s %3d] => ' %(ptr.num,ptr.name,ptr.salary),end='')
i=i+1
if i>=3:
print()
i=0
ptr=ptr.next
#结果
两个链表相连的结果为:
[ 1 Allen 57] => [ 2 Scott 83] => [ 3 Marry 69] =>
[ 4 Jon 61] => [ 5 Mark 56] => [ 6 Ricky 77] =>
[ 7 Lisa 68] => [ 8 Jasica 66] => [ 9 Hanson 55] =>
[10 Amy 89] => [11 Bob 62] => [12 Jack 68] =>
[13 May 66] => [14 John 96] => [15 Michael 93] =>
[16 Andy 90] => [17 Tom 91] => [18 Jane 53] =>
[19 Yoko 100] => [20 Axel 90] => [21 Alex 69] =>
[22 Judy 65] => [23 Kelly 66] => [24 Lucy 75] =>
例子:设计一个Python程序,建立这5个学生成绩的单向链表,然后建立遍历每一个节点并打印学生的姓名与成绩。
import sys
class student:
def __init__(self):
self.num =0
self.name = ''
self.score = 0
self.next = None
print("请输入5项学生数据:")
node = student()
if not node:
print("[Error !!内存分配失败]")
sys.exit(0)
node.num = eval(input('请输入学号:'))
node.name = input('请输入姓名:')
node.score = eval(input('请输入成绩:'))
ptr = node # 保留链表头部,以ptr为当前节点的指针
for i in range(1, 5):
newnode = student()
if not newnode:
print('[error !! 内存分配失败]')
sys.exit(0)
newnode.num = int(input('请输入学号:'))
newnode.name = input('请输入姓名:')
newnode.score = int(input('请输入成绩:'))
newnode.next = None
ptr.next = newnode
ptr = ptr.next
print('学 生 成 绩')
print('学号\t 姓名\t 成绩\n ==============')
ptr = node
while ptr != None:
print('%3d \t %-s \t%3d'%(ptr.num, ptr.name, ptr.score))
node = ptr
ptr = ptr.next # ptr按序往后遍历链表
# 结果:
请输入5项学生数据:
请输入学号:1
请输入姓名:a
请输入成绩:100
请输入学号:2
请输入姓名:b
请输入成绩:99
请输入学号:3
请输入姓名:c
请输入成绩:98
请输入学号:4
请输入姓名:d
请输入成绩:100
请输入学号:5
请输入姓名:e
请输入成绩:100
学 生 成 绩
学号 姓名 成绩
==============
1 a 100
2 b 99
3 c 98
4 d 100
5 e 100
3.1.7 多项式链表表示法
在第二章数组部分,我们已经介绍过了多项式的两种数组表示方法,但是用数组表示有以下问题:
1)多项式内容变动时,对数组结构的影响相当大,算法处理不易
2)由于数组是静态数据结构,因此事先必须要寻找一块连续且足够大的内存空间,这样容易造成内存空间的浪费。
使用链表来表示多项式时,每一个节点具体如下:
COEF | EXP | LINK |
COEF:表示该变量的系数
EXP:表示该变量的指数
LINK:表示指向下一个节点的指针
例子:设计一个python程序,求出两个多项式A(X)+B(X)的最后结果。
import sys
class LinkedList: #声明链表结构
def __init__(self):
self.coef=0
self.exp=0
self.next=None
def create_link(data): #建立多项式子程序
for i in range(4):
newnode=LinkedList()
if not newnode:
print("Error!! 内存分配失败!!")
sys.exit(0)
if i==0: # 相当于链表中的链表头的设置
newnode.coef=data[i]
newnode.exp=3-i
newnode.next=None
head=newnode
ptr=head
elif data[i]!=0:
newnode.coef=data[i]
newnode.exp=3-i
newnode.next=None
ptr.next=newnode
ptr=newnode
return head
def print_link(head): #打印多项式子程序
while head !=None:
if head.exp==1 and head.coef!=0: #X^1时不显示指数
print("%dX + " %(head.coef), end='')
elif head.exp!=0 and head.coef!=0:
print("%dX^%d + " %(head.coef,head.exp), end='')
elif head.coef!=0: #X^0时不显示变量
print("%d" %(head.coef))
head=head.next
print()
def sum_link(a,b): #多项式相加子程序
i=0
ptr=b
plus=[None]*4
while a!=None: #判断多项式1
if a.exp==b.exp: #指数相等,系数相加
plus[i]=a.coef+b.coef
a=a.next
b=b.next
i=i+1
elif b.exp>a.exp: #B指数较大,把系数赋值给C
plus[i]=b.coef
b=b.next
i=i+1
elif a.exp>b.exp: #A指数较大,把系数赋值给C
plus[i]=a.coef
a=a.next
i=i+1
return create_link(plus) #建立相加结果链表C
def main():
data1=[3,0,4,2] #多项式A的系数
data2=[6,8,6,9] #多项式B的系数
#c=LinkedList()
print("原始多项式:\nA=",end='')
a=create_link(data1) #建立多项式A
b=create_link(data2) #建立多项式B
print_link(a) #打印多项式A
print("B=",end='')
print_link(b) #打印多项式B
print("多项式相加的结果:\nC=",end='') #C为A、B多项式相加的结果
print_link(sum_link(a,b)) #打印多项式C
main()
# 结果:
原始多项式:
A=3X^3 + 4X + 2
B=6X^3 + 8X^2 + 6X + 9
多项式相加的结果:
C=9X^3 + 8X^2 + 10X + 11