C语言实现循环单链表的示例代码

 更新时间:2023年08月04日 10:14:49   作者:r1v0z  
这篇文章主要给大家详细介绍了C语言如何实现循环单链表,文章通过代码示例讲解的非常详细,对我们的学习或工作有一定的参考价值,感兴趣的小伙伴跟着小编一起来看看吧

一、定义

  • 与单链表相似,只不过单链表最后一个节点的 next 指向 NULL,循环单链表最后一个节点指向链表的第一个节点

    image-20230802171836724.png

  • 循环单链表也是分为带头结点和不带头结点,部分操作对于不带头结点的循环单链表实现会复杂一些

  • 循环单链表最大的优点就是只要知道其中一个节点就可以访问链表中的全部节点

二、基本操作

1.初始化链表

本文主要讨论不带头结点的循环单链表,初始化链表时不存在任何节点,所以将链表的指针指向 NULL

LinkList InitList()
{
    LinkList list = NULL;
    return list;
}

2.头插法

有两种方法可以实现头插法

  1. 方法一(不太建议):找到最后一个节点,然后修改它的 next 指向新的头节点,很麻烦,要遍历整个链表,就不多介绍了

    image-20230802203854732.png

  2. 方法二(建议):创建新的节点,新节点的数据域存放,链表中第一个节点的数据,将新节点插入到链表的第二位,将第一个节点的数据修改为要插入的数据

    image-20230802205206435.png

status HeadInsertList(LinkList* list, int data)
{
    LinkNode* node = (LinkNode*)malloc(sizeof(LinkNode));
    // 当 node 没有成功分配内存时,直接返回
    if(node == NULL)
        return EXIT_FAILURE;
    if((*list) != NULL)
    {
        node->data = (*list)->data;     // 将新节点的数据域,存放第一个节点的数据
        node->next = (*list)->next;     // 当链表中存在节点,则将 node->next 设置为第一个节点
        (*list)->next = node;
        (*list)->data = data;
    }
    else
    {
        node->data = data;
        node->next = node;     // 链表中没有节点,将 node->next 指向自己
        (*list) = node;        // 修改头指针指向 node
    }
    return EXIT_SUCCESS;
}

3.尾插法

实现从尾部插入新节点,要先找到链表的最后一个节点:

image-20230802212207620.png

status TailInsertList(LinkList* list, int e)
{
    LinkNode* cur = (*list);
    // 判断 cur != NULL,是为了避免链表中没有节点而发生错误,当节点的 next 指向第一个节点,表示该节点为最后一个节点
    while (cur != NULL && cur->next != (*list))       
        cur = cur->next;
    LinkNode* node = (LinkNode*)malloc(sizeof(LinkNode));
    if (node == NULL)
        return EXIT_FAILURE;
    node->data = e;
    if (cur == NULL)    // 链表中没有节点
    {
        node->next = node;
        (*list) = node;
    }
    else                // 链表中有节点
    {
        node->next = (*list);
        cur->next = node;
    }
    return EXIT_SUCCESS;
}

4.按位插入

当链表要在第一位插入节点时,需要进行特殊处理,可以使用头插法解决,在其他位置插入时,先修改新建节点的 next 指针指向下一个节点,再修改前面节点的 next 指向新节点

image-20230802213703897.png

status InsertList(LinkList* list, int pos, int data)
{
    // 初步判断值是否有效,大于等于 1 不代表pos位置能够插入,但小于 1 一定不能插入
    if(pos < 1)
        return EXIT_FAILURE;
    // 当要在第一位插入时,可以使用头差法插入
    if (pos == 1)
        return HeadInsertList(list, data);
    LinkNode* curNode = *list;
    int count = 1;      // 表示当前的位置
    while (curNode->next != *list && count < pos - 1)
    {
        // 
        curNode = curNode->next;
        count++;
    }
    // 当 count < pos - 1时,代表已经到最后一个节点了,但是还是没有到达pos - 1的位置
    if(count < pos - 1)
        return EXIT_FAILURE;
    LinkNode* node = (LinkNode*)malloc(sizeof(LinkNode));
    if(node == NULL)
        return EXIT_FAILURE;
    node->data = data;
    node->next = curNode->next;
    curNode->next = node;
    return EXIT_SUCCESS;
}

5.头删法

头删法也有两种方式删除:

  • 第一种方法:将第一个节点删除,修改最后一个节点的 next 指向新的头结点,这会很麻烦,需要遍历整个链表。

    image-20230803175327634.png

  • 第二种方法是:将第二个节点的数据放入到第一个节点中,然后删除掉第二个节点,这样可以不需要修改最后一个节点的 next 指针

    image-20230803120425092.png

