线性表文档之循环单链表

循环单链表

定义

概念

循环单链表是在单链表的基础上,将链表最后一个结点的 next 指针域指向了链表的第一个结点(如果单链表是带头结点的则最后一个结点的 next 指针域指向头结点;如果单链表是不带头结点的则最后一个结点的 next 指针域指向开始结点)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BbKQEnlJ-1649167121900)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220403174529842.png)]

结构体

循环单链表的链表结点同单链表的结点一样,都是由一个数据域和指针域组成。其中数据域存储当前节点的数据值,而指针域存储当前节点的后继节点的地址。如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cJXwWstm-1649167121902)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220403175002893.png)]

循环单链表结点结构体代码如下:

/**
 * 循环单链表的节点
 */
typedef struct CLNode {
    /**
     * 链表节点的数据域
     */
    int data;
    /**
     * 链表节点的指针域,指向后继节点
     */
    struct CLNode *next;
} CLNode;

特点

单链表有的特点,循环单链表也有:

  • 链式存储线性表时,不需要使用地址连续的存储元素,即不要求逻辑上相邻的元素在物理位置上也相邻。
  • 单链表由于是通过『链』建立起的数据元素之间的逻辑关系,插入和删除操作不需要移动元素,只需要修改链结点指针域的指向。
  • 因为单链表的元素是离散地分布在存储空间中,所以单链表不能随机存取,如果要找到某个数据元素,最坏情况下需要遍历整个单链表。
  • 单链表存储数据不需要大量连续存储空间,但单链表结点除了存储数据值之外,还附加有指针域,就存在浪费存储空间的缺点。

循环单链表独有的特点:

  • 最后一个结点的指针域指向链表的第一个结点(如果是带头结点的循环单链表则指向头结点;如果是不带头结点的循环单链表则指向开始结点)。
  • 循环单链表可以实现从任一结点出发访问链表中的任何结点,而单链表只能从任一结点出发后访问这个结点本身及其之后的所有结点。

基本操作

注:如无特殊说明,下面关于链表的所有操作都是基于带头结点的链表。完整代码请参考:

概述

注:下面都是 C 语言代码,所以如果要对链表进行删除或新增操作,链表参数都是双指针。如果要使用 C++ 的引用则改成 *&。一般如果是考研建议使用 & 引用,避免双指针。

循环单链表的常见操作如下:

  • void init(CLNode **list):初始化带头结点的循环单链表。其中 list 是未初始化的循环单链表。
  • CLNode *createByHead(CLNode **list, int nums[], int n):通过头插法常见循环单链表。其中 list 是未初始化的循环单链表;nums 是待批量插入到循环单链表中的数组;n 是数组长度。返回创建成功的循环单链表。
  • CLNode *createByTail(CLNode **list, int nums[], int n):通过尾插法常见循环单链表。其中 list 是未初始化的循环单链表;nums 是待批量插入到循环单链表中的数组;n 是数组长度。返回创建成功的循环单链表。
  • int insert(CLNode **list, int i, int ele):在循环单链表第 i 个位置插入值为 ele 的新结点。其中 list 是循环单链表;i 是指定结点位置,从 1 开始;ele 是待插入元素值。如果插入成功则返回 1,否则返回 0。
  • void insertFirst(CLNode **list, int ele):在循环单链表的头部插入新元素。其中 list 是循环单链表;ele 是新元素值。
  • void insertLast(CLNode **list, int ele):在循环单链表的尾部插入新元素。其中 list 是循环单链表;ele 是新元素值。
  • int removeByNum(CLNode **list, int i, int *ele):删除循环单链表中第 i 个位置的结点,并将被删结点的值保存到 ele 中。其中 list 是循环单链表;i 是循环单链表中的结点序号,从 1 开始;ele 用来保存被删结点的数据值。如果删除成功则返回 1,否则返回 0 表示删除失败。
  • void removeByEle(CLNode **list, int ele):删除循环单链表中第一个等于指定值 ele 的结点。其中 list 是循环单链表;ele 是指定值。
  • void removeFirst(CLNode **list, int *ele):删除循环单链表第一个结点。其中 list 是循环单链表;ele 用来保存被删第一个结点的数据值。
  • void removeLast(CLNode **list, int *ele):删除循环单链表最后一个结点。其中 list 是循环单链表;ele 用来保存被删的最后一个结点的数据值。
  • CLNode *findByNum(CLNode *list, int i):查找循环单链表中第 i 个结点。其中 list 是循环单链表;i 是结点序号,从 1 开始。如果查找成功则返回该结点,否则返回 NULL 表示查找失败。
  • CLNode *findByEle(CLNode *list, int ele):查找循环单链表中第一个值等于 ele 的结点。其中 list 是循环单链表;ele 是指定值。如果查找成功则返回该结点,否则返回 NULL 表示查找失败。
  • int size(CLNode *list):获取循环单链表的长度,即结点个数。其中 list 是循环单链表。返回循环单链表的结点个数。
  • int isEmpty(CLNode *list):判断循环单链表是否为空。其中 list 是循环单链表。如果循环单链表为空则返回 1,否则返回 0。
  • void clear(CLNode **list):清空循环单链表。其中 list 是循环单链表。
  • void print(CLNode *list):打印循环单链表所有结点。其中 list 是循环单链表。

