数据结构--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