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


相关文章
|
13天前
|
存储
数据结构第二课 -----线性表之单向链表
数据结构第二课 -----线性表之单向链表
链表遍历,链表查找和统计节点,链表插入新节点,链表删除节点,链表修改指定节点,链表头插法,尾插法总结
链表遍历,链表查找和统计节点,链表插入新节点,链表删除节点,链表修改指定节点,链表头插法,尾插法总结
|
4天前
|
存储 算法 Java
数据结构与算法 数组和链表
数据结构与算法 数组和链表
9 0
|
4天前
|
存储 Java
深入浅出数据结构之链表
深入浅出数据结构之链表
|
4天前
|
C++
数据结构(双链表
数据结构(双链表
7 1
|
7天前
|
存储 缓存
[数据结构]~双向+循环链表从(0~1)
[数据结构]~双向+循环链表从(0~1)
|
7天前
|
搜索推荐 C语言
【C语言/数据结构】排序(归并排序|计数排序|排序算法复杂度)
【C语言/数据结构】排序(归并排序|计数排序|排序算法复杂度)
11 0
|
7天前
|
C语言
【C语言/数据结构】排序(快速排序及多种优化|递归及非递归版本)
【C语言/数据结构】排序(快速排序及多种优化|递归及非递归版本)
9 0
|
7天前
|
C语言
【C语言/数据结构】排序(选择排序,推排序,冒泡排序)
【C语言/数据结构】排序(选择排序,推排序,冒泡排序)
12 0
|
7天前
|
C语言
【C语言/数据结构】排序(直接插入排序|希尔排序)
【C语言/数据结构】排序(直接插入排序|希尔排序)
14 4