数据结构(C语言版)——2.3、循环链表实现

本文详细介绍了循环链表的概念、特点以及单循环链表的实现,包括存储结构定义、初始化、节点操作如插入、删除等,并提供了完整的C语言代码实现。循环链表允许从任一节点访问所有节点,特别适用于需要频繁进行头尾操作的场景。
摘要由CSDN通过智能技术生成

2.3、循环链表实现

2.3.1、循环链表说明

  • 循环链表(circular linked list)是另一种形式的链式存储结构,它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。由此,从表中任一结点出发均可找到表中其他的结点。
  • 单链表中,从一已知结点出发,只能访问到该结点及其后续结点,无法找到该结点之前的其它结点。而在单循环链表中,从任一结点出发都可访问到表中所有结点,这一优点使某些运算在单循环链表上易于实现。
  • 单循环链表的操作和单链表基本一致,差别在于算法中的循环条件不是操作指针 p 或 p->next 是否为 NULL ,而是它们是否等于头指针。
    在这里插入图片描述
  • 双循环链表的操作和双链表基本一致。

2.3.2、循环链表的优缺点与适用情况

敬请期待

2.3.3、单循环链表实现

(1)单循环链表存储结构定义与头文件声明

  • 存储结构定义:为了单链表的头尾操作更便捷,这里增设一个链表类型,其中包括链表的头指针 head (始终指向头结点)、尾指针 tail (始终指向链表最后一个结点)以及一个用于记录结点个数的计数器。增设尾指针 tail 使得尾插、合并操作更加便利,时间复杂度更小。
    在这里插入图片描述
  • 头文件声明:头文件声明包括函数结果状态代码 OK、ERROR,数据元素类型 ElemType ,单链表的基本操作的函数原型声明,以及需要的标准库函数头文件。

注:由于C语言不存在引用,需用传址。对于一些需要更改链表内容的操作,需要传递地址。即,形参声明为指针类型,实参用取址符


#ifndef SING_CIRCULAR_LinkList_H
#define SING_CIRCULAR_LinkList_H

#define OK 1
#define ERROR 0
#include <stdio.h>
#include <stdlib.h>
#include<stdbool.h>
#include <assert.h>
typedef int Status;   //自定义函数类型,其值是函数结果的状态代码
typedef int ElemType; //自定义数据类型
typedef struct LNode
{
   //结点类型,同单链表
    ElemType data;
    struct LNode* next; //注意类型
} LNode, *PNode;
typedef struct SCLinkList
{
   
    PNode head;   //指向头结点
    PNode tail;    //指向尾结点
    int len;
} SCLinkList;
//顺序表的基本操作的函数原型说明
Status InitSCLinkList(SCLinkList *L);
//构造一个空顺序表
PNode Buynode(ElemType e);
//开辟一个数据域为e的新结点
Status push_back(SCLinkList *L, ElemType e);
//尾部插入新元素e
Status push_front(SCLinkList *L, ElemType e);
//头部插入新元素e
Status pop_back(SCLinkList *L);
//尾部删除
Status pop_front(SCLinkList *L);
//头部删除
Status Destory_List(SCLinkList *L);
//销毁顺序表 
Status Clear_List(SCLinkList *L);
//置空队列 Q
bool Is_empty(SCLinkList L);
//判断队列是否为空
int Length_List(SCLinkList L);
//返回队列 Q 的元素个数
PNode find(SCLinkList L, ElemType e);
//返回e元素所在结点
Status Insert_val(SCLinkList *L, ElemType e);
//在L中第i个位置前插入新的元素e
Status Delete_val(SCLinkList *L,  ElemType e);
//删除L中的第i个元素,并用e返回其值
Status Sort(SCLinkList *L);
//升序排序
Status Traverse_List(SCLinkList Q /*,visit()*/);
//从队头到队尾依次调用函数 visit(),这里简化为输出
#endif

(2)单循环链表主要操作实现与具体思路

  • 初始化 InitSCLinkList()
    对于有头结点的单循环链表初始化操作就是开辟一个头结点,并给头指针、尾指针赋初值。此时只有一个头结点,则头指针与尾指针都应指向头结点,且尾结点的 next 应指向头结点。单链表长度记录 len 初始化为 0 (无数据的头结点不计入数据结点个数)。
    在这里插入图片描述