init

初始化循环单链表。注意带头结点的循环单链表和不带头结点的循环单链表初始化不一样。

在这里插入图片描述

实现步骤:

  • 如果是带头结点的循环单链表,则创建头结点,为其分配空间,并将头结点的 next 指针域指向头结点本身。
  • 如果是不带头结点的循环单链表,则将头指针 head 指向 NULL

实现代码如下:

/**
 * 对循环单链表进行初始化
 * @param list 循环单链表
 */
void init(CLNode **list) {
    // 为链表的头节点分配空间
    *list = (CLNode *) malloc(sizeof(CLNode));
    // 指定头节点的指针域,将其指向自身,表示是循环单链表,而普通单链表是指向 null
    (*list)->next = *list;// 注意,指向头结点本身
}

createByHead

通过头插法批量插入元素然后创建一个非空循环单链表。所谓的头插法就是每次插入一个新元素都是插入在第一个结点的位置,无论循环单链表是否带有头节点。

在这里插入图片描述

nums=[1, 2, 3, 4, 5]; n=5 为例创建循环单链表:

在这里插入图片描述

实现步骤:

  • 对链表进行初始化。注意,是带头结点的循环单链表。
  • 循环遍历数组中的每个元素,然后根据数组元素创建单链表结点,创建新节点时将数组元素值赋给结点数据域,将新节点的指针域指向 NULL
  • 将创建的新节点的 next 指针域指向单链表的第一个节点,然后将单链表的头结点的 next 指针指向新节点。

实现代码如下:

/**
 * 通过头插法创建循环单链表
 * @param list 已经初始化后的循环单链表
 * @param nums 待插入到单链表中的数据数组
 * @param n 数组长度
 * @return 创建成功的循环单链表
 */
CLNode *createByHead(CLNode **list, int nums[], int n) {
    // 1.初始化循环单链表,即创建循环单链表的头结点。也可以直接调用 init 函数来初始化
    // 1.1 为头结点分配空间
    *list = (CLNode *) malloc(sizeof(CLNode));
    // 1.2 修改头结点的 next 指针,将其指向自己,而普通单链表是指向 null
    (*list)->next = *list;// 注意,指向头结点本身

    // 2.遍历 nums 数组中所有元素,将其添加到链表中,完成链表的创建
    for (int i = 0; i < n; i++) {
        // 2.1 创建新节点
        // 2.1.1 给新节点分配空间
        CLNode *newNode = (CLNode *) malloc(sizeof(CLNode));
        // 2.1.2 指定新节点的数据域
        newNode->data = nums[i];
        // 2.1.3 将新节点的指针域置为 null,指向空
        newNode->next = NULL;

        // 2.2 将新节点插入到链表中,但是插入到头结点的后面
        // 2.2.1 将新节点的 next 指针指向链表第一个节点,此时完成了新节点和链表第一个节点链接
        newNode->next = (*list)->next;
        // 2.2.2 将链表头节点的 next 指针指向新节点,此时完成了链表头节点和新节点的链接
        (*list)->next = newNode;
    }

    return *list;
}

