数据结构与算法_part_3 —— 双向链表和单向循环链表

1. 双向链表

单向链表之所以叫做单向链表,是因为它的所有链接区域都指向了后继节点,这样的缺点就是:无法找到前面的节点,如果想要找前面的节点,只能从头开始查找。

节点指向的后面的那个节点称之为:后继节点

节点指向的前面的那个节点称之为:前驱节点

之前学习的是单向链表:

在这里插入图片描述

现在我们需要把节点进行重新改造,改造为 双向节点

在这里插入图片描述

对于双向节点来说,就有三个部分了:

  1. 数据区
  2. 后继区
  3. 前驱区

其中:

  • n/N: Next
  • p/P: Prior/Previous

一种更复杂的链表是“双向链表“或“双面链表”。每个节点有两个链接:一个指向前一个节点,当此节点为一个节点时,指向空值;而另一个指向下一个节点,当此节点为最后一个节点时,指向空值。

在这里插入图片描述

  • 双向链表头节点的前驱节点为None
  • 双向链表尾节点后继节点为None

1.1 双向链表实现的操作

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

class Node():
    """节点"""
    def __init__(self, item):
        self.elem = item
        self.next = None
        self.prior = None
        
        
class DoubleLinkedList():
    """
    双链表:对于单链表和双链表而言,
        + is_empty()
        + length()
        + travel()
        + search()
        
        这四个方法是通用的,所以我们可以直接继承单链表(这就体现了封装的好处)
        
        但我们一直用的是私有方法,所以按规定是不能继承的  
    """
    
    def __init__(self, node=None):
        """头节点是不向外部暴露的,因此为私有属性
            如果指定node,那么头节点就确定了
            如果不指定node,代表链表为空(里面没有任何节点)
        """
        self.__head = node
    
    def is_empty(self):
        """链表是否为空
            只要self._head指向None,那么意味着链表为空
            如果不指向None,则意味着链表不为空
        """
        return self.__head == None
    
    def length(self):
        """链表长度
            current游标用来移动,遍历节点
        """
        current = self.__head  # cur指向的就是节点node
        # count记录节点的个数
        count = 0  # count初始化
        while current:  # 当current不为None
            count += 1
            current = current.next  # 移动游标(指针)
        return count
    
    def travel(self):
        """遍历整个链表"""
        cur = self.__head  # cur指向的就是节点node
        print("链表内容: [", end="")
        while cur:
            print(f"{
      
      cur.elem}", end=" ")
            cur = cur.next
        print("]", end="\n")
    
    def add(self, item):
        """在链表头部插入一个节点(里面包含了元素)"""
        node = Node(item)  # 构造存放元素的节点
        node.next = self.__head  # 让新节点的next指向头节点
        self.__head.prior = node  # 让头节点的prior指向新的节点
        self.__head = node  # 让头指向新的节点
    
    def append(self, item):
        """在链表头部插入一个节点(里面包含了元素)
            想要在尾部插入,首先需要一个指针 -> 找到尾节点
        """
        node = Node(item)  # 构造存放元素的节点
        
        if self.is_empty():
            self.__head = node  # 特殊情况
        else:
            cur = self.__head
            while cur.next:
                cur = cur.next

            cur.next = node
            node.prior = cur
            # node.next = None  -> 没必要,因为node初始化后,node.next就是等于None
        
    def insert(self, pos, item):
        if pos <= 0:  # 说明是头插,直接调用前面实现的方法即可
            self.add(item)
        elif pos > (self.length() - 1):  # 尾插法
            self.append(item)
        else:
            """
                之前我们为了找到指定位置,使用了两个指针,现在双链表就不需要两个指针了
            """
            node = Node(item)
            cur = self.__head
            count = 0
            while count < pos:
                count += 1
                cur = cur.next
            # 当循环退出后, cur指向pos位置
            # 方法1
            node.next = cur
            node.prior = cur.prior
            cur.prior.next = node
            cur.prior = node
            
            # 方法2
            """
            node.next = cur
            node.prior = cur.prior
            cur.prior = node
            cur.prior.next = node
            """
            
    def remove(self, item):
        """
            特殊情况:
                1. 需删除的节点为头节点
                2. 原有的链表只有一个节点
                3. 要删除的节点为尾节点
        """
        cur = self.__head
        while cur:
            if cur.elem == item:  # 找到该元素并删除
                if cur == self.__head:  # 需删除的元素为头节点
                    self.__head = cur.next
                    if cur.next:  # 如果原有的链表只有一个节点,如果是,不加判断就会报错
                        cur.next.prior = None
                    else:
                        pass  # 已经是None了
                else:  # 需删除的不是头节点
                    cur.prior.next = cur.next  # 指针的上一个node.next指向cur后面的node
                    if cur.next:  # 如果要删除的节点为尾节点,那么cur.next.prior就会报错
                        cur.next.prior = cur.prior  # cur后面的prior指向cur前面的
                    else:  # cur.next is None -> 不用管了
                        pass
                break
            else:
                cur = cur.next  # 指针继续
                
    def search(self, item):
        """查找节点是否存在: 就是查找指定的节点node是否在链表中
            存在:True
            不存在:False

            满足cur==None的特殊情况
        """
        cur = self.__head
        while cur:
            if cur.elem == item:
                return True
            else:
                cur = cur.next
        return False

    
