C语言数据结构篇——单循环链表的创建,插入,节点删除,打印等操作

简介: C语言数据结构篇——单循环链表的创建,插入,节点删除,打印等操作

初识循环链表


在学习过单链表以及进阶的双链表之后就需要进一步学习循环链表了,顾名思义,这种链表头尾相接,形成一个环(即尾节点的后继指针指向第一个节点),其他的单链表的差别不大,但循环链表从表中任意一个节点出发,都可以访问其他的所有节点,无论前后,这也是单链表不具备的优势,循环链表也有很多种,单循环链表 ,双循环链表和多种循环链表,这里我们只研究单循环链表。下图就是单循环链的一种。


30a10218fe0541238136c8c0f6b2ea7b.png


图中可以看出循环链表没有NULL指针,所以终止条件也随之改变,下面我们进入正文:


循环链表头结点和数据节点的定义


这里我们还是选择定义头结构体和数据节点结构体制作链表,循环链表的定义还是和单链表一样,这里就不多说了,直接奉上代码:


typedef struct header//头结点结构体
{
    int length;//保存循环链表的信息
    struct node* next;//指向第一个数据节点
}head;
typedef struct node//数据节点结构体
{
    int val;//数据域
    struct node* next;//指针域
}node;


这样头结点和数据节点的结构体就定义好啦,我们需要用的时候直接引用就可以啦,接下来就让我们创建一个单循环链表


循环链表的创建


头结点加数据节点形式的链表创建大同小异都是创建一个头结点并分配空间,后面要用数据节点的时候再分配内存给数据节点就可以了,所以链表的创建也相对简单,代码如下:


head* listcreat()
{
    head* p;//定义头结点
    p=(head*)malloc(sizeof(head));
    p->next=NULL;//此时没有数据节点,所以头结点指向NULL
    p->length=0;//初始化头结点
    return p;//返回创建好的头节点地址
}


一个初始的头节点就创建好了,用一个图展示链表现在的情况就是下面这样啦


4d429f65bdaa42b3a4b7a1434fa19da1.png


循环链表数据节点的插入


循环链表的数据节点插入其实并不复杂,主要是头插和尾插相对于单链表难一点,建议学的时候可以那一个本子画个草图(巨好用),其他位置的插入和单链表的操作一样,直接附上代码:


void listinsert(head* p,int pos,int x)//头结点,要插入的位置,要插入的元素
{
    if(p==NULL||pos<0||pos>p->length)//健壮性判断
    {
        printf("listinsert():error\n");
        return;
    }
    node* temp=(node*)malloc(sizeof(node));为要插入的数据temp分配空间
    temp->val=x;//填充数据域
    node* pcur=p->next;//定义一个指向第一个数据节点的指针
    node* plast=p->next;//指向第一个节点方便遍历
    while(pcur!=NULL&&plast->next!=pcur)//使plast指向最后一个节点
    {
        plast=plast->next;
    }
    if(p->length==0)//判断循环链表为空的情况
    {
        p->next=temp;
        temp->next=temp;
    }
    else if(pos==0)//头插
    {
        plast->next=temp;
        temp->next=pcur;
        p->next=temp;
    }
    else if(pos==p->length)//尾插
    {
        plast->next=temp;
        temp->next=pcur;
    }
    else
    {
        node* pval=p->next;//pval用来指向要插入位置的数据节点
        for(int i=1;i<pos;i++)
        {
            pval=pval->next;
        }
        temp->next=pval->next;
        pval->next=temp;
    }
    p->length++;//记录循环链表的变化
    return;
}


数据节点的插入函数就封装成功啦,最后就是循环链表中数据节点的删除了。


循环链表数据节点的删除


在搞清楚循环链表的特点以及同单链表的差别之后,数据节点的删除问题也游刃而解了,但注意循环链表中的数据节点删除和双链表一样都需要把头插,尾插单独拎出来分析,而且表中只有一个数据节点时也需要另外分析,具体代码如下:


