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

目录
  • 0. 学习目标
  • 1. 循环链表简介
  • 2. 循环单链表实现
    • 2.1 循环单链表的基本操作
    • 2.2 简单的实现方法
    • 2.3 循环单链表应用示例
    • 2.4 利用循环单链表基本操作实现复杂操作
  • 3. 循环双链表实现
    • 3.1 循环双链表的基本操作
    • 3.2 循环双链表应用示例

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]

以上就是Python数据结构之循环链表详解的详细内容,更多关于Python循环链表的资料请关注我们其它相关文章!

(0)

相关推荐

  • Python实现的单向循环链表功能示例

    本文实例讲述了Python实现的单向循环链表功能.分享给大家供大家参考,具体如下: 概述: 单向循环链表是指在单链表的基础上,表的最后一个元素指向链表头结点,不再是为空. 由图可知,单向循环链表的判断条件不再是表为空了,而变成了是否到表头. 操作 is_empty() 判断链表是否为空 length() 返回链表的长度 travel() 遍历 add(item) 在头部添加一个节点 append(item) 在尾部添加一个节点 insert(pos, item) 在指定位置pos添加节点 rem

  • python单向循环链表原理与实现方法示例

    本文实例讲述了python单向循环链表原理与实现方法.分享给大家供大家参考,具体如下: 单向循环链表 单链表的一个变形是单向循环链表,链表中最后一个节点的next域不再为None,而是指向链表的头节点. 操作 is_empty() 判断链表是否为空 length() 返回链表的长度 travel() 遍历 add(item) 在头部添加一个节点 append(item) 在尾部添加一个节点 insert(pos, item) 在指定位置pos添加节点 remove(item) 删除一个节点 se

  • python实现数据结构中双向循环链表操作的示例

    看此博客之前建议先看看B站的视频python数据结构与算法系列课程,该课程中未实现双向循环链表的操作,所以我按照该视频的链表思路实现了双向循环链表的操作,欢迎大家阅读与交流,如有侵权,请联系博主! 下面附上代码: class Node: def __init__(self, elem): self.elem = elem self.prev = None self.next = None class DoubleCycleLinkList: def __init__(self, node=Non

  • Python数据结构与算法之链表定义与用法实例详解【单链表、循环链表】

    本文实例讲述了Python数据结构与算法之链表定义与用法.分享给大家供大家参考,具体如下: 本文将为大家讲解: (1)从链表节点的定义开始,以类的方式,面向对象的思想进行链表的设计 (2)链表类插入和删除等成员函数实现时需要考虑的边界条件, prepend(头部插入).pop(头部删除).append(尾部插入).pop_last(尾部删除) 2.1 插入: 空链表 链表长度为1 插入到末尾 2.2 删除 空链表 链表长度为1 删除末尾元素 (3)从单链表到单链表的一众变体: 带尾节点的单链表

  • Python双向循环链表实现方法分析

    本文实例讲述了Python双向循环链表实现方法.分享给大家供大家参考,具体如下: 最近身边的朋友在研究用python来实现数据结构.遇到一个问题就是双向循环链表的实现,改指向的时候总是发蒙. 我自己尝实现了一个python的双向循环链表.附上代码,希望对大家有帮助. 如果不懂什么是双向循环链表的伙伴,需要补习一下数据结构的基础之后哦~~~ 在python当中 用一个类Node 来实现链表的节点,节点数据有三个变量: prev:前驱指针: 用于指向当前节点前一个节点 next: 后继指针  用于指

  • python/golang实现循环链表的示例代码

    循环链表就是将单链表的末尾指向其头部,形成一个环.循环链表的增删操作和单链表的增删操作 区别不大.只是增加时,需要考虑空链表增加第一个节点的特殊情况:删除时需考虑删除节点是头/尾节点,和链表中只有一个节点的特殊情况. golang实现: type Node struct { value int next *Node } type Circle struct { tail *Node lenth int } // 增加节点: func (c *Circle) add(value int) { ne

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

    目录 0. 学习目标 1. 循环链表简介 2. 循环单链表实现 2.1 循环单链表的基本操作 2.2 简单的实现方法 2.3 循环单链表应用示例 2.4 利用循环单链表基本操作实现复杂操作 3. 循环双链表实现 3.1 循环双链表的基本操作 3.2 循环双链表应用示例 0. 学习目标 循环链表 (Circular Linked List) 是链式存储结构的另一种形式,它将链表中最后一个结点的指针指向链表的头结点,使整个链表头尾相接形成一个环形,使链表的操作更加方便灵活.我们已经介绍了单链表和双向

  • Python数据结构之双向链表详解

    目录 0. 学习目标 1. 双向链表简介 1.1 双向链表介绍 1.2 双向链表结点类 1.3 双向链表优缺点 2. 双向链表实现 2.1 双向链表的初始化 2.2 获取双向链表长度 2.3 读取指定位置元素 2.4 查找指定元素 2.5 在指定位置插入新元素 2.6 删除指定位置元素 2.7 其它一些有用的操作 3. 双向链表应用 3.1 双向链表应用示例 3.2 利用双向链表基本操作实现复杂操作 0. 学习目标 单链表只有一个指向直接后继的指针来表示结点间的逻辑关系,因此可以方便的从任一结点

  • Python数据结构之栈详解

    目录 0.学习目标 1.栈的基本概念 1.1栈的基本概念 1.2栈抽象数据类型 1.3栈的应用场景 2.栈的实现 2.1顺序栈的实现 2.1.1栈的初始化 2.2链栈的实现 2.3栈的不同实现对比 3.栈应用 3.1顺序栈的应用 3.2链栈的应用 3.3利用栈基本操作实现复杂算法 0. 学习目标 栈和队列是在程序设计中常见的数据类型,从数据结构的角度来讲,栈和队列也是线性表,是操作受限的线性表,它们的基本操作是线性表操作的子集,但从数据类型的角度来讲,它们与线性表又有着巨大的不同.本节将首先介绍

  • Python数据结构之队列详解

    目录 0. 学习目标 1. 队列的基本概念 1.1 队列的基本概念 1.2 队列抽象数据类型 1.3 队列的应用场景 2. 队列的实现 2.1 顺序队列的实现 2.2 链队列的实现 2.3 队列的不同实现对比 3. 队列应用 3.1 顺序队列的应用 3.2 链队列的应用 3.3 利用队列基本操作实现复杂算法 0. 学习目标 栈和队列是在程序设计中常见的数据类型,从数据结构的角度来讲,栈和队列也是线性表,是操作受限的线性表,它们的基本操作是线性表操作的子集,但从数据类型的角度来讲,它们与线性表又有

  • Python数据结构之递归方法详解

    目录 1.学习目标 2.递归 2.1递归的基本概念 2.2递归的重要性 2.3递归三原则 2.4递归的应用 3.递归示例 3.1列表求和 3.2汉诺塔(Towers of Hanoi)问题 1.学习目标 递归函数是直接调用自己或通过一系列语句间接调用自己的函数.递归在程序设计有着举足轻重的作用,在很多情况下,借助递归可以优雅的解决问题.本节主要介绍递归的基本概念以及如何构建递归程序. 通过本节学习,应掌握以下内容: 理解递归的基本概念,了解递归背后蕴含的编程思想 掌握构建递归程序的方法 2.递归

  • python数据结构之链表详解

    数据结构是计算机科学必须掌握的一门学问,之前很多的教材都是用C语言实现链表,因为c有指针,可以很方便的控制内存,很方便就实现链表,其他的语言,则没那么方便,有很多都是用模拟链表,不过这次,我不是用模拟链表来实现,因为python是动态语言,可以直接把对象赋值给新的变量. 好了,在说我用python实现前,先简单说说链表吧.在我们存储一大波数据时,我们很多时候是使用数组,但是当我们执行插入操作的时候就是非常麻烦,看下面的例子,有一堆数据1,2,3,5,6,7我们要在3和5之间插入4,如果用数组,我

  • JavaScript数据结构链表知识详解

    最近在看<javascript数据结构和算法>这本书,补一下数据结构和算法部分的知识,觉得自己这块是短板. 链表:存储有序的元素集合,但不同于数组,链表中的元素在内存中不是连续放置的.每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成. 好处:可以添加或移除任意项,它会按需扩容,且不需要移动其他元素. 与数组的区别: 数组:可以直接访问任何位置的任何元素: 链表:想要访问链表中的一个元素,需要从起点(表头)开始迭代列表直到找到所需的元素. 做点小笔记. funct

  • C++调用Python基础功能实例详解

    c++调用Python首先安装Python,以win7为例,Python路径为:c:\Python35\,通过mingw编译c++代码. 编写makefile文件,首先要添加包含路径: inc_path += c:/Python35/include 然后添加链接参数: ld_flag += c:/Python35/libs/libpython35.a 在源文件中添加头文件引用: #include "Python.h" Python解释器需要进行初始化,完成任务后需要终止: void s

  • python实现单向链表详解

    本文研究的主要是Python中实现单向链表的相关内容,具体如下. 什么是链表 链表顾名思义就是-链 链表是一种动态数据结构,他的特点是用一组任意的存储单元存放数据元素.链表中每一个元素成为"结点",每一个结点都是由数据域和指针域组成的.跟数组不同链表不用预先定义大小,而且硬件支持的话可以无限扩展. 链表与数组的不同点: 数组需要预先定义大小,无法适应数据动态地增减,数据小于定义的长度会浪费内存,数据超过预定义的长度无法插入.而链表是动态增删数据,可以随意增加. 数组适用于获取元素的操作

  • 使用C++调用Python代码的方法详解

    一.配置python环境问题 1.首先安装Python(版本无所谓),安装的时候选的添加python路径到环境变量中 安装之后的文件夹如下所示: 2.在VS中配置环境和库 右击项目->属性->VC++目录 1)包含目录: Python安装路径/include 2)库目录: Python安装路径/libs 右击项目->属性->连接器->输入->附加依赖库 debug下: python安装目录/libs/python37_d.lib release下: python安装目录

随机推荐