数据结构--python第三章 链表之环形链表

在单向链表中,维持链表头指针是相当重要的事情,因为单向链表是由方向的,所以如果链表头指针被破坏或者遗失,整个链表就会遗失,并且会浪费真整个内链表的内存空间。

如果我们把链表的最后一个节点指针指向链表头部,而不是指向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指向同一列中下一个非零项的指针

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值