if __name__ == "__main__":
    # 创建一个空链表 dll -> Double Linked List
    dll = DoubleLinkedList()
    print(f"链表是否为空: {
      
      dll.is_empty()}")
    print(f"链表的长度: {
      
      dll.length()}")
    print("-" * 50)
    
    # 往链表中追加元素
    dll.append(1)
    print(f"链表是否为空: {
      
      dll.is_empty()}")
    print(f"链表的长度: {
      
      dll.length()}")
    
    # 往链表中追加元素
    dll.append(2)
    dll.append(3)
    dll.append(4)
    dll.append(5)
    print(f"链表是否为空: {
      
      dll.is_empty()}")
    print(f"链表的长度: {
      
      dll.length()}")
    
    # 往链表的头部添加元素
    dll.add(100)
    
    # 遍历链表
    dll.travel()
    
    # 在指定位置添加元素
    dll.insert(pos=-2, item=9)
    dll.travel()
    dll.insert(pos=5, item=321)
    dll.travel()
    dll.insert(pos=100, item=123456)
    dll.travel()
    
    # 删除元素
    dll.remove(9)
    dll.travel()
    dll.remove(123456)
    dll.travel()

结果:

链表是否为空: True
链表的长度: 0
--------------------------------------------------
链表是否为空: False
链表的长度: 1
链表是否为空: False
链表的长度: 5
链表内容: [100 1 2 3 4 5 ]
链表内容: [9 100 1 2 3 4 5 ]
链表内容: [9 100 1 2 3 321 4 5 ]
链表内容: [9 100 1 2 3 321 4 5 123456 ]
链表内容: [100 1 2 3 321 4 5 123456 ]
链表内容: [100 1 2 3 321 4 5 ]

2. 单向循环链表

单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头节点。

在这里插入图片描述

2.1 单向循环链表实现的方法

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

class Node():
    def __init__(self, item):
        self.elem = item
        self.next = None
        
        
class SingleCirclarLinkedList():
    """单向循环链表"""
    def __init__(self, node=None):
        self.__head = node
        
        if node:
            node.next = node  # 指向自己(Circular的特点)
            
    def is_empty(self):
        return self.__head is None
    
    def length(self):
        """
            特殊情况:
                1. 链表为空
                2. 只有一个node -> 满足
        """
        if self.is_empty():
            return 0
        
        cur = self.__head
        count = 1  # 为了正确计数,count = 1
        while cur.next != self.__head:
            count += 1
            cur = cur.next
        return count
    
    def travel(self):
        """
            特殊情况:
                1. 链表为空
                2. 链表只有一个node -> 满足
        """
        if self.is_empty():  # case_1
            return 
        cur = self.__head
        print("[", end="")
        while cur.next != self.__head:
            print(cur.elem, end=" ")
            cur = cur.next
        # 退出循环,cur指向尾节点,但尾节点的元素并未打印
        print(cur.elem, end="")
        print("]")
    
    def add(self, item):
        """
            还需要将尾节点的next指向新node -> 需要一个指针
            
            思路1:先找到尾节点,将尾节点的next指向新节点,再操作其他
            思路2:先操作新节点,最后再找尾节点
            
            特殊情况:
                1. 链表为空
                2. 链表只有一个节点 -> 满足
        """
        node = Node(item)
        
        if self.is_empty():  # case_1
            self.__head = node
            node.next = node
        
        else:
            cur = self.__head
            # 方法1 —— 先找到尾节点
            while cur.next != self.__head:
                cur = cur.next
            # 结束循环后,cur指向尾节点
            node.next = self.__head
            self.__head = node
            cur.next = node  # 或者写成cur.next = self.__head


