循环链表

[!Tip] 本节源代码见Github链接🔗


概述

在单向链表、双向链表中,都用NULL值表示链表的结束,但是循环链表没有结束标志,表尾结点的后继指针指向表头结点。 循环链表的数据结构如下图所示: 循环链表数据结构.png

循环链表的类型声明方式如下所示:

【👉🏻>>点击展开查看代码】
        
/**  
 * 循环链表结点  
 *  
 * @className: CllNode  
 * @author: Max Solider  
 * @date: 2022-10-08 21:48  
 */
 public class CllNode {  

    /**  
     * 结点数据  
     */  
    private int data;  

    /**  
     * 后继结点指针  
     */  
    private CllNode next;  

    public CllNode(int data) {  
        this.data = data;  
    }  

    public int getData() {  
        return data;  
    }  

    public void setData(int data) {  
        this.data = data;  
    }  

    public CllNode getNext() {  
        return next;  
    }  

    public void setNext(CllNode next) {  
        this.next = next;  
    }  
}
        
    

优缺点及相关应用场景

优点
在存储上与单向链表相比没有突出的优点,但常与特殊的算法结合使用,应用在特定场景中,如轮训算法。

缺点
由于循环链表没有结束标记,在遍历链表时需要特别注意,容易进入死循环。

应用
常用于计算机资源的分配,比如通过轮训算法为进程分配CPU资源,必须确保在所有其他进程使用结束前,没有进程访问系统资源。还可用于实现栈和队列。


基本操作

对循环链表的基本操作包括:

  • 遍历计数或输出内容;
  • 插入新结点;
  • 删除结点。

统计循环链表的结点个数

核心思路: 与单向链表相同,都是依赖结点的后继结点来判断是否遍历结束。但循环链表需要判断结点的后继指针是否指向头结点,而非NULL。

实现代码

【👉🏻>>点击展开查看代码】
        
/**  
 * 遍历链表  
 *  
 * @author: Max Solider  
 * @date: 2022/10/8 21:59  
 * @param headNode  
 * @return int 链表长度  
 */  
static int listLength(CllNode headNode) {  
    if (headNode == null) {  
        return 0;  
    }  
    CllNode currentNode = headNode;  
    int count = 1;  
    do {  
        System.out.println("第" + count + "个结点的值是:" + currentNode.getData());  
        currentNode = currentNode.getNext();  
        count++;  
    } while (currentNode != headNode);  
    return count - 1;  
}
        
    

时间复杂度
时间复杂度是O(n),用于遍历一次链表。
空间复杂度
空间复杂度是O(1),用于存储临时变量。

输出循环链表的内容

和上述统计循环链表结点数量方法相同,不再赘述。

在循环链表的表尾插入结点

核心思路
在循环链表的表尾插入新结点,包含以下3步:

  1. 找到表尾结点;
  2. 将表尾结点的后继指针指向新结点;
  3. 将新结点的后继指针指向表头结点。 循环链表表尾插入结点.png

实现代码

【👉🏻>>点击展开查看代码】
        
/**  
 * 表尾插入新结点  
 *  
 * @param headNode  
 * @param newNode  
 * @return org.example.linkedlist.circular.CllNode  
 * @author: Max Solider  
 * @date: 2022/10/8 22:14  
 */
 CllNode insertTail(CllNode headNode, CllNode newNode) {  
    if (headNode == null) {  
        headNode = newNode;  
        headNode.setNext(newNode);  
        return headNode;  
    }  
    // 找到原表尾结点  
    CllNode oldTailNode = headNode;  
    do {  
        oldTailNode = oldTailNode.getNext();  
    } while (oldTailNode.getNext() != headNode);  
    // 插入新表尾结点  
    oldTailNode.setNext(newNode);  
    newNode.setNext(headNode);  
    return headNode;  
}
        
    

时间复杂度
时间复杂度是O(n),用于遍历链表。
空间复杂度
空间复杂度是O(1),用于保存临时变量。

在循环链表的表头插入结点

