欢迎来到代码驿站!

Python代码

当前位置:首页 > 软件编程 > Python代码

Python数据结构之循环链表详解

时间:2022-06-26 10:34:06|栏目:Python代码|点击:

0. 学习目标

循环链表 (Circular Linked List) 是链式存储结构的另一种形式,它将链表中最后一个结点的指针指向链表的头结点,使整个链表头尾相接形成一个环形,使链表的操作更加方便灵活。我们已经介绍了单链表和双向链表,本节中我们将基于单链表和双向链表实现循环链表与循环双链表以及它们的相关操作。
通过本节学习,应掌握以下内容:

循环链表的基本概念及其优缺点

循环链表及其操作的实现

循环双链表及其操作的实现

1. 循环链表简介

在单链表/双向链表中,最后一个结点的指针域为 None,访问链表中任何数据只能从链表头开始顺序访问,而不能进行任何位置的随机查询访问,如果查询的结点在链表的尾部也需遍历整个链表。

循环链表是一种特殊的链表,在循环链表中,首尾端点相互连接,使整个链表头尾相接形成一个环形,也就是说链表中的最后一个结点指向第一个结点;换句话说,在循环链表中所有结点都指向下一个结点,每一个结点都有一个后继结点。

循环链表的优点是,从链表中任一结点出发,顺着指针链都可找到表中其它结点,因此没有结点指向 None;同时并不会占用额外的存储空间,仅仅是改变了最后一个结点的指针指向,就可以使操作更加方便灵活。

循环链表可以基于单链表和双向链表,通常基于单链表的循环链表称为循环单链表,而基于双向链表的循环链表称为循环双链表,两种类型的循环链表示意图如下所示:

NOTE:由于我们对于链表已经非常熟悉了,因此对于链表中相似的操作只会进行简要的介绍,我们的主要精力将放在具有差异的操作上。

2. 循环单链表实现

类似于单链表,接下来让我们实现一个带有头结点的循环单链表类,并用头指针标识链表的开头,如果你还不了解单链表,可以参考《单链表及其操作实现》相关介绍。

2.1 循环单链表的基本操作

循环单链表的基本操作大部分与单链表相同,只是循环遍历是否完成的条件需要由 current == None 改为 current != head

2.1.1 循环单链表的初始化

初始化循环单链表函数中,创建的头结点指针域 next 不为空,而是指向自身:

class CircularLinkedList:
    def __init__(self):
        head_node = Node()
        self.head = head_node
        head_node.next = head_node
        self.length = 0

2.1.2 获取循环单链表长度

重载 __len__ 从对象返回 length 的值用于求取循环单链表长度:

    def __len__(self):
        return self.length

2.1.3 读取指定位置元素

重载 __getitem__ 操作用于实现读取链表指定位置元素的操作,类似的,我们也可以实现修改指定位置元素的操作,只需要重载 __setitem__ 操作,它们的时间复杂度均为O(n):

def __getitem__(self, index):
        if index > self.length - 1 or index < 0:
            raise IndexError("CircularLinkedList assignment index out of range")
        else:
            count = -1
            current = self.head
            while count < index:
                current = current.next
                count += 1
            return current.data
    def __setitem__(self, index, value):
        if index > self.length - 1 or index < 0:
            raise IndexError("CircularLinkedList assignment index out of range")
        else:
            count = -1
            current = self.head
            while count < index:
                current = current.next
                count += 1

            current.data = value

我们可以发现,这两个操作与单链表的操作完全相同。

2.1.4 查找指定元素

在循环单链表中查找指定元素的操作与单链表基本相同,使用 current 指针沿后继链依次遍历每个结点,并判断其值是否等于指定值 value,若是则返回该结点索引,否则继续往后搜索;只是循环遍历是否完成的条件需要由 current == None 改为 current != head:

    def locate(self, value):
        count = 0
        current = self.head.next
        while current != self.head and current.data != value:
            count += 1
            current = current.next
        if current and current.data == value:
            return count
        else:
            raise ValueError("{} is not in sequential list".format(value))