status HeadDeleteList(LinkList* list)
{
    // 判断 next 是否指向本身,如果是表示只有一个节点
    if((*list)->next == *list)
    {
        // 只有一个节点时,释放后需要将指针指向 NULL
        free(*list);
        *list = NULL;
    }
    else
    {
        LinkNode* curNode = (*list)->next;
        (*list)->next = curNode->next;
        (*list)->data = curNode->data;
        free(curNode);
    }
    return EXIT_SUCCESS;
}

6.尾删法

将倒数第二个节点的 next 指向第一个节点,然后将最后一个节点删除

image-20230803180922490.png

status TailDeleteList(LinkList* list)
{
    LinkNode* cur = *list;
    LinkNode* pre = NULL;
    // 遍历链表,cur 表示当前的节点, pre 表示前一个节点
    while(cur->next != *list)
    {
        pre = cur;
        cur = cur->next;
    }
    free(cur);
    // 当 pre == NULL,则说明链表只有一个节点
    if(pre == NULL)
        *list = NULL;       // 只有一个节点,删除后没有节点,所以 *list 指向 NULL
    else
        pre->next = *list;  // 有多个节点, 将删除后的最后一个节点,指向开头
    return EXIT_SUCCESS;
}

7. 按位删除

当要删除的是第一位时,需要修改 list 的指向,可以使用头删法删除,在其他位置时,只需将删除节点的前一节点的 next 修改为 删除节点的 next ,最后将删除节点

image-20230803183347287.png

status DeleteList(LinkList* list, int pos)
{
    // 判断 pos 是否小于1,插入位置不能小于 1
    if (pos < 1)
        return EXIT_FAILURE;
    // 当 pos 等于 1 时,使用头插法
    if (pos == 1)
        return HeadDeleteList(list);
    int curPos = 1;                 // 当前节点的位置
    LinkNode* curNode = *list;     // 用于查找要删除的节点
    LinkNode* preNode = NULL;      // 指向 curNode 的前一个节点,一开始没有前一节点,所以初始化为NULL
    while (curNode->next != *list && curPos < pos)
    {
        // 移动 curNode,直到 curNode 指向第 pos 位节点
        preNode = curNode;
        curNode = curNode->next;
        curPos++;
    }
    // 当 curPos 小于 pos-1 时,curNode 已经指向最后一个节点,故要插入的位置超出当前链表长度
    if(curPos < pos - 1)
        return EXIT_FAILURE;
    preNode->next = curNode->next;
    free(curNode);
}

8.打印链表

循环遍历链表的所有节点,并将节点的数据打印出来,使用do ... while 是为了更好的判断最后一个节点,当第一次判断 cur != list 时,cur已经不是指向开头了,而是指向第二个节点的位置,使用 cur != list 判断是否完全遍历整个链表是没有问题的,而要使用其他循环打印会变得麻烦

status PrintList(LinkList  list)
{
    // list == NULL 链表中没有节点;
    LinkNode* cur = list;
    do
    {
        printf("%d -> ", cur->data);    // 打印当前节点的数据
        cur = cur->next;    // cur 指向下一个节点
    }while (cur != list);   // cur == list 时表示已经遍历了整个链表,如果使用 cur->next != list 进行判断最后一个节点会无法打印     
    printf("NULL\n");
    return EXIT_SUCCESS;
}

9.销毁链表

遍历所有的节点,当有节点的 next 指向当前链表的第一个节点则结束,删除遍历的节点,最后将链表指针指向 NULL(可以理解为循环调用头删法,直到链表中没有节点)

status DestroyList(LinkList* list)
{
    if((*list) == NULL)
        return EXIT_FAILURE;
    LinkNode* curNode = *list;     // curNode指向要释放的节点
    LinkNode* nextNode = NULL;     // nextNode 指向要释放节点的下一节点
    while (curNode->next != *list)  // 当 curNode->next 指向头指针,curNode指向的是最后一个节点
    {
        nextNode = curNode->next;
        free(curNode);
        curNode = nextNode;
    }
    *list = NULL;
    return EXIT_SUCCESS;
}

三、完整代码

  • LoopLinkList.h
#include<stdio.h>
#include<stdlib.h>
typedef int status;
typedef struct Node
{
    int data;
    struct Node* next;
}LinkNode, *LinkList;
// 初始化
LinkList InitList();
// 销毁链表
status DestroyList(LinkList* list);
// 头插法
status HeadInsertList(LinkList* list, int e);
// 尾插法
status TailInsertList(LinkList* list, int e);
// 头删法
status HeadDeleteList(LinkList* list);
// 尾删法
status TailDeleteList(LinkList* list);
// 打印链表
status PrintList(LinkList list);
// 按位插入节点
status InsertList(LinkList* list, int pos, int data);
// 按位删除节点
status DeleteList(LinkList* list, int pos);
// 求表长
int Length(LinkList list);
// 按位置查找节点
LinkNode* GetElem(LinkList list, int pos);
// 按值查找节点
LinkNode* LocateElem(LinkList list, int data);
  • LoopLinkList.c