核心思路
在表头插入新结点,包含以下3步:

  1. 新结点的后继指针指向原表头结点;
  2. 表头指针指向新结点;
  3. 表尾结点的后继指针指向新结点。 表头插入结点.png

实现代码

【👉🏻>>点击展开查看代码】
        
/**  
 * 链表头插入新结点  
 *  
 * @author: Max Solider  
 * @date: 2022/10/8 22:29  
 * @param headNode  
 * @param newNode  
 * @return org.example.linkedlist.circular.CllNode  
 */
 CllNode insertHead(CllNode headNode, CllNode newNode) {  
    if (headNode == null) {  
        headNode = newNode;  
        headNode.setNext(newNode);  
        return headNode;  
    }  
    if (newNode == null) {  
        return headNode;  
    }  
    // 更新头结点  
    newNode.setNext(headNode);  
    headNode = newNode;  
    // 更新尾结点信息  
    CllNode tailNode = headNode.getNext();  
    do {  
        tailNode = tailNode.getNext();  
    } while (tailNode.getNext() != headNode.getNext());  
    tailNode.setNext(headNode);  
    return headNode;  
}
        
    

时间复杂度
时间复杂度是O(n),用于遍历链表。
空间复杂度
空间复杂度是O(1),用于存储临时变量。

删除循环链表中的最后一个结点

核心思路: 删除表尾结点,包含以下3步:

  1. 找到表尾结点;
  2. 找到表尾结点的前驱结点,并将它的后继指针指向表头结点;
  3. 移除表尾结点。 删除表尾结点.png

实现代码

【👉🏻>>点击展开查看代码】
        
/**  
 * 删除表尾结点  
 *  
 * @param headNode  
 * @return org.example.linkedlist.circular.CllNode  
 * @author: Max Solider  
 * @date: 2022/10/8 22:42  
 */
 CllNode deleteTail(CllNode headNode) {  
    if (headNode == null) {  
        return headNode;  
    }  
    if (headNode.getNext() == headNode) {  
        headNode.setNext(null);  
        headNode = null;  
        return headNode;  
    }  
    // 找到表尾结点的前驱结点  
    CllNode currentNode = headNode;  
    while (currentNode.getNext().getNext() != headNode) {  
        currentNode = currentNode.getNext();  
    }  
    // 更新表尾结点信息  
    currentNode.getNext().setNext(null);  
    // 更新表尾结点的前驱结点的信息  
    currentNode.setNext(headNode);  
    return headNode;  
}
        
    

时间复杂度
时间复杂度是O(n),用于遍历链表。
空间复杂度
空间复杂度是O(1),用于存储临时变量。

删除循环链表中的第一个结点

核心思路
删除头结点包含以下3步:

  1. 找到尾结点,将尾结点的后继指针指向原头结点的后继结点;
  2. 将头指针指向原头结点的后继结点;
  3. 移除原头结点。 删除表头结点.png

实现代码

【👉🏻>>点击展开查看代码】
        
/**  
 * 删除表头结点  
 *  
 * @author: Max Solider  
 * @date: 2022/10/8 22:54  
 * @param headNode  
 * @return org.example.linkedlist.circular.CllNode  
 */
 CllNode deleteHead(CllNode headNode) {  
    if (headNode == null) {  
        return headNode;  
    }  
    if (headNode.getNext() == headNode) {  
        headNode.setNext(null);  
        headNode = null;  
        return headNode;  
    }  
    // 更新表尾结点  
    CllNode tailNode = headNode;  
    while (tailNode.getNext() != headNode) {  
        tailNode = tailNode.getNext();  
    }  
    tailNode.setNext(headNode.getNext());  
    // 更新头指针  
    CllNode oldHead = headNode;  
    headNode = headNode.getNext();  
    // 更新原头结点  
    oldHead.setNext(null);  
    oldHead = null;  
    return headNode;  
}
        
    

时间复杂度
时间复杂度是O(n),用于遍历链表找到尾结点。
空间复杂度
空间复杂度是O(1),用于保存临时变量。


© MaxSolider all right reserved,powered by Gitbook文件修订时间: 2022-10-12 23:28:14

results matching ""

    No results matching ""