(Python3)数据结构——06.双向链表的原理及实现

前言

  • 有Python基础
  • 学过数据结构那自然是最好的

原理

双向链表

在这里插入图片描述
双向链表和单链表的差别在哪?双向链表的节点和单链表的节点是不一样的。单链表的节点有item和next,而双向链表的节点还必须有一个指向前一个节点的prior(别的命名也行,反正必须有个东西指向前一个节点)。prior存在的意义也是为了更好地查找节点的前驱节点。因此必须对Node进行改造。

实现

节点的定义

class Node(object):
    # 初始化时它就只是个孤家寡人,前不着村后不着店,prior和next都是空
    def __init__(self, item):
        self.item = item
        self.prior = None
        self.next = None

初始化

  • 双向链表的构造函数和单链表一样,只需要设置空的头节点即可。
class DeLink(object):
    def __init__(self):
        self.__head = None
  • 事实上,双向链表比单链表的节点多了一个属性prior,其他的方法照样可以沿用。
  • 参照博客
https://blog.csdn.net/sf9898/article/details/104946291  
  • 将单链表的代码拷贝下来,在单链表的class Node下按照上面说的那样子改造,加一个prior属性,其他的别改,直接运行并没有毛病。其原因在于增加了一个并没有使用到的prior属性,运行的实际上还是单链表。
  • 正如前面讲的,prior存在的意义是为了查找前一个节点,这也是双向链表的优点。那么就可以有利用prior属性查找节点的方法。
  • 接下来按照双向链表的原理重新构造一下class DeLink
  • size 方法不需要讲究,利用原来的单链表方法即可,毕竟只是求个数。当然,判空和遍历也可以沿用。
 	def isEmpty(self):
        return self.__head is None
        
 	def size(self):
        count = 0
        cur = self.__head
        while cur:
            count += 1
            cur = cur.next
        return count

    def travel(self):
        cur = self.__head
        while cur:
            print(cur.item, end=' ')
            cur = cur.next
        print('')

插入

  • 查找,删除数值等函数亦是可以沿用,加上了prior属性之后变化的主要是插入和删除,其他的沿用即可。接下来重点看看插入和删除。
  • 头部插入

在这里插入图片描述

 # 头部插入
    def addFront(self, item):
        node = Node(item)       # 新的节点只有item,next和prior都是None
        node.next = self.__head     # 新的节点的next指向当前的第一个节点
        if self.__head:
        	self.__head.prior = node        # 不为空时设置当前第一个节点的prior指向新来的node
        self.__head = node      # 新来的node成为新的头结点
  • 尾部插入:尾部插入需要将原来的尾部节点last的next指向新的node(原来的last的next是指向None的),node的prior指向last(这一步要有,这样通过node的prior就可以找到last了)。为了减少工作量,直接在原有的单链表上面改了。

在这里插入图片描述

# 尾部插入
    def addBack(self, item):
        node = Node(item)
        if self.__head is None:
            self.__head = node
            return
        cur = self.__head
        pre = None
        while cur:
            pre = cur
            cur = cur.next
            # 当cur 为最后一个节点时带入,pre更新为最后一个节点,cur更新为最后一个节点的下一个节点即为空,
            # 下一次while cur 时会退出循环,此时的pre表示的就是最后一个节点,将node挂到pre的后面即可
        pre.next = node     # 这里的pre相当于例子中的last
        # 仅加了下面这句
        node.prior = pre
  • 中间插入:需要注意的是要找到插入位置的前一个节点。如图,找到pre,之后:
  1. pre.next = node
  2. node.prior = pre
  3. node.next = nd
  4. nd.prior = node

在这里插入图片描述

	# 插入节点, 节点在插入后是第 pos 个节点,当然这个函数也可以实现头部插入和尾部插入的功能
    # 故pos取值1时,即头部插入,取值size+1时是尾部插入。因此取值的合法范围是[1,size + 1]
    def insert(self, pos, item):
        if pos > (self.size() + 1) or pos < 1:
            return
        if pos == 1:
            self.addFront(item)
            return
        node = Node(item)
        cur = self.__head
        pre = None
        for i in range(pos - 1):
            pre = cur
            cur = cur.next
        # 1) pre.next = node
        # 2) node.prior = pre
        # 3) node.next = nd
        # 4) nd.prior = node
        pre.next = node
        node.prior = pre
        node.next = cur
        if cur:
            cur.prior = node

删除

  • 头部删除:更新头结点为头结点的下一个节点,原头结点的next指向None,现头结点的prior指向None。

在这里插入图片描述

# 删除头部结点
    def removeFront(self):
        cur = self.__head
        self.__head = self.__head.next
        cur.next = None
        self.__head.prior = None
  • 删除尾部结点

在这里插入图片描述

# 删除尾部节点
    def removeBack(self):
        # 空节点时
        if self.__head is None:
            return
        # 只有一个节点
        if self.__head and self.__head.next is None:
            self.__head = None
            return
        # 链表节点有两个及以上
        cur = self.__head  # 当前节点
        pre = None  # 前一个节点
        cn = cur.next  # 后一个节点
        # 刚开始cur取到的是第一个节点,cn是第二个
        while cn:
            pre = cur
            cur = cur.next
            cn = cur.next
        # 仅修改此处
        pre.next = None
        cur.prior = None
  • 中间删除

在这里插入图片描述

