数据结构与算法_python语言描述_具有基本功能的单链表_带有尾结点引用的单链表_循环单链表_双链表

一、单链表

单链表,实现其基本功能
1.基本功能

  1. 判断该单链表是否为空
  2. 在单链表的表头添加元素
  3. 单链表的弹出操作
  4. 在单链表的表尾添加元素
  5. 删除表中最后的元素
  6. 查询表中是否存在所给定的表元素
  7. 打印链表操作
  8. 迭代器(好处:如果链表中有多个重复的数时,find操作只能返回第一次遇到的num;而迭代器可以不断返回符合条件的num,直至没有)

2.python实现

class LNode:
    '''
    产生结点类
    为形成链表做准备
    '''
    def __init__(self, elem, next_=None):
        self.elem = elem   #这个是用来形成结点的,结点里有什么?元素,和 next指针。因此写的时候考虑这两个属性即可
        self.next = next_
 
class Llist:
    '''
    基于节点类LNode,
    定义单链表对象类
    实现时 需要一个指针变量p,用来标记是否到达链尾
    '''
    def __init__(self):
        self._head = None   #这里LList对象的_head域作为对象的内部表示,不希望外部使用。
                                #python属于这种情况的域按照习惯用单个下划线开头的名字命名
                                
    
    def isEmpty(self):
        '''
        判断该单链表是否为空
        '''
        return self._head is None
    
    def prepend(self, elem):
        '''
        在单链表的表头添加元素 函数
        '''
        self._head = LNode(elem, self._head)
        
    def pop(self):
        '''
        单链表的弹出操作
        删除表头结点 并 返回这个结点里的数据
        '''
        if self._head is None:
            print('该链表已经为空')  #其实这里应该是引发异常
        
        e = self._head.elem
        self._head = self._head.next
        return e
    
    def append(self, elem):
        '''
        后端操作,在链表的最后插入元素。
        必须先找到链表的最后一个结点
        其实现首先是一个扫描循环,找到相应结点后把包含新元素的结点插入在其后
        '''
        if self._head is None:
            self._head = LNode(elem)   #此时表示没有头结点,因此则可以把新的结点直接作为头结点
            return
        
        p = self._head
        while p.next is not None:
            p = p.next  #这里第一次写的是self._head.next  这是不对的,因为p是指针变量,可以一直变,但是写成self._head.next就出不去循环了!!!
        
        p.next = LNode(elem)
    
    def pop_last(self):
        '''
        删除表中最后元素的操作
        两个特殊情况:
        1.如果表空没有可返回的元素时应引发异常
        2.表中只有一个元素的情况需要特殊处理,因为这是应该修改表头指针,并且不存在p.next.next变量
        '''
        
        if self._head is None:
            print('空表')
        
        p = self._head
        if p.next is None: #只有一个头结点
            e = p.elem
            self._head = None
            return e
        
        while p.next.next is not None:
            p = p.next
        
        e = p.next.elem
        p.next = None
        return e
    
    def find(self, pred):
        '''
        找到满足给定条件的表元素
        该方法返回第一个满足谓词(pred)的表元素
        '''
        p = self._head   #这里可以明白为什么要引入一个p变量,其实也就是一个扫描指针,用来遍历链表。随时改变值
        while p is not None:
            if pred(p.elem):
                return pred(p.elem)
            p = p.next
    
    def printAll(self):
        '''
        打印链表操作,随时随地可以查看链表内部情况
        '''
        p = self._head
        while p is not None:
            print(p.elem, end='')
            if p.next is not None:
                print(', ', end='')  #end参数表示以什么结尾,默认为\n,即换行符
            p = p.next
        
        print('')
        
    def filter(self, pred):
        '''
        迭代器,
        注意yield的用法,相当于return
        但是和return又有区别(主要就是有next函数和send函数)
        yield可以暂停,然后返回值,下次继续从暂停的地方开始
        
        迭代器的好处是:
        find函数只能返回遇到的第一个值,但是通过利用迭代器就可以把所有满足条件的全都返回出来!
        
        调用方法:
        for x in list1.filer(pred)
            print(x)
        '''
        p = self._head
        while p is not None:
            if pred(p.elem):
                yield p.elem
            p = p.next
        
#=============================================================================
'''
实例化
'''  
mlist1 = Llist()
for i in range(5):
    mlist1.prepend(i)