Status InitSCLinkList(SCLinkList *L)
{
   
    PNode s = (PNode)malloc(sizeof(LNode)); //开辟一个结点空间
    assert(s != NULL);
    L->head = L->tail = s;   //此时仅有一个结点,头尾指针均指向该结点
    L->tail->next = L->head; //让尾结点的指针域指向头结点,实现循环
    L->len= 0;
    return OK;
}
  • 结点申请操作 Buynode()
    为了减少代码的重复,特设此函数。该函数开辟一个新结点空间,并完成结点数据域、指针域的初始化,最后返回这个结点的地址。
PNode Buynode(ElemType e)
{
   
    PNode s = (PNode)malloc(sizeof(LNode));
    assert(s != NULL);
    s->data = e;
    s->next = NULL;
    return s;
}
  • 尾插 push_back()
    即将元素 e 插入到当前单循环链表的尾结点后。首先申请一个结点(Buynode()操作),其次将该结点链结到当前链表的尾部,然后更新尾指针指向,最后单链表长度加 1 。插入删除操作均要更改链表内容,故形参需要传递地址。
Status push_back(SCLinkList *L, ElemType e)
{
    //尾部插入新元素e
    PNode s = Buynode(e);
    L->tail->next = s;       //实现新结点s接在当前链表的尾结点之后
    L->tail = s;             //更新当前尾结点,即当前尾结点为s
    L->tail->next = L->head; //实现新的尾结点指向头结点
    L->len++;
    return OK;
}
  • 头插 push_front()
    即将新结点插入到单循环链表头部(头结点之后,第一个数据结点之前)。首先要申请一个结点,其次,要链结到第一个数据结点的前端,再链结到头结点的后端(不可颠倒,如果先将新结点链结到头结点的后端,会导致其他结点丢失)。最后应区分头插的新结点是不是当前第一个数据结点,如果是,那么需要更新尾结点。
Status push_front(SCLinkList *L, ElemType e)
{
    //头部插入新元素e
    PNode s = Buynode(e);
    //先链接结点尾部
    s->next = L->head->next;
    L->head->next = s;
    //以上两行对于s为第一个结点时,仍插入成功,但tail指针指向不正确 ,故如下
    if (L->head == L->tail) //两者相等即新插入的结点为第一个结点
    {
   
        L->tail = s; //更新尾结点,此时新插的结点是尾结点
        L->tail->next = L->head;//实现循环 
    }
    L->len++;
    return OK;
}
  • 尾删 pop_back()
    首先判断当前链表是否为空,若为空直接返回。要删除尾结点,则必须先获取尾结点的直接前驱。获取尾结点前驱需要操作指针 p 从头结点开始,依次向后遍历,直至 p 的next 域指向尾结点(即 p ->next == L->tail ),则此时 p 便指向尾结点的前驱。获取前驱后,直接释放尾结点,最后更新尾结点指向即可。
Status pop_back(SCLinkList *L)
{
    //尾部删除
    if (L->len== 0)
        return ERROR;
    PNode p = L->head; //要先寻找尾结点的前驱
    while (p->next != L->tail)
    {
    //从第一个元素结点位置向后遍历,直至p->next==L->tail,即p为尾结点的前驱
        p = p->next;
    }

    free(L->tail);
    L->tail = p;             //更新尾结点
    L->tail->next = L->head; //更新尾结点的指针域
    L->len--;
    return OK;
}
  • 头删 pop_front()
    同样,头删要考虑当前链表是否为空,若为空,直接返回。由于头结点的指针域指向第一个数据结点,所以不需要像尾删那样,遍历到尾结点的直接前驱。只需先获取第一个元素结点,然后将第二个元素结点链结到头结点的 next 域即可,最后释放结点。该操作要考虑,头删后链表是否仅剩一个头结点,如果是,则应更新尾指针 tail ,使其指向头结点。
Status pop_front(SCLinkList *L)
{
    //头部删除
    if (L->len== 0)
        return ERROR;
    PNode p = L->head->next;
    L->head->next = p->next;

    free(p);
    if (L->len== 1)
    {
    //如果当前只有一个结点(即删除的是尾结点lsat),需要更改尾结点的指向
        //此时删除后,仅剩一个头结点
        L->tail = L->head;
        L->tail->next = L->head;//实现循环
    }
    L->len--;
    return
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值