createByTail

通过尾插法创建循环单链表。所谓的尾插法就是每次将新节点插入到链表的尾部。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-krV8aftQ-1649167121922)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220403232355083.png)]

nums=[1, 2, 3, 4, 5]; n=5 为例使用尾插法创建循环单链表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sZ8h3CUn-1649167121934)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220403223745581.png)]

实现步骤:

  • 初始化单链表。
  • 设置一个变量来记录单链表的尾结点 tailNode,初始为单链表的头结点。
  • 循环遍历数组中的每个元素,然后根据数组元素创建单链表结点,创建新节点时将数组元素值赋给结点数据域,将新节点的指针域指向 NULL
  • 将尾结点 tailNodenext 指针指向新结点 newNode,然后将新结点 newNodenext 指针指向循环单链表的头结点 head,最后将新结点 newNode 记录为新的尾节点 tailNode

实现代码如下:

/**
 * 通过尾插法创建循环单链表
 * @param list  循环单链表
 * @param nums 待插入到单链表中的数据数组
 * @param n 数组长度
 * @return 创建成功的循环单链表
 */
CLNode *createByTail(CLNode **list, int nums[], int n) {
    // 1.初始化循环单链表,即创建循环单链表的头结点。也可以直接调用 init 函数来初始化
    // 1.1 为头结点分配空间
    *list = (CLNode *) malloc(sizeof(CLNode));
    // 1.2 修改头结点的 next 指针,将其指向自己,而普通单链表是指向 null
    (*list)->next = *list;// 注意,指向头结点本身

    // 保存链表的尾节点,初始为链表的头节点,即空链表的尾节点就是链表的头节点
    CLNode *tailNode = *list;

    // 2.循环数组 nums 中所有数据,插入到单链表中
    for (int i = 0; i < n; i++) {
        // 2.1 创建新节点
        // 2.1.1 给新节点分配空间
        CLNode *newNode = (CLNode *) malloc(sizeof(CLNode));
        // 2.1.2 指定新节点的数据域
        newNode->data = nums[i];
        // 2.1.3 将新节点的指针域置为 null,指向空
        newNode->next = NULL;

        // 2.2 将新节点追加到链表的尾部
        // 2.2.1 将链表原尾节点的 next 指针指向新节点,即让新节点成为链表的尾节点
        tailNode->next = newNode;
        // 2.2.2 然后将新节点(即链表新的尾节点)指向链表的头节点
        newNode->next = *list;
        // 2.2.3 然后将 tailNode 指向新节点(即新的尾节点)
        tailNode = newNode;
    }

    // 3.返回创建成功的链表
    return *list;
}

insert

在循环单链表的第 i 个位置插入值为 ele 的新结点。以 list=[1, 2, 3, 4, 5]; i=3; ele=66 为例如图:
在这里插入图片描述

实现步骤:

  • 参数校验,位置 i 的范围必须在 [1, length]。否则超出范围就判定插入失败。
  • 声明一个变量 node 保存循环单链表中的结点,初始为链表的开始结点;声明一个变量 pre 保存结点 node 的前驱结点,为了插入。
  • 通过遍历循环单链表(注意循环结束条件),找到第 i 个结点 node 和 第 i-1 个结点 pre
  • 然后创建一个新结点 newNode,为其分配数据域和指针域。将新结点 newNode 插入到第 i-1 个结点 pre 和第 i 个结点 node 之间。
  • 最后完成插入,返回 1 表示插入成功。

实现代码如下:

/**
 * 向链表的第 i 个位置插入新节点
 * 注意,如果是空链表(已经初始化),插入第 1 个节点会失败
 * @param list 循环单链表
 * @param i 序号,从 1 开始
 * @param ele 新节点的数据值
 * @return 如果插入成功则返回 1,否则返回 0
 */
