【数据结构与算法Python描述】——单向循环链表简介、Python语言实现及应用

【数据结构与算法Python描述】——单向线性链表简介及其Python实现给出了链表的最简单形式——单向线性链表,以及其Python语言实现和相关应用。

实际上,基于单向线性链表的变形很多,而单向循环链表(本文简称“循环链表”)是其中一种较为常见的形式。

一、单向循环链表引入

在文章【数据结构与算法Python描述】——队列和双端队列简介及其高效率版本Python实现中,我们首次引入了循环的概念,但实际上列表的内存模型并不存在任何首尾相连的特征,只是通过使用取模运算可以使得类似循环的特点。

对于链表,特别是单向线性链表,通过对其进行一定的变形可以获得真正意义上的环状形态,即如下图所示,让原本引用None的单向线性链表尾结点的next域引用头结点。

在这里插入图片描述

实际上,对于循环链表,已经不存在所谓头和尾的概念了,所以一般地可将循环链表画成如下图所述形式。对于这一说法以及循环链表的模型类比理解,一个现实中的例子是上海的地铁4号线,这是一条以横贯方式连接上海所有主要地铁线的环形线。

在这里插入图片描述

尽管说循环链表不存在开始和结束的概念,还是有必要定义一个变量使其引用循环链表中的某一个结点,上图使用名为current的变量,只有这样才能通过类似current = current.next的语法遍历找到链表中的所有节点。

二、单向循环链表应用

对于循环链表,我们不会像单向线性链表一样去实现其ADT的所有方法,因为循环不存在所谓头(尾)部插入(删除)等概念,即使实现了也大同小异。更多地,我们将循环链表的一些特性来实现特定的功能,比如下面的循环调度算法。

1. 循环调度算法

循环调度算法可以实现这样一种功能:对于某一个对象集合,该算法可以一种循环的方式挨个获取对象中的每个元素,然后对该元素做某种处理。

典型地使用循环调度算法的案例是计算机CPU为电脑上的不同应用分配执行时间片,我们知道计算机上的CPU数量一般远小于正在运行的应用数量,计算机就是利用诸如循环调度算法实现CPU在不同应用之间轮流切换执行,从而达到看似多个应用同时执行的效果。

2. 循环链表实现队列

2.1 分析

对于循环调度算法的实现,可以使用一个普通的队列来实现,即循环执行下列三个步骤:

  • 使用e = queue.dequeue()获取应用e
  • 为元素e代表的应用服务;
  • 使用queue.enqueue(e)将已被服务的元素e重新入队。

在这里插入图片描述

实际上,如果使用【数据结构与算法Python描述】——队列和双端队列简介及其高效率版本Python实现中的ListQueue来实现循环调度算法,则在每次循环中都需要先对某一元素执行一次队头出队操作,再对同一个元素执行一次队尾入队操作。

如果使用循环链表的思想,那么头结点出队然后在尾结点处入队的动作只需一个方法(一般名为rotate())即可实现,如下图所示:

  • 先将队列的头尾结点链接在一起;
  • 然后在该方法中使用标记当前头和尾结点的变量,使得在一次循环中将当前头结点变为尾结点,当前头结点的下一个结点成为新的头结点。

在这里插入图片描述

2.2 ADT

由上述分析,如果将使用循环链表思想实现支持循环调度算法的队列命名为CircularQueue,则其ADT至少包含下列方法:

方法名称 方法描述
__len__() 重写该方法,使对于CircularQueue的对象可以使用len()方法
is_empty() 判断CircularQueue对象是否为空
first() 返回但并不删除队列的头部元素
rotate() 完成头部元素出队并从尾部入队操作
enqueue(e) 向当前队列的尾部加入新的元素
dequeue() 删除当前队列中在队头的元素

2.3 实现

为了实现上述ADT包含的方法,乍一看似乎两个指向结点的变量,即self._headself._tail。实际上,使用一个self._tail即可,因为总是可以通过self._tail.next来获取头结点的引用。

因此,使用下列两个变量即可实现CircularQueue类:

  • self._tail:指向尾结点的变量;
  • self._size:保存当前队列中元素数量的变量。
class Empty(Exception):
    """尝试对空队列进行删除操作时抛出的异常"""
    pass


class _Node:
    """节点类"""

    def __init__(self, element, next=None):
        """
        :param element: 节点代表的对象元素
        :param next: 下一个节点
        """
        self.element = element
        self.next = next
        

class CircularQueue:
    """支持循环调度算法的队列"""
    
    def __init__(self):
        """创建一个空的队列"""
        self._tail = None
        self._size = 0
        
    def __len__(self):
        """
        返回队列中元素个数
        :return: 队列中元素个数
        """
        return self._size
    
    def is_empty(self):
        """
        如果队列为空则返回True,否则返回False
        :return: 队列是否为空的标识
        """
        return self._size == 0
    
    def first(self):
        """
        返回但不删除队头结点的元素
        :return: 队头结点的元素
        """
        if self.is_empty():
            raise Empty('当前队列为空!')
        head = self._tail
        return head.element
    
    def rotate(self):
        """
        完成头部元素出队并从尾部入队操作
        :return: None
        """
        if self._size > 0:
            self._tail = self._tail.next  # 标识尾结点的变量指向当前头结点
            
    def enqueue(self, element):
        """
        向当前队列的尾部加入新的对象元素
        :param element: 待从尾部入队的对象元素
        :return: None
        """
        node = _Node(element)  # 元素封装成结点
        if self.is_empty():
            self._tail = node
            self._tail.next = node  # 链表成环
        node.next = self._tail.next  # 新结点和当前头结点建链
        self._tail.next = node  # 当前尾结点和新结点建链
        self._tail = node  # 新结点成为尾结点
        self._size += 1
        
    def dequeue(self):
        """
        删除当前队列中在队头的元素
        :return: 队头的元素
        """
        if self.is_empty():
            raise Empty('当前队列为空!')
        old_head = self._tail.next
        if self._size == 1:
            self._tail = None  # 将队列置为空
        else:
            self._tail.next = old_head.next  # 直接跳过旧的头结点
        self._size -= 1
        return old_head.element

对于上述代码,有两点值得记录的是:

  • 对于rotate()方法,在Python的collections模块中,其deque类有个功能类似的同名方法;
  • 对于enqueue(e)方法,由于出现了两次完全一样的语句self._tail = node,其代码可以修改成如下形式:
def enqueue(self, element):
    """
    向当前队列的尾部加入新的对象元素
    :param element: 待从尾部入队的对象元素
    :return: None
    """
    node = _Node(element)  # 元素封装成结点
    if self.is_empty():
        node.next = node  # 链表成环
    node.next = self._tail.next  # 新结点和当前头结点建链
    self._tail.next = node  # 当前尾结点和新结点建链
    self._tail = node  # 新结点成为尾结点
    self._size += 1

猜你喜欢

转载自blog.csdn.net/weixin_37780776/article/details/108180400