# 删除指定数值的节点
    def delete(self, item):
        if self.__head is None:
            return
        if self.__head.item == item:
            self.__head = None
            return
        cur = self.__head.next  # 取第二个节点
        pre = self.__head  # 第一个节点
        while cur:
            if cur.item == item:
            	# 仅修改此处
                pre.next = cur.next
                cur.prior = None
                cur.next.prior = pre
                cur.next = None
                break
            else:
                pre = cur
                cur = cur.next

完整代码

参照之前单链表进行测试,下面是完整代码,如有错误之处还望批评指出。

class Node(object):
    # 初始化时它就只是个孤家寡人,前不着村后不着店,prior和next都是空
    def __init__(self, item):
        self.item = item
        self.prior = None
        self.next = None


class DeLink(object):
    def __init__(self):
        self.__head = None

    def isEmpty(self):
        return self.__head is None

    # 头部插入
    def addFront(self, item):
        node = Node(item)  # 新的节点只有item,next和prior都是None
        node.next = self.__head  # 新的节点的next指向当前的第一个节点
        if self.__head:
            self.__head.prior = node  # 设置当前第一个节点的prior指向新来的node
        self.__head = node  # 新来的node成为新的头结点

    # 尾部插入
    def addBack(self, item):
        node = Node(item)
        if self.__head is None:
            self.__head = node
            return
        cur = self.__head
        pre = None
        while cur:
            pre = cur
            cur = cur.next
            # 当cur 为最后一个节点时带入,pre更新为最后一个节点,cur更新为最后一个节点的下一个节点即为空,
            # 下一次while cur 时会退出循环,此时的pre表示的就是最后一个节点,将node挂到pre的后面即可
        pre.next = node  # 这里的pre相当于例子中的last
        node.prior = pre

    def size(self):
        count = 0
        cur = self.__head
        while cur:
            count += 1
            cur = cur.next
        return count

    def travel(self):
        cur = self.__head
        while cur:
            print(cur.item, end=' ')
            cur = cur.next
        print('')

    # 删除头部节点
    def removeFront(self):
        cur = self.__head
        self.__head = self.__head.next
        cur.next = None
        self.__head.prior = None

    # 删除尾部节点
    def removeBack(self):
        # 空节点时
        if self.__head is None:
            return
        # 只有一个节点
        if self.__head and self.__head.next is None:
            self.__head = None
            return
        # 链表节点有两个及以上
        cur = self.__head  # 当前节点
        pre = None  # 前一个节点
        cn = cur.next  # 后一个节点
        # 刚开始cur取到的是第一个节点,cn是第二个
        while cn:
            pre = cur
            cur = cur.next
            cn = cur.next
        # 仅修改此处
        pre.next = None
        cur.prior = None

    # 查找链表中有没有item,有返回True,没有则返回False
    def search(self, item):
        cur = self.__head
        res = False
        while cur:
            if cur.item == item:
                res = True
                break
            else:
                cur = cur.next
        return res

    # 查找某个数的下标
    # def searchIndex(self, item):
    #     if self.search(item):
    #         # 存在的话才进行
    #         cur = self.__head
    #         index = -1
    #         while cur:
    #             index += 1
    #             if cur.item == item:
    #                 break
    #             else:
    #                 cur = cur.next
    #         return index
    #     else:
    #         return -1

    # 删除指定数值的节点
    def delete(self, item):
        if self.__head is None:
            return
        if self.__head.item == item:
            self.__head = None
            return
        cur = self.__head.next  # 取第二个节点
        pre = self.__head  # 第一个节点
        while cur:
            if cur.item == item:
                pre.next = cur.next
                cur.prior = None
                cur.next.prior = pre
                cur.next = None
                break
            else:
                pre = cur
                cur = cur.next

    # 插入节点, 节点在插入后是第 pos 个节点,当然这个函数也可以实现头部插入和尾部插入的功能
    # 故pos取值1时,即头部插入,取值size+1时是尾部插入。因此取值的合法范围是[1,size + 1]
    def insert(self, pos, item):
        if pos > (self.size() + 1) or pos < 1:
            return
        if pos == 1:
            self.addFront(item)
            return
        node = Node(item)
        cur = self.__head
        pre = None
        for i in range(pos - 1):
            pre = cur
            cur = cur.next
        # 1) pre.next = node
        # 2) node.prior = pre
        # 3) node.next = nd
        # 4) nd.prior = node
        pre.next = node
        node.prior = pre
        node.next = cur
        if cur:
            cur.prior = node


ll = DeLink()
print(ll.isEmpty())     # 初始化时为空,理应输出True
for i in range(5):
    ll.addFront(i)
print('size:', ll.size())       # 5个数
ll.travel()     # 4 3 2 1 0 
for i in range(5):
    ll.addBack(i)
ll.travel()     # 4 3 2 1 0 0 1 2 3 4 
ll.removeFront()
ll.travel()     # 3 2 1 0 0 1 2 3 4 
print('----')
ll.removeBack()
ll.travel()     # 3 2 1 0 0 1 2 3 
ll.insert(9, 12)
ll.travel()     # 3 2 1 0 0 1 2 3 12 
print(ll.search(13))        # 没有,False
print(ll.search(1))     # True
ll.delete(2)
ll.travel()     # 3 1 0 0 1 2 3 12 
print('------')
# print(ll.searchIndex(12))
ll.insert(8, 100)
ll.travel()     # 3 1 0 0 1 2 3 100 12 
  • 结果

在这里插入图片描述

可以发现写起来代码量会多一点点,事实上,很多东西用单链表就能解决了,双向链表比单链表多了一个prior,这一点是双向链表的优点,同时也是它的缺点,毕竟多一个元素就多占一点空间,因此还是单链表用的比较多。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值