2.1.5 在指定位置插入新元素

由于有 length 属性的原因,我们可通过 length 判断插入位置是否合法,因此在循环单链表中的指定位置插入新元素与在单链表指定位置插入新元素完全相同:

    def insert(self, index, data):
        count = -1
        current = self.head
        # 判断插入位置的合法性
        if index > self.length or index < 0:
            raise IndexError("CircularLinkedList assignment index out of range")
        else:
            node = Node(data)
            while count < index:
                # 查找插入位置
                previous = current
                current = current.next
                count += 1
            # 插入新结点
            node.next = previous.next
            previous.next = node
            self.length += 1

2.1.6 删除指定位置元素

要删除链表中第i个结点,同样首先找到删除位置的前一个结点 previous,指针 current 指向要删除的结点,将 previous 的指针域修改为待删除结点 current 的后继结点的地址,删除后的结点需动态的释放:

    def __delitem__(self, index):
        if index > self.length - 1 or index < 0:
            raise IndexError("CircularLinkedList assignment index out of range")
        else:
            count = -1
            previous = self.head
            while count < index - 1:
                previous = previous.next
                count += 1
            current = previous.next
            previous.next = current.next
            self.length -= 1
            del current

2.1.7 其它一些有用的操作

[1] 使用 str 函数调用对象上的 __str__ 方法可以创建适合打印的字符串表示:

     def __str__(self):
        head = self.head
        s = "["
        current = self.head.next
        count = 0
        while current != head:
            count += 1
            s += str(current)
            current = current.next
            if count < self.length:
                s += '-->'
        s += "]"
        return s

[2] 删除指定元素,其时间复杂度与删除指定位置元素相同,都为O(n):

    def del_value(self, value):
        previous = self.head
        current = self.head.next
        while current != self.head:
            if current.data == value:
                previous.next = current.next
                self.length -= 1
                del current
                return
            else:
                previous = current
                current = current.next
        raise ValueError("The value provided is not present!")

[3] 为了方便的在链表尾部追加新元素,可以实现函数 append:

    def append(self, value):
        node = Node(value)
        current = self.head.next
        while current.next != self.head:
            current = current.next
        node.next = current.next
        current.next = node
        self.length += 1

2.2 简单的实现方法

从上述实现,我们可以看到,CircularLinkedList 类的代码中大部分与 SinglyLinkedList 类完全相同,如果你对继承机制还有印象的话,我们也可以令 CircularLinkedList 继承自在《单链表及其操作实现》中实现的 SinglyLinkedList,简化循环单链表的实现。

from SinglyLinkedList import SinglyLinkedList
class CircularLinkedList(SinglyLinkedList):
    """
    利用继承机制仅需要实现循环单链表与单链表中不同的操作,仅需要重载父类中:
    __init__(), locate(), del_value(), __str__(), append() 函数即可
    相关代码在上一小节已经给出,这里不再赘述
    """
    pass

2.3 循环单链表应用示例

接下来,我们将测试上述实现的循环单链表,以验证操作的有效性。首先初始化一个链表 cllist,并在其中追加若干元素:

cllist = CircularLinkedList()
# 在循环单链表末尾追加元素
cllist.append('apple')
cllist.append('lemon')
# 在指定位置插入元素
cllist.insert(0, 'banana')
cllist.insert(2, 'orange')

我们可以直接打印链表中的数据元素、链表长度等信息:

print('循环单链表 cllist 数据为:', cllist)
print('循环单链表 cllist 长度为:', len(cllist))
print('循环单链表 cllist 第 0 个元素为:', cllist[0])
cllist[0] = 'pear'
print('修改循环单链表 cllist 后数据元素为:', cllist)

以上代码输出如下:

循环单链表 cllist 数据为: [banana-->apple-->orange-->lemon]
循环单链表 cllist 长度为: 4
循环单链表 cllist 第 0 个元素为: banana
修改循环单链表 cllist 后数据元素为: [pear-->apple-->orange-->lemon]