int insert(CLNode **list, int i, int ele) {
    // 0.参数校验
    if (i < 0 || i > size(*list)) {
        return 0;
    }

    // 1.声明一些变量
    // 1.1 链表的第一个节点
    CLNode *node = (*list)->next;// 链表的第一个节点
    // 1.2 保存前驱节点,初始为链表的头节点
    CLNode *pre = *list;// 保存前驱节点
    // 1.3 计数器,记录当前节点是链表的第几个节点
    int count = 0;

    // 注意,使用下面的方式插入第 1 个节点时,如果链表为空则会失败
    // 找到第 i 个节点的前驱节点(即第 i-1 个节点),因为插入节点必须知道它的前驱节点
    // 2.遍历链表所有节点,找到第 i 个节点,然后插入新节点
    while (node != *list) {
        // 2.1 计数器加 1,表示已经迭代一个节点了
        count++;
        // 2.2 找到第 i 个节点,那么就可以插入新节点了
        if (count == i) {
            // 2.2.1 创建新节点
            // 2.2.1.1 给新节点分配空间
            CLNode *newNode = (CLNode *) malloc(sizeof(CLNode));
            // 2.2.1.2 给新节点的数据域赋值
            newNode->data = ele;
            // 2.2.1.3 给新节点的指针域赋为 null
            newNode->next = NULL;

            // 2.2.2 将新节点插入到链表中
            // 2.2.2.1 将第 i-1 个节点的 next 指针指向新节点
            pre->next = newNode;
            // 2.2.2.2 将新节点的 next 指针指向原第 i 个节点
            newNode->next = node;

            // 2.2.3 结束循环,跳出程序
            break;
        }
        // 2.3 保存当前节点为前驱节点
        pre = node;
        // 2.4 继续下一个节点
        node = node->next;
    }

    return 1;
}

insertFirst

在循环单链表的开始结点位置插入新结点。

在这里插入图片描述

实现步骤:

  • 创建新结点 newNode,为新结点分配存储空间,并指明数据域和指针域。
  • 然后将新结点插入到链表的头部。

实现代码如下:

/**
 * 在循环单链表的头部插入新节点
 * @param list 循环单链表
 * @param ele 待插入的元素
 */
void insertFirst(CLNode **list, int ele) {
    // 1.创建新节点
    // 1.1 为新节点分配空间
    CLNode *newNode = (CLNode *) malloc(sizeof(CLNode));
    // 1.2 为新节点的数据域指定内容
    newNode->data = ele;
    // 1.3 将新节点的指针域指向 null
    newNode->next = NULL;

    // 2.将新节点插入到链表的头部,成为新的链表第一个节点
    // 2.1 将新节点的 next 指针指向原链表的第一个节点
    newNode->next = (*list)->next;
    // 2.2 将头节点的 next 指针指向新节点,即让新节点成为链表的第一个节点
    (*list)->next = newNode;
}

insertLast

在循环单链表的尾部插入新结点。
在这里插入图片描述

实现步骤:

  • 找到循环单链表的尾结点 tailNode
  • 然后创建新结点 newNode 为其分配存储空间,并指定数据域和指针域。最后将新结点 newNode 插入到尾结点 tailNode 的后面,注意新尾结点的 next 指针必须指向链表的头结点,才能构成一个循环。

实现代码如下:

/**
 * 向循环单链表末尾追加一个新节点
 * @param list 循环单链表
 * @param ele 新节点的数据值
 */
void insertLast(CLNode **list, int ele) {
    // 1.先找到链表的最后一个节点,即尾节点
    // 1.1 链表的第一个节点
    CLNode *node = (*list)->next;
    // 1.2 通过循环找到链表的最后一个节点,注意这里循环结束的条件是 node.next!=list,在循环结束之后,node 就是链表的尾节点
    while (node->next != *list) {
        node = node->next;
    }

    // 2.创建新节点然后插入到链表的尾部
    // 2.1 创建新节点
    // 2.1.1 为新节点分配空间
    CLNode *newNode = (CLNode *) malloc(sizeof(CLNode));
    // 2.1.2 为新节点的数据域指定内容
    newNode->data = ele;
    // 2.1.3 将新节点的指针域指向 null
    newNode->next = NULL;

    // 2.2 将新节点追加到循环单链表的末尾
    // 2.2.1 将链表的原尾节点的 next 指针指向新节点,已经完成了新节点链接到链表中
    newNode->next = node->next;// 其实 node->next 就是链表的头结点,所以 newNode->next=*list 也是可以的
    // 2.2.2 将新节点的 next 指针指向链表的头节点,完成了尾节点与头节点的链接
    node->next = newNode;
}

