013 python数据结构与算法:双向链表

链表

单链表,所有节点指针都指向他们的后继节点。

  • 后继节点:当前节点的下一个节点
  • 前驱节点:当前节点的前一个节点

双向链表

一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个链接:一个指向前前一个节点,当此节点为第一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。
在这里插入图片描述
需要注意的是:链表中出现的next仅仅表示“下一个”,和生成器里的next是不一样的。

在这里插入图片描述
在双向链表中,头节点没有前驱,尾节点没有后继,都指向空。

操作具体实现

  • is_empty()链表是否为空
  • length()链表长度
  • travel()遍历链表
  • add(item)链表头部添加
  • append(item)链表尾部添加
  • insert(pos,item)在指定位置添加
  • remove(item)删除节点
  • search(item)查找节点是否存在

首先要先等一节点类:

class Node(object):
    """节点"""
    def __init__(self,item):
        self.elem=item
        self.next=None
        self.prev=None

双向链表的节点中有两个地址区域,一个指向节点的前驱节点,一个指向节点的后继节点。
接着要定义双向链表的类和双向链表的具体操作方法:
判断链表是否为空

class DoubleLinkList(object):
    """双链表"""
    def __init__(self,node=None):
        self.__head=node
    def is_empty(self):
        """判断链表是否为空"""
        return self.__head is None

求链表长度
双向链表求链表长度的操作和单链表是一样的,同样需要一个游标去进行标记、计数。

在这里插入图片描述

    def length(self):
        """求链表长度"""
        cur=self.__head#cur用来移动遍历节点
        count=0#计数
        while cur!=None:
            count+=1
            cur=cur.next
        return count

遍历链表

    def travel(self):
        """遍历整个链表"""
        cur=self.__head
        while cur!=None:
            print(cur.elem,end=" ")
            cur=cur.next
        print(" ")

双向链表的添加元素和删除元素就会用到前驱节点和后继节点,所以对应的操作也和单链表的操作不一样。
因为以上判空、求长度和遍历都和单链表的操作一样,所以我们可以考虑继承和复用。直接继承单链表的操作,就不用再写一遍相应操作的代码,这是面向对象的继承(代码复用)。

class DoubleLinkList(SingleLinkList):
#这样去调用单链表中的一些方法就可以实现代码复用

头插法插入元素
在这里插入图片描述
由图可知,头插法的代码是不唯一的,只要修改链的顺序正确就可以。
可以先改变node的next域指向self.__head,再改变self._head指向node,最后再将100所属节点的前驱节点指向node。就完成了一次头部插入节点。
代码:self.__head.prev=node self.__head=node node.next.prev=node

    def add(self,item):
        """链表头部插入节点"""
        node=Node(item)
        node.next=self.__head
        self.__head=node
        node.next.prev=node

链表尾部插入节点
在这里插入图片描述
和头插法一样,代码是不唯一的,关键是修改链的顺序不能出错。

    def append(self,item):
        """链表尾部插入节点"""
        node=Node(item)
        if self.is_empty():
            self.__head=None
        else:
            cur=self.__head
            while cur.next!=None:
                cur=cur.next
            cur.next=node#先修改尾节点的指向(由none改为指向node)
            node.prev=cur#再修改node的前驱节点域指向cur

链表指定位置插入节点
因为双向链表的每个节点都可以找到它的前一个节点,所以在在双向链表中的执行位置插入不用像单链表一样需要两个游标,只需要一个游标就可以。
在这里插入图片描述
我们可以先让node的next指针指向cur,接着再让node的prev(前驱域)指向cur的prev。最后再将前驱的后继指向node,cur指定节点指向node。

node.next=cur
node.prev=cur.prev
cur.prev.next=node
cur.prev=node

但是如果用另外一种链接方式的话,代码就会出现变化。先修改node的next域指向cur,接着让noede的前驱指向cur的前驱。此时要注意,当先改变cur的前驱指针时,就不能再通过cur的前驱去修改指定位置节点前驱的后继。这时就需要从node的前驱下手去进行指针修改。

node.next=cur
node.prev=cur.prev
cur.prev=node
node.prev,next=node