for i in range(11, 20):
    mlist1.append(i)
mlist1.printAll()

3.总结
想要实现一个链表,最起码需要满足:

  1. 每一个节点都要有定义,因此必须先定义节点类
  2. 可以根据头节点找到后面的某个节点,因此对于链表类中需要有head变量
  3. 在链表的操作中,时刻注意头节点为空的边界条件的处理
  4. 如果需要写到p.next.next这种是否为空,就是多次next操作时;需要先判断p.next是否为空;否则按照顺序执行时会报错(因为可能p.next就已经是None了,就不会再有p.next.next了!)
  5. 对于迭代器的用法,简单来看就是return;其中yield中有两个函数,一个是next函数,一个是send函数。
    具体的迭代器用法,见:
    https://blog.csdn.net/mieleizhi0522/article/details/82142856

二、带有尾结点引用的单链表

1.思路
我们可以继承基本功能的单链表,然后再扩充一些定义即可形成新链表类。需要扩充的有:

  1. 链表类除了头节点,还要有尾节点
  2. 因为引入了尾节点,因此表头插入元素、表尾插入元素时还需要考虑尾节点引用域的设置
  3. 弹出表尾元素时,需要更新尾节点

2.python实现

'''
Test2:带有尾结点引用的单链表的实现
通过继承和扩充定义新链表类
'''        
class LList1(Llist):
    '''
    继承于上面所写的最基本的类
    '''
    def __init__(self):
        Llist.__init__(self)
        self._rear = None  #由于作为内部数据域,用_rear作为域名,将它初始化为None
    
    def prepend(self, elem):
        '''
        重写前端插入操作
        操作方式于Llist里的一样,但现在还要考虑尾结点引用域的设置
        '''
        if self._head is None:
            self._head = LNode(elem)
            self._rear = self._head
        
        else:
            self._head = LNode(elem, self._head)
            #本质是这样,但是不能这么写 LNode(elem).next = self._head.next 
            #self._head = LNode(elem)
    
    def append(self, elem):
        '''
        增加了尾结点引用,可以直接找到尾结点,在其后增加结点的操作也能更快的完成
        注意:在链表操作定义中,通常都需要区分被修改的是头变量(域)的情况还是一般情况
        '''
        if self._head is None:
            self._head = LNode(elem)
            self._rear = self._head
        
        else:            
            self._rear.next = LNode(elem)
            self._rear = self._rear.next
            
    def pop_last(self):
        '''
        最后是弹出末元素的操作
        现在删除了尾结点之后还需要更新_rear
        '''
        if self._head is None:
            print('空表')
        
        p = self._head
        if p.next is None:
            e = p.elem          #因为想完成一个返回值的功能,因此需要引入临时变量先储存下来
            self._head = None
            return e
        
        while p.next.next is not None:            
            p = p.next
        
        e = p.next
        p.next = None
        self._rear = p
        return e

import random

mlist1 = LList1()
mlist1.prepend(99)
for i in range(11, 20):
    mlist1.append(random.randint(1, 20))
print('======================================================')
for x in mlist1.filter(lambda y:y % 2 == 0):
    print(x)   #输出表mlist1里的所有偶数,其中用一个lambda表达式描述筛选条件

3、总结

  1. 继承时对于子类重写父类的构造函数时,需要先把父类的构造函数引入一下。经典类的写法: 父类名称.init(self,参数1,参数2,…)。具体的见:https://www.cnblogs.com/bigberg/p/7182741.html;
    https://blog.csdn.net/feishicheng/article/details/79596000;
    https://www.cnblogs.com/homle/p/8724125.html;
    https://blog.csdn.net/yilulvxing/article/details/85374142
  2. python中下划线的5种含义,见:https://zhuanlan.zhihu.com/p/36173202

三、循环单链表(循环链表)

1、思想
循环链表就是尾节点还连接到了头节点。这样,对于循环链表,其实就不用那么细分头节点和尾节点;
对于一些操作的更正:

  1. 前端添加元素的时候,需要细分出来如果只有一个节点的时候,也要让它自己连到自己
  2. 尾端添加元素的时候,可以直接调用前端插入函数。因为尾端插入元素其实也就是在头和尾之间插入一个,也就是在头节点插入一个,只不过将新加进来的节点赋值为尾节点即可。

2.python实现