#include "./head/LoopLinkList.h"
// 初始化
LinkList InitList()
{
    LinkList list = NULL;
    return list;
}
// 销毁链表
status DestroyList(LinkList* list)
{
    if((*list) == NULL)
        return EXIT_FAILURE;
    LinkNode* curNode = *list;     // curNode指向要释放的节点
    LinkNode* nextNode = curNode->next;     // nextNode 指向要释放节点的下一节点
    while (curNode->next != *list)  // 当 curNode->next 指向头指针,curNode指向的是最后一个节点
    {
        nextNode = curNode->next;
        free(curNode);
        curNode = nextNode;
    }
    *list = NULL;
    return EXIT_SUCCESS;
}
// 头插法
status HeadInsertList(LinkList* list, int data)
{
    LinkNode* node = (LinkNode*)malloc(sizeof(LinkNode));
    // 当 node 没有成功分配内存时,直接返回
    if(node == NULL)
        return EXIT_FAILURE;
    if((*list) != NULL)
    {
        node->data = (*list)->data;     // 将新节点的数据域,存放第一个节点的数据
        node->next = (*list)->next;     // 当链表中存在节点,则将 node->next 设置为第一个节点
        (*list)->next = node;
        (*list)->data = data;
    }
    else
    {
        node->data = data;
        node->next = node;     // 链表中没有节点,将 node->next 指向自己
        (*list) = node;        // 修改头指针指向 node
    }
    return EXIT_SUCCESS;
}
// 尾插法
status TailInsertList(LinkList* list, int e)
{
    LinkNode* cur = (*list);
    // 判断 cur != NULL,是为了避免链表中没有节点而发生错误,当节点的 next 指向第一个节点,表示该节点为最后一个节点
    while (cur != NULL && cur->next != (*list))       
        cur = cur->next;
    LinkNode* node = (LinkNode*)malloc(sizeof(LinkNode));
    if (node == NULL)
        return EXIT_FAILURE;
    node->data = e;
    if (cur == NULL)    // 链表中没有节点
    {
        node->next = node;
        (*list) = node;
    }
    else                // 链表中有节点
    {
        node->next = (*list);
        cur->next = node;
    }
    return EXIT_SUCCESS;
}
// 头删法
status HeadDeleteList(LinkList* list)
{
    // 判断 next 是否指向本身,如果是表示只有一个节点
    if((*list)->next == *list)
    {
        // 只有一个节点时,释放后需要将指针指向 NULL
        free(*list);
        *list = NULL;
    }
    else
    {
        LinkNode* curNode = (*list)->next;
        (*list)->next = curNode->next;
        (*list)->data = curNode->data;
        free(curNode);
    }
    return EXIT_SUCCESS;
}
// 尾删法
status TailDeleteList(LinkList* list)
{
    LinkNode* cur = *list;
    LinkNode* pre = NULL;
    // 遍历链表,cur 表示当前的节点, pre 表示前一个节点
    while(cur->next != *list)
    {
        pre = cur;
        cur = cur->next;
    }
    free(cur);
    // 当 pre == NULL,则说明链表只有一个节点
    if(pre == NULL)
        *list = NULL;       // 只有一个节点,删除后没有节点,所以 *list 指向 NULL
    else
        pre->next = *list;  // 有多个节点, 将删除后的最后一个节点,指向开头
    return EXIT_SUCCESS;
}
// 打印链表
status PrintList(LinkList  list)
{
    // list == NULL 链表中没有节点;
    LinkNode* cur = list;
    do
    {
        printf("%d -> ", cur->data);    // 打印当前节点的数据
        cur = cur->next;    // cur 指向下一个节点
    }while (cur != list);   // cur == list 时表示已经遍历了整个链表,如果使用 cur->next != list 进行判断最后一个节点会无法打印     
    printf("NULL\n");
    return EXIT_SUCCESS;
}
// 按位插入节点
status InsertList(LinkList* list, int pos, int data)
{
    // 判断值是否合法,大于等于 1 不代表pos有效,但小于1一定不能插入
    if(pos < 1)
        return EXIT_FAILURE;
    // 当要在第一位插入时,可以使用头差法插入
    if (pos == 1)
        return HeadInsertList(list, data);
    LinkNode* curNode = *list;
    int count = 1;      // 表示当前的位置
    while (curNode->next != *list && count < pos - 1)
    {
        // 
        curNode = curNode->next;
        count++;
    }
    // 当 count < pos - 1时,代表已经到最后一个节点了,但是还是没有到达pos - 1的位置
    if(count < pos - 1)
        return EXIT_FAILURE;
    LinkNode* node = (LinkNode*)malloc(sizeof(LinkNode));
    if(node == NULL)
        return EXIT_FAILURE;
    node->data = data;
    node->next = curNode->next;
    curNode->next = node;
    return EXIT_SUCCESS;
}
// 按位删除节点
status DeleteList(LinkList* list, int pos)
{
    // 判断 pos 是否小于1,插入位置不能小于 1
    if (pos < 1)
        return EXIT_FAILURE;
    // 当 pos 等于 1 时,使用头插法
    if (pos == 1)
        return HeadDeleteList(list);
    int curPos = 1;                 // 当前节点的位置
    LinkNode* curNode = *list;     // 用于查找要删除的节点
    LinkNode* preNode = NULL;      // 指向 curNode 的前一个节点,一开始没有前一节点,所以初始化为NULL
    while (curNode->next != *list && curPos < pos)
    {
        // 移动 curNode,直到 curNode 指向第 pos 位节点
        preNode = curNode;
        curNode = curNode->next;
        curPos++;
    }
    // 当 curPos 小于 pos-1 时,curNode 已经指向最后一个节点,故要插入的位置超出当前链表长度
    if(curPos < pos - 1)
        return EXIT_FAILURE;
    preNode->next = curNode->next;
    free(curNode);
}
// 求表长
int Length(LinkList list)
{
    int len = 1;
    LinkNode* curNode = list;
    while (curNode->next != list)
    {
        len++;
        curNode = curNode->next;
    }
    return len;
}
// 按位置查找节点
LinkNode* GetElem(LinkList list, int pos)
{
    LinkNode* curNode = list;
    int curPos = 1;
    while (curNode->next == list && curPos < pos)
    {
        curNode = curNode->next;
        curPos++;
    }
    if (curPos == pos)
        return curNode;
    else
        return NULL;
}
// 按值查找节点
LinkNode* LocateElem(LinkList list, int data)
{
    LinkNode* curNode = list;
    while (curNode->next == list)
    {
        if(curNode->data == data)
            return curNode;
        curNode = curNode->next;
    }
    return NULL;
}