#             # 方法2 —— 先处理新node,再重新找尾节点
#             node.next = self.__head
#             self.__head = node
#             while cur.next != node.next:
#                 cur = cur.next
#             # 结束循环后,cur指向尾节点
#             cur.next = node
            
    def append(self, item):
        """
            特殊情况:
                1. 链表为空
                2. 只有一个元素 -> 满足
        """
        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
            cur.next = node
            
            
    def insert(self, pos, item):
        if pos <= 0:  # 头插法
            self.add(item)
        elif pos > (self.length() - 1):  # 尾插法
            self.append(item)
        else:
            """
                对于中间插入,不影响到尾部链接头部 <=> 单链表的insert方法
            """
            prior = self.__head
            count = 0
            while count < (pos - 1):
                count += 1
                prior = prior.next
            # 退出循环,prior指向pos-1位置
            node = Node(item)
            node.next = prior.next
            prior.next = node
    
    def remove(self, item):
        """
            只有涉及到头节点或尾节点时,才需要遍历(因为尾节点需链接到头节点)
        """
        if self.is_empty():  # 链表为空
            return
        
        cur = self.__head
        prior = None
        
        while cur.next != self.__head:
            if cur.elem == item:
                # 判断情况
                if cur == self.__head:  # 头节点
                    rear = self.__head
                    while rear.next != self.__head:
                        rear = rear.next
                    # rear指向尾节点
                    self.__head = cur.next
                    rear.next = self.__head
                    
                else:  # 中间节点(不涉及到尾节点重新链接问题)
                    prior.next = cur.next
                
                # 已经找到了,循环退出(实际是方法退出)
                return
                
            else:  # 这次没有找到,指针移动
                prior = cur
                cur = cur.next
        
        # 如果删除的节点是尾节点(尾节点不会进循环体,需单独处理)
        # 退出循环,cur指向尾节点
        if cur.elem == item:  # 尾节点或者只有一个节点
            if cur == self.__head:  # 链表只有一个节点
                self.__head = None
            else:
                prior.next = cur.next
        
    
    def search(self, item):
        """
            特殊情况:
                1. 链表为空
                2. 只有一个node -> 满足
        """
        if self.is_empty():  # Case_1: 空链表肯定没有元素
            return False
        
        cur = self.__head
        while cur.next != self.__head:
            if cur.elem == item:
                return True
            else:
                cur = cur.next
        # 循环走完,cur指向尾节点,但没有让尾节点的数据于item进行比较
        if cur.elem == item:  # 尾节点数据与item进行判断
            return True
        else:
            return False


if __name__ == "__main__":
    # 创建一个空链表
    scll = SingleCirclarLinkedList()
    print(f"链表是否为空: {
      
      scll.is_empty()}")
    print(f"链表的长度: {
      
      scll.length()}")
    
    # 往链表中追加元素
    scll.append(1)
    print(f"链表是否为空: {
      
      scll.is_empty()}")
    print(f"链表的长度: {
      
      scll.length()}")
    
    # 往链表中追加元素
    scll.append(2)
    scll.append(3)
    scll.append(4)
    scll.append(5)
    print(f"链表是否为空: {
      
      scll.is_empty()}")
    print(f"链表的长度: {
      
      scll.length()}")
    
    # 往链表的头部添加元素
    scll.add(100)
    
    # 遍历链表
    scll.travel()
    
    # 在指定位置添加元素
    scll.insert(pos=-2, item=9)
    scll.travel()
    scll.insert(pos=5, item=321)
    scll.travel()
    scll.insert(pos=100, item=123456)
    scll.travel()
    
    # 删除元素
    scll.remove(9)
    scll.travel()
    scll.remove(123456)
    scll.travel()

结果:

链表是否为空: True
链表的长度: 0
链表是否为空: False
链表的长度: 1
链表是否为空: False
链表的长度: 5
[100 1 2 3 4 5]
[9 100 1 2 3 4 5]
[9 100 1 2 3 321 4 5]
[9 100 1 2 3 321 4 5 123456]
[100 1 2 3 321 4 5 123456]
[100 1 2 3 321 4 5]

3. 链表的总结

  • 单向链表
  • 双向链表
  • 单向循环链表

现在我链表的部分就讲完了,其实如果还要扩充的话:

  1. 可以将双链表扩充为双向循环链表
  2. 在将顺序表时,有一个表头信息。那么对于链表来说,是否可以做成 包含表头信息的链表

在学习链表的过程中,代码是其次的,主要是学习指针移动的思想

猜你喜欢

转载自blog.csdn.net/weixin_44878336/article/details/125221509