指定位置添加节点完整代码:

    def insert(self,pos,item):
        """指定位置添加节点"""
        if pos<=0:
            self.add(item)
        elif pos>(self.length()-1):
            self.append(item)
        else:
            cur=self.__head
            count=0
            while count<pos:
                count+=1
                cur=cur.next
            #当循环推出后,cur指向pos位置
            node=Node(item)
            node.next=cur
            node.prev=cur.prev
            cur.prev.next=node
            cur.prev=node

查找
查找的操作和单链表的查找操作是一样的,直接遍历。

    def search(self,item):
        """查找节点是否存在"""
        cur=self.__head#初始化指向头节点
        while cur!=None:
            if cur.elem==item:
                return True
            else:
                cur=cur.next
        return False

删除指定节点
在这里插入图片描述
删除节点只需要先将指定节点的前驱节点的next域指向对应节点的后继节点,再将本来的后继节点的前驱域指向本来的前驱节点,这样就完成了指定节点的删除。
此时还要考虑特殊情况:当要删除的节点是头节点时,需要先判断指定的节点是否为头节点,如果是头节点,就需要让self.head指向cur的next节点,再让其前驱指向none。
在这里插入图片描述

    def remove(self,item):
        """删除节点"""
        cur=self.__head
        while cur!=None:
            if cur.elem==item:
                #先判断此节点是否为头节点
                #是头节点
                if cur==self.__head:
                    self.__head=cur.next
                    cur.next.prev=None
                else:
                    cur.prev.next=cur.next
                    cur.next.prev=cur.prev
                break
            else:
                cur=cur.next

还要考虑原链表只有一个节点的情况:
在这里插入图片描述
只有一个节点,删除后指向none,就没有前驱节点了,所以需要一个判断:判断链表是否只有一个节点。

    def remove(self,item):
        """删除节点"""
        cur=self.__head
        while cur!=None:
            if cur.elem==item:
                #先判断此节点是否为头节点
                #是头节点
                if cur==self.__head:
                    self.__head=cur.next
                    if cur.next:
                        #判断链表是否只有一个节点
                        cur.next.prev=None
                else:
                    cur.prev.next=cur.next
                    cur.next.prev=cur.prev
                break
            else:
                cur=cur.next

if cur.next代表如果cur.next存在,返回True。再执行后继的前驱指向none。如果不存在cur.next,那么none是没有前驱节点的,不操作。
还存在指定节点是最后一个节点的情况:
在这里插入图片描述
如果是最后一个元素,删除后,就不会有前驱节点,不操作,所以这里还需要有一个判断:cur.next是否存在。
在这里插入图片描述

    def remove(self,item):
        """删除节点"""
        cur=self.__head
        while cur!=None:
            if cur.elem==item:
                #先判断此节点是否为头节点
                #是头节点
                if cur==self.__head:
                    self.__head=cur.next
                    if cur.next:
                        #判断链表是否只有一个节点
                        cur.next.prev=None
                else:
                    cur.prev.next=cur.next
                    if cur.next:
                    #判断是否为链表的最后一个节点
                        cur.next.prev=cur.prev
                break
            else:
                cur=cur.next

测试双向链表的操作具体实现:

class Node(object):
    """节点"""
    def __init__(self,item):
        self.elem=item
        self.next=None
        self.prev=None