void listdelete(head* p,int x)//头结点,要删除的元素
{
    node* temp;//temp待会指向要删除的节点
    temp=p->next;
    for(int i=0;i<p->length;i++)//遍历链表寻找要删除的数据节点
    {
        if(temp->val==x)
        {
            break;
        }
        temp=temp->next;
    }
     if(temp->val!=x)//没找到对应的数据节点
    {
        printf("listdelete():error\n");
        return;
    }
    node* pcur=p->next;//pcur指向第一个节点
    node* plast=p->next;//plast用来指向最后一个节点
    while(plast->next!=pcur)//使plast移向最后一个节点
    {
        plast=plast->next;
    }
    if(p->length==1)//循环链表中只有一个数据节点时
    {
        p->next=NULL;
    }
    else if(temp==pcur)//删除的是第一个节点时
    {
        p->next=pcur->next;
        plast->next=pcur->next;
    }
    else if(temp==plast)//删除的是最后一个节点时
    {
        node* pre=p->next;//定义一个指针,待会指向倒数第二个节点
        while(pre->next!=plast)
        {
            pre=pre->next;
        }
        pre->next=pcur;
    }
    else
    {
        node* pre=p->next;
        while(pre->next!=temp)//使pre指向temp的前一个元素(注意,这里的pre不是上面的pre)
        {
            pre=pre->next;
        }
        pre->next=temp->next;
    }
    p->length--;//记录循环链表的变化
}


循环链表的遍历打印


为了方便实际操作,也可以将循环链表的打印输出封装成一个函数,如下:


void listprint(head* p)//头结点
{
    if(p==NULL||p->length==0)//健壮性判断
    {
        printf("listprint():error");
        return;
    }
    node* temp=p->next;
    for(int i=0;i<p->length;i++)//注意此时遍历的结束条件
    {
        printf("%d ",temp->val);
        temp=temp->next;
    }
    printf("\n");
    return;
}


在单循环链表打印输出时值得注意的是遍历时的结束条件,因为循环链表不再有NULL指针,所以结束条件也发生了变化,这时候头结点中记录的链表大小就起到了作用。


完整代码


循环链表的操作远不止这些,这里就不一一写出来了,但我认为将这几个操作弄明白之后其他的操作都会变得更加简单,将这几个函数综合使用的完整代码如下,可以根据需要取舍:


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct header
{
    int length;
    struct node* next;
}head;
typedef struct node
{
    int val;
    struct node* next;
}node;
head* listcreat()
{
    head* p;
    p=(head*)malloc(sizeof(head));
    p->next=NULL;
    p->length=0;
    return p;
}
void listinsert(head* p,int pos,int x)
{
    if(p==NULL||pos<0||pos>p->length)
    {
        printf("listinsert():error\n");
        return;
    }
    node* temp=(node*)malloc(sizeof(node));
    temp->val=x;
    node* pcur=p->next;//指向第一个数据节点
    node* plast=p->next;//指向最后一个数据节点
    while(pcur!=NULL&&plast->next!=pcur)//使plast指向最后一个节点
    {
        plast=plast->next;
    }
    if(p->length==0)//判断循环链表为空的情况
    {
        p->next=temp;
        temp->next=temp;
    }
    else if(pos==0)//头插
    {
        plast->next=temp;
        temp->next=pcur;
        p->next=temp;
    }
    else if(pos==p->length)//尾插
    {
        plast->next=temp;
        temp->next=pcur;
    }
    else
    {
        node* pval=p->next;//pval用来指向要插入位置的数据节点
        for(int i=1;i<pos;i++)
        {
            pval=pval->next;
        }
        temp->next=pval->next;
        pval->next=temp;
    }
    p->length++;
    return;
}
void listdelete(head* p,int x)
{
    node* temp;//temp指向要删除的节点
    temp=p->next;
    for(int i=0;i<p->length;i++)
    {
        if(temp->val==x)
        {
            break;
        }
        temp=temp->next;
    }
     if(temp->val!=x)
    {
        printf("listdelete():error\n");
        return;
    }
    node* pcur=p->next;//pcur指向第一个节点
    node* plast=p->next;//plast用来指向最后一个节点
    while(plast->next!=pcur)
    {
        plast=plast->next;
    }
    if(p->length==1)//只有一个元素时
    {
        p->next=NULL;
    }
    else if(temp==pcur)//删除的是第一个节点
    {
        p->next=pcur->next;
        plast->next=pcur->next;
    }
    else if(temp==plast)//删除的是最后一个节点
    {
        node* pre=p->next;//指向倒数第二个节点
        while(pre->next!=plast)
        {
            pre=pre->next;
        }
        pre->next=pcur;
    }
    else
    {
        node* pre=p->next;
        while(pre->next!=temp)//使pre指向temp的前一个元素
        {
            pre=pre->next;
        }
        pre->next=temp->next;
    }
    p->length--;
}
void listprint(head* p)
{
    if(p==NULL||p->length==0)
    {
        printf("listprint():error");
        return;
    }
    node* temp=p->next;
    for(int i=0;i<p->length;i++)
    {
        printf("%d ",temp->val);
        temp=temp->next;
    }
    printf("\n");
    return;
}
int main()
{
    head* p;
    p=listcreat();
    int number;
    printf("请输入循环链表的初始数据节点个数\n");
    scanf("%d",&number);
    int a[number];
    printf("请依次填充数据节点\n");
    for(int i=0;i<number;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=number-1;i>=0;i--)
    {
        listinsert(p,0,a[i]);
    }
    printf("输出初始链表\n");
    listprint(p);
    printf("请输入插入的位置和元素\n");
    int m,n;
    scanf("%d%d",&m,&n);
    listinsert(p,m,n);
    printf("输出插入后的链表\n");
    listprint(p);
    printf("请输入要删除的元素\n");
    int del;
    scanf("%d",&del);
    listdelete(p,del);
    printf("输出删除后的链表\n");
    listprint(p);
}