removeByNum

删除循环单链表第 i 个结点,并将被删结点的值保存到 ele 中。以 list=[11, 22, 33, 44, 55]; i=3 为例如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KiJwZnQE-1649167121943)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220403234829266.png)]

实现步骤:

  • 参数校验,序号 i 必须在 [1, length] 范围内,否则参数不合法则返回 0 表示删除失败。
  • 声明两个变量 prenode,其中 node 记录单链表中的结点,从链表第一个结点开始(即头结点的后继结点);而 pre 记录结点 node 的前驱结点。
  • 通过循环遍历找到第 i 个结点 node 和第 i-1 个结点 pre
  • 最后删除第 i 个结点(即 pre->next=node->next),用 ele 保存被删结点的值,然后调用 free 函数释放结点空间。

实现代码如下:

/**
 * 删除循环单链表中第 i 个节点
 * @param list 循环单链表
 * @param i 指定序号,从 1 开始
 * @param ele 保存被删除节点的数据域
 * @return 如果删除成功则返回 1,否则返回 0
 */
int removeByNum(CLNode **list, int i, int *ele) {
    // 0.参数校验
    if (i < 0 || i > size(*list)) {
        return 0;
    }

    // 1.声明一些变量
    // 1.1 链表的第一个节点
    CLNode *node = (*list)->next;
    // 1.2 保存前驱节点,初始为链表的头节点
    CLNode *pre = *list;
    // 1.3 计数器,记录当前是链表的第几个节点
    int count = 0;

    // 2.遍历链表,寻找第 i 个节点,通过循环计数的方式来找到
    while (node != *list) {
        // 2.1 计数器加 1
        count++;
        // 2.2 找到待删除节点,即比较计数器与参数值是否相等,如果相等则删除节点
        if (count == i) {
            // 2.2.1 删除 node 节点,即将第 i-1 个节点的 next 指针指向第 i+1 个节点,这样就删除了第 i 个节点
            pre->next = node->next;
            // 2.2.2 保存被删除节点的数据域值
            *ele = node->data;
            // 2.2.3 释放节点空间
            free(node);
            // 2.2.4 删除成功则返回 1
            return 1;
        }
        // 2.3 保存当前节点为前驱节点
        pre = node;
        // 2.4 继续下一个节点
        node = node->next;
    }

    return 0;
}

removeByEle

删除循环单链表中第一个值为 ele 的结点,并用 ele 来保存被删结点的数据值。以 list=[11, 22, 33, 44, 55]; ele=33 为例如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lte0U9wZ-1649167121951)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220403234953959.png)]

实现步骤:

  • 声明一个变量 node 来记录循环单链表中的结点,直到找到第 i 个结点,从链表的第一个结点(即头结点的后继结点)开始;再声明一个变量 pre 来记录 node 结点的前驱结点,便于删除结点。
  • 遍历循环,找到循环单链表中第一个值为 ele 的结点,如果找到则删除该结点。

实现代码如下:

/**
 * 根据值删除循环单链表元素
 * @param list 循环单链表
 * @param ele 被删除节点的数据域值
 */
void removeByEle(CLNode **list, int ele) {
    // 0.声明一些变量
    // 0.1 链表的第一个节点,其实主要用于遍历链表
    CLNode *node = (*list)->next;
    // 0.1 保存值等于 ele 的节点的前驱节点
    CLNode *pre = *list;

    // 1.循环链表,查找节点值等于 ele 的节点,然后进行删除
    while (node != *list) {
        // 1.1 如果找到值等于 ele 的节点
        if (node->data == ele) {
            // 1.1.1 删除 node 节点,即将第 i-1 个节点的 next 指针指向第 i+1 个节点,这样就删除了第 i 个节点
            pre->next = node->next;
            // 1.1.2 释放节点空间
            free(node);
            // 1.1.3 跳出循环
            break;
        }
        // 1.2 保存当前节点为前驱节点
        pre = node;
        // 1.3 继续链表的下一个节点
        node = node->next;
    }
}

remvoeFirst

删除循环单链表中的开始结点,并用 ele 保存开始结点的数据值。以 list=[11, 22, 33, 44, 55] 为例如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kr3YAthM-1649167121952)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220403235937032.png)]