class DoubleLinkList(object):
    """双链表"""
    def __init__(self,node=None):
        self.__head=node
    def is_empty(self):
        """判断链表是否为空"""
        return self.__head is None
    def length(self):
        """求链表长度"""
        cur=self.__head#cur用来移动遍历节点
        count=0#计数
        while cur!=None:
            count+=1
            cur=cur.next
        return count

    def travel(self):
        """遍历整个链表"""
        cur = self.__head
        while cur != None:
            print(cur.elem, end=" ")
            cur = cur.next
        print(" ")

    def add(self, item):
        """链表头部插入节点"""
        node = Node(item)
        node.next = self.__head
        self.__head = node
        node.next.prev = node

    def append(self, item):
        """链表尾部插入节点"""
        node = Node(item)
        if self.is_empty():
            self.__head = node
        else:
            cur = self.__head
            while cur.next != None:
                cur = cur.next
            cur.next = node  # 先修改尾节点的指向(由none改为指向node)
            node.prev = cur  # 再修改node的前驱节点域指向cur
    def insert(self,pos,item):
        """指定位置添加节点"""
        if pos<=0:
            self.add(item)
        elif pos>(self.length()-1):
            self.append(item)
        else:
            cur=self.__head
            count=0
            while count<pos:
                count+=1
                cur=cur.next
            #当循环推出后,cur指向pos位置
            node=Node(item)
            node.next=cur
            node.prev=cur.prev
            cur.prev.next=node
            cur.prev=node

    def search(self,item):
        """查找节点是否存在"""
        cur=self.__head#初始化指向头节点
        while cur!=None:
            if cur.elem==item:
                return True
            else:
                cur=cur.next
        return False
    def remove(self,item):
        """删除节点"""
        cur=self.__head
        while cur!=None:
            if cur.elem==item:
                #先判断此节点是否为头节点
                #是头节点
                if cur==self.__head:
                    self.__head=cur.next
                    if cur.next:
                        #判断链表是否只有一个节点
                        cur.next.prev=None
                else:
                    cur.prev.next=cur.next
                    if cur.next:
                        cur.next.prev=cur.prev
                break
            else:
                cur=cur.next
if __name__=="__main__":
    dll=DoubleLinkList()
    print(dll.is_empty())
    print(dll.length())

    dll.append(1)#1
    print(dll.is_empty())
    print(dll.length())

    dll.append(2)#12
    dll.add(8)#812
    dll.append(3)#8123
    dll.append(4)
    dll.append(5)
    dll.append(6)#8123456

    dll.insert(-1,9)#98123456
    dll.travel()
    dll.insert(3,100)#981 100 23456
    dll.travel()
    dll.insert(10,200)#981 100 23456 200
    dll.travel()
    dll.remove(100)#981 23456 200
    dll.travel()
    dll.remove(9)#81 23456 200
    dll.travel()
    dll.remove(200)#81 23456
    dll.travel()

运行结果:

True
0
False
1
9 8 1 2 3 4 5 6  
9 8 1 100 2 3 4 5 6  
9 8 1 100 2 3 4 5 6 200  
9 8 1 2 3 4 5 6 200  
8 1 2 3 4 5 6 200  
8 1 2 3 4 5 6  

由此可见,在链表中,由于单链表和双向链表的结构差异,操作方式有些是相同的,有些事不相同的,需要全面考虑。

单向循环链表

单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为none,而是指向链表的头节点。
在这里插入图片描述

操作的具体实现

  • is_empty()链表是否为空
  • length()链表长度
  • travel()遍历链表
  • add(item)链表头部添加
  • append(item)链表尾部添加
  • insert(pos,item)在指定位置添加
  • remove(item)删除节点
  • search(item)查找节点是否存在
    在定义单向循环链表的节点类时,需要注意其尾节点还要指向头节点
class SingleCircleLinkList(object):
    """单向循环链表"""
    def __init__(self,node=None):
        self.__head=node
        if node:
            node.next=node#尾节点要指向头节点
    def is_empty(self):
        """链表是否为空"""
        return self.__head==None

求链表长度
同样需要遍历,当遍历到next指向__head时,表示遍历结束。但是不可以使用cur==self.__head这一条件判断,应该当遍历开始的第一个节点就是self._head。
判断进入循环的条件变了,那么count的初始值就不能再是0,而是从1开始,这样才能count出正确的值。
在这里插入图片描述

    def length(self):
        """链表长度"""
        #cur游标,用来移动遍历节点
        cur=self.__head
        #count记录数量
        count=1
        while cur.next!=self.__head:
            count+=1
            cur=cur.next
        return  count

对于空链表来说,没法进行count,因此需要进行判断:

    def length(self):
        """链表长度"""
        if self.is_empty():
            return 0
        #cur游标,用来移动遍历节点
        cur=self.__head
        #count记录数量
        count=1
        while cur.next!=self.__head:
            count+=1
            cur=cur.next
        return  count

