概述

        循环链表本质上也是一个单向或双向链表,但其最后一个节点的指针并不指向NULL,而是指向链表的第一个节点,从而形成一个闭合的环。这种结构使得在遍历链表时,可以从任意一个节点开始,并最终回到起始点。

        音乐播放软件一般都提供了重复播放的功能,这意味着:当播放列表中的最后一首歌曲播放完毕后,自动跳转至第一首歌曲继续播放。这种功能可以通过循环链表来轻松实现,其中每首歌曲代表链表中的一个节点。可以看到,循环链表非常适合需要重复访问元素的场景,比如:循环队列、时间轮等。

基本操作

        与单向链表类似,单向循环链表的每个节点包含两部分:一部分是存储数据的数据域,另一部分是指向下一个节点的指针。只不过最后一个节点的pNext指针不为NULL,而是指向头节点。

        对单向循环链表进行插入操作时,如果插入位置在中间,则与单向链表的插入逻辑基本相同。如果在头部插入新节点,则新节点的pNext指向当前头节点,尾部节点的pNext指向新节点,然后更新头指针指向新节点。如果链表为空,则新节点的pNext也指向自己。如果在尾部插入新节点,则首先找到链表的尾节点,即pNext指针指向头节点的那个节点;然后,让它的pNext指向新节点,新节点的pNext指向头节点。

        对单向循环链表进行删除操作时,根据要删除的节点位置,调整前一个节点的pNext指针指向被删节点的下一个节点。如果是删除头节点,则需要更新头指针。

        对单向循环链表进行遍历操作时,可以从任意节点开始,沿着pNext指针移动,直到再次到达起始节点为止。

        具体的实现,可参考下面的示例代码。双向循环链表与双向链表的区别,与上面基本类似,这里就不再赘述了。

#include <iostream>
using namespace std;

struct Node
{
    int nData;
    Node* pNext;
};

// 创建新节点
Node* CreateNode(int nData)
{
    Node* pNode = new Node();
    pNode->nData = nData;
    pNode->pNext = NULL;
    return pNode;
}

// 查找前一个节点
Node* FindPrevious(Node* pHead, Node* pTarget)
{
    if (pHead == NULL || pTarget == NULL)
    {
        return NULL;
    }

    Node* pCurrent = pHead;
    while (pCurrent->pNext != pTarget)
    {
        pCurrent = pCurrent->pNext;
        if (pCurrent == pHead)
        {
            // 循环了一圈没找到
            return NULL;
        }
    }

    return pCurrent;
}

// 头部插入
void InsertAtHead(Node*& pHead, int nData)
{
    Node* pNode = CreateNode(nData);
    if (pHead == NULL)
    {
        pHead = pNode;
        // 循环指向自己
        pNode->pNext = pHead;
    }
    else
    {
        // 新节点的pNext指向当前头节点
        pNode->pNext = pHead;
        // 尾部节点的pNext指向新节点
        Node *pPre = FindPrevious(pHead, pHead);
        if (pPre != NULL)
        {
            pPre->pNext = pNode;
        }

        pHead = pNode;
    }
}

// 尾部插入
void InsertAtTail(Node*& pTail, int nData)
{
    Node* pNode = CreateNode(nData);
    if (pTail == NULL)
    {
        pTail = pNode;
        pNode->pNext = pTail;
    }
    else
    {
        pNode->pNext = pTail->pNext;
        pTail->pNext = pNode;
        // 更新尾节点为新节点
        pTail = pNode;
    }
}

// 指定位置后插入
bool InsertAfter(Node* pNode, int nData)
{
    if (pNode == NULL)
    {
        return false;
    }

    Node* pNewNode = CreateNode(nData);
    pNewNode->pNext = pNode->pNext;
    pNode->pNext = pNewNode;
    return true;
}

// 删除操作
bool DeleteNode(Node*& pHead, Node* pDelNode)
{
    if (pHead == NULL || pDelNode == NULL)
    {
        return false;
    }

    // 只有一个节点的情况
    if (pDelNode->pNext == pDelNode)
    {
        delete pDelNode;
        pHead = NULL;
        return true;
    }

    // 找到前一个节点
    Node* pPrev = FindPrevious(pHead, pDelNode);
    if (pPrev == NULL)
    {
        return false;
    }

    // 更新指针
    pPrev->pNext = pDelNode->pNext;

    // 如果删除的是头节点,更新head指针
    if (pDelNode == pHead)
    {
        pHead = pDelNode->pNext;
    }

    delete pDelNode;
    return true;
}

// 遍历操作
void TraverseList(Node* pNode)
{
    if (pNode == NULL)
    {
        cout << "List is empty" << endl;
        return;
    }

    Node* pCurrent = pNode;
    do
    {
        cout << pCurrent->nData << " -> ";
        pCurrent = pCurrent->pNext;
    } while (pCurrent != pNode);

    cout << "(back to head)" << endl;
}

int main()
{
    Node* pHead = NULL;
    Node* pTail = NULL;
    InsertAtTail(pTail, 77);
    pHead = pTail;
    InsertAtTail(pTail, 88);
    InsertAtHead(pHead, 66);
    InsertAfter(pTail, 99);

    // 输出:66 -> 77 -> 88 -> 99 -> (back to head)
    TraverseList(pHead);
    return 0;
}

总结

        循环链表是一种非常有用的数据结构,它通过改变传统链表最后一个节点的指针指向,形成了一个闭环,使得数据的循环访问变得简单而高效。

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