以上就是C语言实现循环单链表的示例代码的详细内容,更多关于C 循环单链表的资料请关注脚本之家其它相关文章!

相关文章

  • C++简单实现RPC网络通讯的示例详解

    C++简单实现RPC网络通讯的示例详解

    RPC是远程调用系统简称,它允许程序调用运行在另一台计算机上的过程,就像调用本地的过程一样。本文将用C++简单实现RPC网络通讯,感兴趣的可以了解一下
    2023-04-04
  • C/C++ 宏详细解析

    C/C++ 宏详细解析

    关于宏的一些语法问题,可以在google上找到。相信我,你对于宏的了解绝对没你想象的那么多。如果你还不知道#和##,也不知道prescan,那么你肯定对宏的了解不够
    2013-09-09
  • C语言代码链表实现贪吃蛇游戏

    C语言代码链表实现贪吃蛇游戏

    这篇文章主要为大家详细介绍了C语言链表实现贪吃蛇游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-01-01
  • 浅谈带缓冲I/O 和不带缓冲I/O的区别与联系

    浅谈带缓冲I/O 和不带缓冲I/O的区别与联系

    下面小编就为大家带来一篇浅谈带缓冲I/O 和不带缓冲I/O的区别与联系。小编觉得挺不错的现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • 解析C++中多层派生时的构造函数及一些特殊形式

    解析C++中多层派生时的构造函数及一些特殊形式

    这篇文章主要介绍了解析C++中多层派生时的构造函数及一些特殊形式,特殊形式主要针对基类和子对象类型的构造函数内容,需要的朋友可以参考下
    2015-09-09
  • C语言数字图像处理之直方图均衡化

    C语言数字图像处理之直方图均衡化

    这篇文章主要为大家详细介绍了C语言数字图像处理之直方图均衡化,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • C++实现Date类各种运算符重载的示例代码

    C++实现Date类各种运算符重载的示例代码

    这篇文章主要为大家详细介绍了C++实现Date类各种运算符重载的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-02-02
  • C语言如何使用函数求素数和举例

    C语言如何使用函数求素数和举例

    素数又称质数,所谓素数是指除了1和它本身以外,不能被任何整数整除的数,下面这篇文章主要给大家介绍了关于C语言如何使用函数求素数和的相关资料,需要的朋友可以参考下
    2022-11-11
  • Qt QChart 创建图表的实现方法

    Qt QChart 创建图表的实现方法

    这篇文章主要介绍了Qt QChart 创建图表的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • C/C++高精度算法的实现

    C/C++高精度算法的实现

    这篇文章主要介绍了C/C++高精度算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02

最新评论