随便写点数据运行一下就是下面这个效果啦,刚学习c语言半年,有不完善的地方还请见谅,感谢阅读。


请输入循环链表的初始数据节点个数

6

请依次填充数据节点

1 2 3 4 5 6

输出初始链表

1 2 3 4 5 6

请输入插入的位置和元素

6 7

输出插入后的链表

1 2 3 4 5 6 7

请输入要删除的元素

5

输出删除后的链表

1 2 3 4 6 7


相关文章
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
711 1
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
227 4
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
223 4
|
存储 算法 数据处理
【趣学C语言和数据结构100例】81-85
本文介绍了五个经典算法问题及其C语言实现,涵盖图论与树结构的基础知识。包括使用BFS求解单源最短路径、统计有向图中入度或出度为0的点数、统计无向无权图各顶点的度、折半查找及二叉排序树的查找。这些算法不仅理论意义重大,且在实际应用中极为广泛,有助于提升编程能力和数据结构理解。
189 4
|
算法 数据可视化 数据建模
【趣学C语言和数据结构100例】76-80
本文介绍了五种图论算法的C语言实现,涵盖二叉树的层次遍历及广度优先搜索(BFS)和深度优先搜索(DFS)的邻接表与邻接矩阵实现。层次遍历使用队列按层访问二叉树节点;BFS利用队列从源节点逐层遍历图节点,适用于最短路径等问题;DFS通过递归或栈深入图的分支,适合拓扑排序等场景。这些算法是数据结构和算法学习的基础,对提升编程能力和解决实际问题至关重要。
258 4
|
存储 算法 vr&ar
【趣学C语言和数据结构100例】71-75
本文介绍了五个C语言数据结构问题及其实现,涵盖链表与二叉树操作,包括按奇偶分解链表、交换二叉树左右子树、查找节点的双亲节点、计算二叉树深度及求最大关键值。通过递归和遍历等方法,解决了理论与实际应用中的常见问题,有助于提升编程能力和数据结构理解。
257 4
|
存储 算法 C语言
【趣学C语言和数据结构100例】66-70
本书《趣学C语言和数据结构100例》精选了5个典型的数据结构问题及C语言实现,涵盖链表与数组操作,如有序集合的集合运算、有序序列表的合并、数组中两顺序表位置互换、三递增序列公共元素查找及奇偶数重排。通过详细解析与代码示例,帮助读者深入理解数据结构与算法设计的核心思想,提升编程技能。
186 4
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
487 30
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
712 25
|
定位技术 C语言
c语言及数据结构实现简单贪吃蛇小游戏
c语言及数据结构实现简单贪吃蛇小游戏

热门文章

最新文章