接下来,我们将演示在指定位置添加/删除元素、以及如何查找指定元素等:

# 在指定位置添加/删除结点
cllist.insert(1, 'grape')
print('在位置 1 添加 grape 后循环单链表 cllist 数据:', cllist)
del(cllist[2])
print('修改循环单链表 cllist 后数据:', cllist)
# 删除指定元素
cllist.del_value('pear')
print('删除 pear 后循环单链表 cllist 数据:', cllist)
cllist.append('watermelon')
print('添加 watermelon 后循环单链表 cllist 数据:', cllist)

以上代码输出如下:

在位置 1 添加 grape 后循环单链表 cllist 数据: [pear-->grape-->apple-->orange-->lemon]
修改循环单链表 cllist 后数据: [pear-->grape-->orange-->lemon]
删除 pear 后循环单链表 cllist 数据: [grape-->orange-->lemon]
添加 watermelon 后循环单链表 cllist 数据: [grape-->orange-->lemon-->watermelon]

2.4 利用循环单链表基本操作实现复杂操作

[1] 将两个循环单链表首尾相接进行合并,连接示例如下图所示:

与单链表不同,循环单链表的连接不仅需要遍历第一个链表找到最后一个结点连接到第二个链表上,还需要遍历第二个链表,使第二个链表的尾结点的 next 指针指向第一个链表的头结点,具体实现如下:

def merge(cllist1, cllist2):
    current = cllist1.head
    while current.next != cllist1.head:
        current = current.next
    # 若cllist2不为空,进行连接操作
    if cllist2.head.next != cllist2.head:
        current.next = cllist2.head.next
        current2 = cllist2.head.next
        while current2.next != cllist2.head:
            current2 = current2.next
        current2.next = cllist1.head
        cllist1.length += len(cllist2)
        return cllist1
# 算法测试
cllist1 = CircularLinkedList()
cllist2 = CircularLinkedList()
for i in range(5):
    cllist1.append(i)
    cllist2.append((i+1)*5)
print('循环单链表 cllist1:', cllist1)
print('循环单链表 cllist2:', cllist2)
result = merge(cllist1, cllist2)
print('循环链表连接结果:', result)

算法执行结果如下:

循环单链表 cllist1: [0-->1-->2-->3-->4]
循环单链表 cllist2: [5-->10-->15-->20-->25]
循环单链表连接结果: [0-->1-->2-->3-->4-->5-->10-->15-->20-->25]

3. 循环双链表实现

类似于双向链表,接下来我们实现一个带有头结点的循环双链表类,并用头指针标识链表的开头,如果你还不了解双向链表,可以参考《双向链表及其操作实现》相关介绍。

3.1 循环双链表的基本操作

由于循环双链表类 DoubleLinkedCircularList 的代码中大部分与双向链表类 DoublyLinkedList 完全相同,因此我们借助继承机制来简化循环双链表的实现,DoublyLinkedList 类的实现参考《双向链表及其操作实现》

from DoublyLinkedList import DoublyLinkedList

class Node:
    def __init__(self, data=None):
        self.data = data
        self.next = None
        self.previous = None

    def __str__(self):
        return str(self.data)

循环双链表的初始化,不仅需要将头结点的 next 指针指向自身外,previous 指针同样需要指向自身:

class DoubleLinkedCircularList(DoublyLinkedList):
    def __init__(self, data=None):
        self.length = 0
        # 初始化头结点
        head_node = Node() 
        self.head = head_node
        self.head.next = self.head
        self.head.previous = self.head

定位元素位置,同样只需要修改遍历完成条件即可:

    def locate(self, value):
        count = 0
        current = self.head.next
        while current != self.head and current.data != value:
            count += 1
            current = current.next
        if current and current.data == value:
            return count
        else:
            raise ValueError("{} is not in sequential list".format(value))