'''
问题三:循环链表
'''

class LNode:
    '''
    产生结点类
    为形成链表做准备
    '''
    def __init__(self, elem, next_=None):
        self.elem = elem   #这个是用来形成结点的,结点里有什么?元素,和 next指针。因此写的时候考虑这两个属性即可
        self.next = next_
               
class LCList:
    '''
    循环单链表类
    '''
    def __init__(self):
        self._rear = None
        
    def isEmpty(self):
        return self._rear is None
    
    def prepend(self, elem):
        '''
        前端添加元素函数
        '''
        p = LNode(elem)
        if self._rear is None:
            p.next = p    #self._rear = LNode(elem) 这么写是错的
            self._rear = p  #这个很重要!!就是一个结点的时候也要建立一个结点的环,因此需要自己指向自己
                
        p.next = self._rear.next
        self._rear.next = p
        
    def append(self, elem):
        '''
        末端添加元素函数
        '''
        self.prepend(elem)   #这里直接调用了前端插入函数,因为无论是前端还是后端插入,都是在表头和表尾之间插元素
        self._rear = self._rear.next
        
    def pop(self):
        '''
        前端弹出函数
        '''
        if self._rear is None:
            print('空表')
            
        p = self._rear.next
        e = p.elem
        
        if self._rear is p:  #这里忘了考虑只有一个结点,即自己循环自己的时候!!!这里需要作为边界条件考虑
            self._rear = None
        else:
            self._rear.next = p.next
        
        return e
    
    def pop_last(self):
        '''
        删除表中最后元素的操作
        两个特殊情况:
        1.如果表空没有可返回的元素时应引发异常
        2.表中只有一个元素的情况需要特殊处理,因为这是应该修改表头指针,并且不存在p.next.next变量
        '''
        
        if self._rear is None:
            print('空表')
        
        p = self._rear
        if p.next == p: #只有一个尾结点
            e = p.elem
            self._rear = None
            return e
        
        tmp = self._rear.next   #多个结点时,遍历寻找到倒数第二个结点
        while tmp.next != self._rear:
            tmp = tmp.next
 
        e = tmp.next.elem  #取出尾结点的元素,用来返回
        tmp.next = self._rear.next  #将倒数第二个循环至“头结点”
        self._rear = tmp   #重新定义尾结点,将之前的尾结点释放
        return e
    
    def printall(self):
        '''
        打印表元素函数
        '''
        if self._rear is None:
            return
        
        p = self._rear.next  #表头
        while p.next != self._rear.next:   #这里不能再用none判断了!!!因为这时候是个循环列表即一个环,没有none变量!!!!
            print(p.elem)
            p = p.next
            
#        while True:
#            print(p.elem)
#            if p is self._rear:
#                break
#            p = p.next        标准答案
    
    def filter(self, pred):
        '''
        迭代器,
        注意yield的用法,相当于return
        但是和return又有区别(主要就是有next函数和send函数)
        yield可以暂停,然后返回值,下次继续从暂停的地方开始
        
        迭代器的好处是:
        find函数只能返回遇到的第一个值,但是通过利用迭代器就可以把所有满足条件的全都返回出来!
        
        调用方法:
        for x in list1.filer(pred)
            print(x)
        '''
        p = self._rear.next  #表头
        
        while p.next != self._rear.next:
            if pred(p.elem):
                yield p.elem
            p = p.next
            
import random
mlist1 = LCList()
mlist1.prepend(99)
for i in range(11, 20):
    mlist1.append(random.randint(1, 20))
    
mlist1.printall()   #看一下原始输出的东西
print('======================================================')
mlist1.pop_last()
print('======================================================')
mlist1.printall()  #删除尾结点以后的输出,用来检查是否删除成功

print('======================================================')
for x in mlist1.filter(lambda y:y % 2 == 0):
    print(x)   #输出表mlist1里的所有偶数,其中用一个lambda表达式描述筛选条件

四、双向链表(双链表)

1、思想
双链表改变的是节点结构,因此节点结构需要继承一下,然后加入向前指向指针;
无论是插入元素还是弹出元素,我们不仅需要改变next引用域还需要改变prev引用域。

2、python实现

'''
练习四:双向链接表(双链表)
'''