实现步骤:

  • 即将头结点的 next 指针指向开始结点 node 的后继结点。

注:代码待完善,当循环单链表为空的时候虽然不会报错,但是由于会释放头结点空间,所以导致循环单链表结构出问题。

实现代码如下:

/**
 * 删除链表的第一个节点
 * @param list 循环单链表
 * @param ele 被删除的节点的数据域值
 */
void removeFirst(CLNode **list, int *ele) {
    // 链表的第一个节点
    CLNode *node = (*list)->next;
    // 删除第一个节点,即将链表的头节点的 next 指针指向原链表的第二个节点(即让原链表第二个节点成为新的链表第一个节点)
    (*list)->next = node->next;
    // 保存被删除节点的数据域值
    *ele = node->data;
    // 释放节点空间
    free(node);
}

removeLast

删除循环单链表的尾结点,并用 ele 保存被删结点的数据值。以 list=[11, 22, 33, 44, 55] 为例如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xVtRXrDK-1649167121953)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220404002857534.png)]

实现步骤:

  • 声明两个变量 prenode,其中 node 记录循环单链表中的结点,初始为循环单链表的开始结点(即头结点的后继结点);而 pre 记录 node 结点的前驱结点。其实 node 就是为了最后记录循环单链表的尾结点,pre 记录尾结点的前驱结点,便于删除。
  • 通过遍历循环,找到循环单链表的尾结点 node 和尾结点的前驱结点 pre
  • 删除循环单链表的尾结点 node,用 ele 保存被删结点的数据值,然后释放存储空间。

实现代码如下:

/**
 * 删除循环单链表的尾节点
 * @param list 循环单链表
 * @param ele 被删除节点的数据域值
 */
void removeLast(CLNode **list, int *ele) {
    // 0.声明一些变量
    // 0.1 链表的第一个节点
    CLNode *node = (*list)->next;
    // 0.2 记录尾节点的前驱节点
    CLNode *pre = *list;

    // 1.找到链表的尾节点和尾节点的前驱节点
    while (node->next != *list) {// 注意循环结束的条件是 node.next!=list,当循环结束之后 node 就是链表的尾节点
        // 1.1 保存当前节点为前驱节点
        pre = node;
        // 1.2 继续链表的下一个节点
        node = node->next;
    }

    // 2.删除链表的最后一个节点
    // 2.1 删除节点 node
    pre->next = node->next;// 其实等价于 pre->next=*list
    // 2.2 保存被删除节点的数据域值
    *ele = node->data;
    // 2.3 释放节点空间
    free(node);
}

findByNum

查找循环单链表中第 i 个结点。以 list=[11, 22, 33, 44, 55]; i=3 为例如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-piBQFjmh-1649167121954)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220404114454991.png)]

实现步骤:

  • 参数校验,序号 i 的范围必须在 [1, length] 范围内。否则参数不合法,则返回 NULL
  • 声明一个变量 count 来记录已经遍历到第几个结点了,直到遇到 count==i,则表示找到第 i 个结点,然后返回。

实现代码如下:

/**
 * 通过序号在链表中检索节点
 * @param list 循环单链表
 * @param i 序号,从 1 开始
 * @return 如果找到第 i 个节点则返回,否则返回 NULL
 */
CLNode *findByNum(CLNode *list, int i) {
    // 0.参数校验
    if (i < 1 || i > size(list)) {
        return NULL;
    }

    // 1.声明一些变量
    // 1.1 链表的第一个节点
    CLNode *node = list->next;
    // 1.2 计数器,记录当前正在遍历的节点是链表的第几个节点
    int count = 0;

    // 2.遍历链表所有节点,找到第 i 个节点
    while (node != list) {
        // 2.1 计数器加 1,表示已经迭代了一个节点
        count++;
        // 2.2 比较计数器的值与参数值是否相等,如果相等则表示找到了第 i 个节点则返回该节点
        if (count == i) {
            return node;
        }
        // 2.3 继续下一个节点
        node = node->next;
    }
    return NULL;
}

findByEle

查找循环单链表中第一个值为 ele 的结点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tjTAWB3I-1649167121957)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220404115054797.png)]

