07. 循环链表

一、什么是循环链表

  循环链表是一种头尾相接的链表,即表中最后一个节点的指针域指向第一个节点,整个链表形成一个环。我们从表中任一节点出发均可找到其它节点。

循环列表

typedef int ElementType;

typedef struct LNode {
    ElementType Data;
    struct LNode * Next;
} Lnode, * List;

二、求表长

/**
 * @brief 求循环链表的表长
 * 
 * @param PtrL 循环链表
 * @return int 循环链表的长度
 */
int Length(List PtrL)
{
    List p = PtrL;
    int j = 1;

    if (PtrL == NULL)
    {
        return 0;
    }

    while (p->Next != PtrL)
    {
        p = p->Next;
        j++;                                                                    // 当前p指向的是第j个节点
    }

    return j;
}

三、查找元素

/**
 * @brief 按序号查找
 * 
 * @param PtrL 循环链表
 * @param K 要查找的序号
 * @return List 如果找到返回指向第K个的指针,否则返回NULL
 */
List FindKth(List PtrL, int K)
{
    List p = PtrL;                                                              // p指向表的第一个节点
    int i = 1;

    if (PtrL == NULL)
    {
        printf("链表为空!\n");
        return NULL;
    }

    if (i < 1 || i > Length(PtrL) + 1)
    {
        printf("查找的位置不合法\n");
        return PtrL;
    }

    while (p->Next != PtrL && i < K)
    {
        p = p->Next;
        i++;                                                                    // 当前p指向的是第j个节点
    }

    return (i == K) ? p : NULL;
}

/**
 * @brief 按值查找
 * 
 * @param PtrL 循环链表
 * @param X 要查找的元素
 * @return List 如果找到返回指向X的指针,否则返回NULL
 */
List FindElement(List PtrL, ElementType X)
{
    List p = PtrL;                                                              // p指向表的第一个节点

    if (PtrL == NULL)
    {
        printf("链表为空!\n");
        return NULL;
    }

    while (p->Data != X)
    {
        if (p->Next == PtrL)
        {
            return NULL;
        }
        p = p->Next;
    }

    return p;
}

四、插入元素

  1. 先构造一个新节点,用 s 执行。
  2. 再找到链表的第 i-1 个结点,用 p 指向。
  3. 然后修改指针,插入结点(p 之后插入新结点是 s)。

循环链表的插入

循环链表的表头插入

/**
 * @brief 插入一个元素
 * 
 * @param PtrL 循环链表
 * @param i 要插入的位置
 * @param X 要插入的元素
 * 
 * @return List 指向添加元素的循环链表的头指针
 */
List Insert(List PtrL, int i, ElementType X)
{
    List p = NULL, s = NULL;

    if (i < 1 || i > Length(PtrL) + 1)
    {
        printf("插入的位置不合法\n");
        return PtrL;
    }

    if (PtrL == NULL)
    {
        PtrL = (List)malloc(sizeof(LNode));
        PtrL->Data = X;
        PtrL->Next = PtrL;
        return PtrL;
    }
  
    if (i == 1)                                                                 // 新节点插入在表头
    {
        s = (List)malloc(sizeof(LNode));                                        // 申请新节点
        s->Data = X;                                                            // 新节点的数据域为表头
        s->Next = PtrL;                                                         // 新节点指向第二个节点
        p = FindKth(PtrL, Length(PtrL));                                        // 找到最后一个节点
        p->Next = s;                                                            // 最后一个节点指向新节点
        return s;
    }

    p = FindKth(PtrL, i - 1);                                                   // 找到第i-1个节点
    if (p == NULL)                                                              // 插入位置不合法
    {
        printf("插入的位置不合法\n");
        return PtrL;
    }

    s = (List)malloc(sizeof(LNode));                                            // 申请新节点
    s->Data = X;
    s->Next = p->Next;                                                          // 新节点指向p的下一个节点
    p->Next = s;                                                                // p指向新节点

    return PtrL;
}

五、删除元素

  1. 先找到链表的第 i 个节点,用 p 指向。
  2. 然后修改指针,删除 p 所指节点。
  3. 最后释放 p 所指节点的空间。

循环链表的删除

循环链表的表头删除

/**
 * @brief 删除一个元素
 * 
 * @param PtrL 链表
 * @param i 要删除元素的位置
 * @return List 指向删除后的循环链表的头指针
 */
List DeleteByPositon(List PtrL, int i)
{
    List p = NULL, s = NULL;

    if (i < 1 || i > Length(PtrL))
    {
        printf("删除的位置不合法\n");
        return PtrL;
    }

    if (i == 1)                                                                 // 要删除的节点是表头
    {
        p = FindKth(PtrL, Length(PtrL));                                        // 找到表尾
        s = PtrL;                                                               // s指向表头
        PtrL = PtrL->Next;                                                      // 删除表头,表头指向下一个节点
        p->Next = PtrL;                                                         // 表尾重新指向表头
        free(s);                                                                // 释放s
        return PtrL;
    }

    p = FindKth(PtrL, i - 1);                                                   // 找到第i-1个节点
    if (p == NULL)
    {
        printf("第%d个节点不存在!\n", i - 1);
    }
    else if (p->Next == PtrL)
    {
        printf("第%d个节点不存在!\n", i);
    }
    else
    {
        s = p->Next;                                                            // s指向第i个节点
        p->Next = s->Next;                                                      // p指向第i+1个节点
        free(s);                                                                // 释放s
    }
    return PtrL;
}
/**
 * @brief 按元素删除
 * 
 * @param PtrL 链表
 * @param X 要删除的元素
 * @return List 指向删除后的链表的头指针
 */
List DeleteByElement(List PtrL, ElementType X)
{
    List p = PtrL;
    LNode * node = FindElement(PtrL, X);
    LNode * next_node = NULL;

    if (p == NULL)
    {
        printf("链表为空!\n");
        return NULL;
    }

    if (node == NULL)
    {
        printf("要删除的元素不存在!\n");
        return PtrL;
    }

    next_node = node->Next;

    if (next_node != PtrL)
    {
        node->Data = next_node->Data;
        node->Next = next_node->Next;
        free(next_node);
        return PtrL;
    }
    else
    {
        return DeleteByPositon(PtrL, Length(PtrL));
    }
}

六、遍历链表

  由于循环链表中没有 NULL 指针,故遍历操作时,其终止条件就不像非循环链表那样判断 p 或 p->next 是否为空,而是判断它们是否头指针。

/**
 * @brief 遍历链表
 * 
 * @param PtrL 链表
 */
void PrintCircularLinkedList(List PtrL)
{
    List p = PtrL;

    if (p == NULL)
    {
        return;
    }
  
    while (1)
    {
        printf("%d ", p->Data);
        if (p->Next == PtrL)
        {
            break;
        }
        p = p->Next;
    }
    printf("\n");
}
posted @ 2023-06-27 18:57  星光樱梦  阅读(18)  评论(0编辑  收藏  举报