class DLNode(LNode):
    '''
    双链表结点类
    '''
    def __init__(self, elem, prev=None, next_=None):
        LNode.__init__(self, elem, next_)
        self.prev = prev #prev给了一个默认值,正常不赋值就是None,如果赋值则把None覆盖
    
class DLList(LList1): #双链表类,继承于 带首尾节点引用的单链表类LList1
    '''
    其中,空表判断和find、filter、printall方法都可以继承,他们执行中只是用了next方向的引用
    
    '''
    def __init__(self):
        LList1.__init__(self)
        
    def prepend(self, elem):
        '''
        前端插入
        '''
        p = DLNode(elem, None, self._head)   #prev为none,也就是这块还没有prev域
        #该操作生成一个next域指向当前头结点的结点,因为一会要将p插入作为新的头结点
        if self._head is None:
            '''
            空表
            '''
            self._rear = p  #首尾都是自己
        else:  #非空表,设置prev引用
            p.next.prev = p
        self._rear = p
    
    def append(self, elem):
        '''
        表尾插入
        '''
        p = DLNode(elem, self._rear, None)   #该操作生成一个prev域指向当前尾结点的结点,因为一会要将p插入作为新的尾结点
        if self._head is None:
            self._head = p  #空表
        else:
            p.prev.next = p
        self._rear = p
        
    def pop(self):
        '''
        弹出表头结点
        '''
        if self._head is None:
            print('空表')
        
        e = self._head.elem
        self._head = self._head.next
        if self._head is not  None:
            self._head.prev = None
        return e    #答案
        
#        e = self._head.elem
#        self._head.next.prev = None
#        self._head = self._head.next
#        return e   #自己写的
    
    def pop_last(self):
        '''
        弹出表尾元素
        '''
        if self._head is None:
            print('空表')
        
        e = self._rear.elem
        self._rear = self._rear.prev
        if self._rear is None:
            self._head = None #设置_head保证is_empty正确工作
        else:
            self._rear.next = None   #这里直接把新的表尾结点的next设置为None,则可以理解为这时候已经把新的表尾和旧表尾的连接断开了
        return e
    
    
    def reverseDoublelist(self):
        '''
        双链表的反转函数
        '''
        if self._head == None:
            print('空表')
            return

        left = self._head.prev
        right = self._head
        
        while right is not None:
            #一个节点只有前向指针和后向指针两个,因此这四步操作就是搞定这两个指针
            left = right.prev  #确保left永远在right的左边
            right.prev = right.next  #先搞定前向指针,因为要是先做后向指针后面的链就断了
            right.next = left   #搞定后向指针
            right = right.prev  #更新right,使得一直在往后走
                                    #这一步是最关键的,刚开始一直用的是right.next,结果每次
                                    #只能运行一次,因为right.next在上一行被改变变成了None了
            '''
            总结:先处理前向指针,然后处理后向指针
            1.缓存前向指针
            2.将后向指针赋值给前向指针
            3.将缓存的前者指针,赋值给后向指针
            4.当前结点指针移动到下一个待处理结点
            '''
        if left != None:      #修改头指针之前,先检测链表是否为空链表,或者只有一个结点的情况
            self._head = left.prev

import random
mlist1 = DLList()
#mlist1.prepend(99)
for i in range(11, 20):
    mlist1.append(random.randint(1, 20))

mlist1.printAll()   #看一下原始输出的东西
print('======================================================')
#mlist1.pop_last()
#mlist1.printAll()  #删除尾结点以后的输出,用来检查是否删除成功
#print(mlist1._head.next)
mlist1.reverseDoublelist()
mlist1.printAll()
#print('======================================================')
#for x in mlist1.filter(lambda y:y % 2 == 0):
#    print(x)   #输出表mlist1里的所有偶数,其中用一个lambda表达式描述筛选条件   

3、总结
这里面包含了双链表的反转内容,这里先不谈如何实现双链表的反转(双链表的反转在后面几期博客内容中会单独提出来)。

  1. 可以把反转写在双链表类中,这样的好处就是不用往里面传参,实例化的时候直接添加好节点得到链表以后直接调用链表的反转方法就行。
  2. 可以把反转单独写在外面作为一个函数,这样参数就要传的是头节点。因为只有拿到头节点,才可以在内存中找到链表。具体的传入自定义类类型的方法:node = Node。详见:
    https://www.cnblogs.com/Justsoso-WYH/p/7788270.html
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值