实现步骤:

  • 从头到尾扫描单链表,比较结点 node 的数据值是否等于 ele,如果相等则返回该结点,否则继续判断链表的下一个结点。

实现代码如下:

/**
 * 通过数据值在链表中检索节点
 * @param list 循环单链表
 * @param ele 指定数据值
 * @return 如果找到则返回对应值的节点,如果没有找到则返回 NULL
 */
CLNode *findByEle(CLNode *list, int ele) {
    // 链表的第一个节点
    CLNode *node = list->next;

    // 1.遍历链表所有节点,比较节点的值与输入的参数值是否相等,如果相等则返回该节点
    while (node != list) {
        // 1.1 比较当前节点的数据域是否与参数值相等,如果相等则表示找到指定值的节点,那么返回即可
        if (node->data == ele) {
            return node;
        }
        // 1.2 继续下一个节点
        node = node->next;
    }
    return NULL;
}

size

获得循环单链表的长度,即循环单链表中的结点个数,不包括头结点。以 list=[11, 22, 33, 44, 55] 为例如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9pZEgoDn-1649167121960)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220404120035775.png)]

实现步骤:

  • 从头到尾扫描循环单链表所有结点,用变量 count 来记录所有结点个数。

实现代码如下:

/**
 * 统计循环单链表的节点个数
 * @param list 循环单链表
 * @return 链表的长度
 */
int size(CLNode *list) {
    // 计数器,记录链表的节点个数
    int count = 0;
    // 链表的第一个节点
    CLNode *node = list->next;
    // 遍历链表所有节点,统计个数,注意结束循环的条件
    while (node != list) {
        // 统计个数
        count++;
        // 继续下一个节点
        node = node->next;
    }
    // 返回节点个数
    return count;
}

isEmpty

判断循环单链表是否为空。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4po6wPJc-1649167121967)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220404120824423.png)]

实现步骤:

  • 如果是带头结点的循环单链表,则直接判断 head->next==head;如果是不带头结点的循环单链表,则直接判断 head==NULL 即可。

实现代码如下:

/**
 * 判断链表是否为空
 * @param list 待判断的循环单链表
 * @return 如果为空则返回 1,否则返回 0
 */
int isEmpty(CLNode *list) {
    // 判断链表的第一个节点是否又是头结点,如果是则表示链表为空
    return list->next == list;
}

clear

清空循环单链表。注意如果是带头结点的循环单链表,则需要将头结点的 next 指针指向它本身。

实现步骤:

  • 从头到尾扫描循环单链表所有结点,释放结点空间。
  • 最后将头结点的 next 指针指向它本身,空表仍然是循环单链表。

实现代码如下:

/**
 * 清空循环单链表
 * @param list 待清空的循环单链表
 */
void clear(CLNode **list) {
    // 链表的第一个节点
    CLNode *node = (*list)->next;
    // 循环链表所有节点,删除节点,释放空间
    while (node != *list) {
        // 临时保存当前节点的后继节点
        CLNode *temp = node->next;
        // 释放空间
        free(node);
        // 继续下一个节点
        node = temp;
    }
    // 最后重新将链表的头结点的 next 指针指向自身,仍然是循环单链表
    (*list)->next = *list;
}

print

打印循环单链表所有结点值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C8Q0rwMV-1649167121972)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220404121434530.png)]

实现步骤:

  • 从头到尾扫描循环单链表所有结点,打印它们的数据值。

实现代码如下:

/**
 * 打印循环单链表
 * @param list 待打印的循环单链表
 */
void print(CLNode *list) {
    printf("[");
    CLNode *node = list->next;
    while (node != list) {
        printf("%d", node->data);
        if (node->next != list) {
            printf(", ");
        }
        node = node->next;
    }
    printf("]\n");
}

注意事项

循环单链表判空操作

如果是带头结点的循环单链表,则判空条件是:head->next==head

如果是不带头结点的循环单链表,则判空条件是:head=NULL

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QnraHlwa-1649167121974)(image-%E5%BE%AA%E7%8E%AF%E5%8D%95%E9%93%BE%E8%A1%A8/image-20220403213944279.png)]

练习题

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值