对于链表中只有一个节点也可以通过以上代码解决。
遍历链表
当遍历到尾节点时,没法进入循环,不能打印尾节点,需要单独打印cur指向的节点。同时也是需要考虑特殊情况,如链表为空的情况,链表中只有一个节点的情况:

    def travel(self):
        """遍历整个链表"""
        if self.is_empty():
            return
        cur=self.__head
        while cur.next!=self.__head:
            print(cur.elem,end=" ")
            cur=cur.next
        #退出循环,cur指向尾节点,但尾节点的元素不打印,需要单独打印
        print(cur.elem)

头插法
在这里插入图片描述
在单向循环链表的头部插入节点,不仅需要像单链表一样修改两次指针,还要将尾节点的指针指向新插入的节点元素,所以单向循环链表的头插法还需要遍历。
在这里插入图片描述

    def add(self,item):
        """链表头部插入节点"""
        node=Node (item)
        cur=self.__head
        while cur.next!=None:
            cur=cur.next
        #退出循环,cur指向尾节点
        node.next=self.__head
        self.__head=node
        cur.next=self.__head

上述代码中,cur,next=self.__head之所以能使用,是因为在使用前,已经将self._head指向了新添加的节点。
除此之外,我们还要考虑特殊情况——链表为空时,此时,需要在代码中设置一个判断:

        if self.is_empty():
            self.__head=node
            node.next=node

尾插法
方法一:先让待插入节点的next域指向头节点,再让尾节点next域指向待插入节点。
在这里插入图片描述
方法二:先让cur的next域指向待插入节点,再让node的next域指向self.__head。
cur.next=node node.next=self.__head
其实两种方法本质是修改指针的顺序不同,代码也就不同。

    def append(self,item):
        """链表尾部添加节点,尾插法"""
        node=Node(item)
        if self.is_empty():
            self.__head=node
            node.next=node
        else:
            cur=self.__head
            while cur.next!=self.__head:
                cur=cur.next
            node.next=self.__head
            cur.next=node

再指定位置插入节点
除了头插和尾插,在中间插入节点,因为不涉及尾部和头部的变化,所以和单链表的在指定位置插入节点的操作是一样的。这里不再赘述。
查找元素
同样需要遍历,cur从头开始。根据判断条件,在查找循环到尾部时,其实没有进行比较,为了不落下尾部节点元素的比较,需要单独处理。当然,也要考虑当链表中只有一个节点的特殊情况:

    def search(self,item):
        """查找节点是否存在"""
        if self.is_empty():
            return False
        cur=self.__head
        while cur.next!=self.__head:
            if cur.elem==item:
                return True
            else:
                cur=cur.next
        #退出循环,cur指向尾节点
        if cur.elem==item:
            return True
        return False

删除节点
当腰删除的元素不是头节点或者尾节点,那么操作和单向链表的操作是一致的。那么,如果想要删除头节点,先去找到尾节点是更快速的方式。
同时,需要考虑空链表的删除这个特殊情况,以及链表中只有一个节点的特殊情况。

    def remove(self,item):
        """删除节点"""
        if self.is_empty():
            return 
        cur=self.__head
        pre=None
        while cur.next!=self.__head:
            if cur.elem==item:
                #先判断此节点是否是头节点
                if cur==self.__head:
                    #是头节点
                    #找尾节点
                    rear=self.__head
                    while rear.next!=self.__head:
                        rear.next=self.__head
                    self.__head=cur.next
                    rear.next=self.__head
                else:
                    #是中间节点
                    pre.next=cur.next
                return
            else:
                pre=cur
                cur=cur.next
        #退出循环,cur指向尾节点
        if cur.elem==item:
            if cur==self.__head
                #链表只有一个节点
                self.__head=None
            else:
                pre.next=cur.next

在单链表删除元素时,通过break终止循环来终止函数,但是在单循环链表中,break终止的只是循环并没有终止函数。所以这里只能用return。
remove操作是删除元素的,需要遍历链表,首先要考虑链表是否为空:需要两个游标做辅助,遍历的终止条件是cur.next=self.head。
退出循环后,需要判断cur指向节点是否是指定节点,
当链表里只有一个节点,且这个节点恰好是要被删除的节点,这时pre指向的是none,不适用最后的pre.next=cur.next这句代码,所以需要self.head直接指向none。在单向循环链表中,删除头节点的时候,需要更改一下尾节点的next域,所以需要先找到尾节点。
测试单循环链表:

class Node(object):
    """节点"""
    def __init__(self,item):
        self.elem=item
        self.next=None
class SingleCircleLinkList(object):
    """单向循环链表"""
    def __init__(self,node=None):
        self.__head=node
        if node:
            node.next=node#尾节点要指向头节点
    def is_empty(self):
        """链表是否为空"""
        return self.__head==None
    def length(self):
        """链表长度"""
        if self.is_empty():
            return 0
        #cur游标,用来移动遍历节点
        cur=self.__head
        #count记录数量
        count=1
        while cur.next!=self.__head:
            count+=1
            cur=cur.next
        return  count
    def travel(self):
        """遍历整个链表"""
        if self.is_empty():
            return
        cur=self.__head
        while cur.next!=self.__head:
            print(cur.elem,end=" ")
            cur=cur.next
        #退出循环,cur指向尾节点,但尾节点的元素不打印,需要单独打印
        print(cur.elem)
    def add(self,item):
        """链表头部插入节点"""
        node=Node (item)
        if self.is_empty():
            self.__head=node
            node.next=node
        else:
            cur=self.__head
            while cur.next!=self.__head:
                cur=cur.next
            #退出循环,cur指向尾节点
            node.next=self.__head
            self.__head=node
            cur.next=self.__head
    def append(self,item):
        """链表尾部添加节点,尾插法"""
        node=Node(item)
        if self.is_empty():
            self.__head=node
            node.next=node
        else:
            cur=self.__head
            while cur.next!=self.__head:
                cur=cur.next
            node.next=self.__head
            cur.next=node
    def insert(self,pos,item):
        """指定位置添加节点元素"""
        if pos<=0:
            self.add(item)
        elif pos>(self.length()-1):
            self.append(item)
        else:
            pre=self.__head
            count=0
            while count<(pos-1):
                count+=1
                pre=pre.next
            #当循环退出后,pre指向pos-1的位置
            node=Node(item)
            node.next=pre.next
            pre.next=node
    def search(self,item):
        """查找节点是否存在"""
        if self.is_empty():
            return False
        cur=self.__head
        while cur.next!=self.__head:
            if cur.elem==item:
                return True
            else:
                cur=cur.next
        #退出循环,cur指向尾节点
        if cur.elem==item:
            return True
        return False
    def remove(self,item):
        """删除节点"""
        if self.is_empty():
            return
        cur=self.__head
        pre=None
        while cur.next!=self.__head:
            if cur.elem==item:
                #先判断此节点是否是头节点
                if cur==self.__head:
                    #是头节点
                    #找尾节点
                    rear=self.__head
                    while rear.next!=self.__head:
                        rear.next=self.__head
                    self.__head=cur.next
                    rear.next=self.__head
                else:
                    #是中间节点
                    pre.next=cur.next
                break
            else:
                pre=cur
                cur=cur.next
        #退出循环,cur指向尾节点
        if cur.elem==item:
            if cur==self.__head:
                #链表只有一个节点
                self.__head=None
            else:
                pre.next=cur.next

if __name__ == "__main__":
        sll = SingleCircleLinkList()
        print(sll.is_empty())
        print(sll.length())

        sll.append(1)  # 1
        print(sll.is_empty())
        print(sll.length())

        sll.append(2)  # 12
        sll.add(8)  # 812
        sll.append(3)  # 8123
        sll.append(4)
        sll.append(5)
        sll.append(6)  # 8123456

        sll.insert(-1, 9)  # 98123456
        sll.travel()
        sll.insert(3, 100)  # 981 100 23456
        sll.travel()
        sll.insert(10, 200)  # 981 100 23456 200
        sll.travel()
        sll.remove(100)  # 981 23456 200
        sll.travel()
        sll.remove(9)  # 81 23456 200
        sll.travel()
        sll.remove(200)  # 81 23456
        sll.travel()

运行结果:
在这里插入图片描述
链表小知识:
在这里插入图片描述
p指针指向的这一块内存区域是可以存链表相关信息的区域(有什么节点,或者一些辅助信息)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值