相比于双链表,循环双链表的删除和插入操作更加方便,由于其循环特性的原因,并不需要考虑所删除或插入的结点是否是链表中的最后一个结点:

    def __delitem__(self, index):
        # 删除指定位置结点
        if index > self.length - 1 or index < 0:
            raise IndexError("DoubleLinkedCircularList assignment index out of range")
        else:
            count = -1
            current = self.head
            while count < index:
                current = current.next
                count += 1
            current.previous.next = current.next
            current.next.previous = current.previous
            self.length -= 1
            del current
    
    def del_value(self, value):
        # 删除链表中的指定元素
        current = self.head
        while current.next != self.head:
            if current.data == value:
                current.previous.next = current.next
                current.next.preious = current.previous
                self.length -= 1
                del current
                return
            else:
                current = current.next
        raise ValueError("The value provided is not present!")
    
    def insert(self, index, data):
        count = 0
        current = self.head
        # 判断插入位置的合法性
        if index > self.length or index < 0:
            raise IndexError("DoubleLinkedCircularList assignment index out of range")
        else:
            new_node = Node(data)
            while count < index:
                current = current.next
                count += 1
            new_node.next = current.next
            current.next.previous = new_node
            new_node.previous = current
            current.next = new_node
            self.length += 1

由于继承的缘故,并不需要在子类中重写相同的操作(类如查找指定元素等),最后我们重载一些其它有用的操作: 

  def append(self, data):
        new_node = Node(data)
        current = self.head
        while current.next != self.head:
            current = current.next
        new_node.next = current.next
        current.next.previous = new_node
        new_node.previous = current
        current.next = new_node
        self.length += 1

    def __str__(self):
        head = self.head
        s = "["
        current = self.head.next
        count = 0
        while current != head:
            count += 1
            s += str(current)
            current = current.next
            if count < self.length:
                s += '<-->'
        s += "]"
        return s

3.2 循环双链表应用示例

接下来,我们测试上述实现的循环双链表,以验证操作的有效性:

dlclist = DoubleLinkedCircularList()
# 在链表末尾追加元素
dlclist.append('apple')
dlclist.append('lemon')
# 在指定位置插入元素
dlclist.insert(0, 'banana')
dlclist.insert(2, 'orange')
print('循环双链表 dlclist 数据为:', dlclist)
print('循环双链表 dlclist 长度为:', len(dlclist))
# 查找指定元素,这里就是调用父类的locate方法
print('apple 在 dlclist 中序号为:', dlclist.locate('apple'))
# 获取指定位置元素
print('循环双链表 dlclist 第 0 个元素为:', dlclist[0])
# 修改数据元素
dlclist[0] = 'pear'
print('修改循环双链表 dlclist 后数据元素为:', dlclist)
del(dlclist[2])
print('修改循环双链表 dlclist 后数据:', dlclist)
# 删除指定元素
dlclist.del_value('pear')
print('删除 pear 后循环双链表 dlclist 数据:', dlclist)

上述程序输出如下所示:

循环双链表 dlclist 数据为: [banana<-->apple<-->orange<-->lemon]
循环双链表 dlclist 长度为: 4
apple 在 dlclist 中序号为: 1
循环双链表 dlclist 第 0 个元素为: banana
修改循环双链表 dlclist 后数据元素为: [pear<-->apple<-->orange<-->lemon]
修改循环双链表 dlclist 后数据: [pear<-->apple<-->lemon]
删除 pear 后循环双链表 dlclist 数据: [apple<-->lemon]

上一篇:django跳转页面传参的实现

栏    目:Python代码

下一篇:PyTorch使用GPU训练的两种方法实例

本文标题:Python数据结构之循环链表详解

本文地址:http://www.codeinn.net/misctech/205978.html

推荐教程

广告投放 | 联系我们 | 版权申明

重要申明:本站所有的文章、图片、评论等,均由网友发表或上传并维护或收集自网络,属个人行为,与本站立场无关。

如果侵犯了您的权利,请与我们联系,我们将在24小时内进行处理、任何非本站因素导致的法律后果,本站均不负任何责任。

联系QQ:914707363 | 邮箱:codeinn#126.com(#换成@)

Copyright © 2020 代码驿站 版权所有