当前位置: 首页 > article >正文

单链表专题(上)

链表的定义与创建

线性表: 1. 物理结构上不一定是线性的

                2. 逻辑结构上一定是线性的

链表是一种物理存储结构上非连续,非顺序的存储结构

链表也是线性表的一种,但是在物理结构上不是连续的

链表是由一个一个的节点组成,需要数据的时候只需要申请空间即可

节点包含两个组成部分: 1. 存储的数据

                                         2. 指向下一个节点的指针

//节点的结构演示
struct SListNode   //single list node
{
    int data;
    struct SListNode* next;  //指向下一个节点的指针
}

下面我们将详细的进行链表节点的定义:

//定义节点的结构
//数据 + 指向下一个节点的指针
typedef int SLTDataType;  //由于数据不止 int 一种类型,所以统一用 SLTDataType 表示 
typedef struct SListNode {
    SLTDataType data;
    struct SListNode* next;
}SLTNode;

这里我们将struct SListNode 统一改写成 SLTNode

接下来,我们来学习创建几个节点:

//创建四个节点
SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
node1->data = 1;
SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
node2->data = 2;
SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
node3->data = 3;
SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
node4->data = 4;
//将四个节点连接起来
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;

由于创建的新节点的内存情况未知,所以使用 malloc 来动态分配内存。realloc 主要用于调整已分配内存块的大小

链表的打印

void SLTPrint(SLTNode* phead)
{
    SLTNode* pcur = phead;
    while (pcur)
    {
        printf("%d->", pcur->data);
        pcur = pcur->next; 
    }
    printf("NULL\n");
}

但是我们一般不会像那样创建节点,而是给一个空链表去插入数据,所以下面是链表的尾插

链表的尾插

想找到进行尾插,要先找到尾节点,再将尾节点和新节点连接起来

注意: 我们在尾插头插等等时,都需要用到malloc创建新的空间,重复的书写会使代码显得冗长,所以我们封装一个函数专门用于开辟空间

SLTNode* SLTBuyNode(SLTDataType x)
{
     SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
     //判断空间是否开辟成功
     if (newnode == NULL)
     {
        perror("malloc failed !");
        exit(1); //用非零的数表示非正常退出
     }
     newnode->data = x;
     newnode->next = NULL;

     return newnode;
}

有了创建控件函数后,我们开始写尾插代码:

void SLTPushBack(SLTNode* phead, SLTDataType x)
{
    SLTNode* newnode = SLTBuyNode(x);
    //先找尾
    SLTNode* ptail = phead; 
    while (ptail->next != NULL)
    {
        ptail = ptail->next;
    }
    //此时ptail指向的就是尾节点
    ptail->next = newnode;
}

请注意,这里的原链表并不知道是否是空链表,若为空,则对空指针解引用是非法的。所以需要对两种情况进行讨论:

void SLTPushBack(SLTNode* phead, SLTDataType x)
{
     //空链表情况
     SLTNode* newnode = SLTBuyNode(x);
     if (phead == NULL)
     {
        phead = newnode;
     }
     else
     {
        //非空链表情况
        //先找尾
        SLTNode* ptail = phead; 
        while (ptail->next != NULL)
        {
            ptail = ptail->next;
        }
        //此时ptail指向的就是尾节点
        ptail->next = newnode;
     }    
}

这里用个很重大的问题,我们传入实参 plist ,而我们需要形参改变实参,plist虽然也是指针,但他存储着一个结构体的地址,我们现在就是要修改这个地址的相关内容,所以我们需要使用二级指针来传址调用

下面是一些对应关系:

第一个节点

*plist

**pphead

指向第一个节点的指针

plist

*pphead

指向第一个节点的指针的地址

&plist

pphead

//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);
    //空链表情况
    SLTNode* newnode = SLTBuyNode(x);
    if (*pphead == NULL)
    {
        *pphead = newnode;
    }
    else
    {
        //非空链表情况
        //先找尾
        SLTNode* ptail = *pphead; 
        while (ptail->next != NULL)
        {
            ptail = ptail->next;
        }
        //此时ptail指向的就是尾节点
        ptail->next = newnode;
    }
}

由于节点的地址的地址不能为空,所以再加入断言,尾插代码就写好了。

void SLTPopBack(SLTNode** pphead)
{
    assert(pphead && *pphead);  //链表不能为空
    SLTNode* prev = *pphead;
    SLTNode* ptail = *pphead;
    while (ptail->next)
    {
        prev = ptail;
        ptail = ptail->next;
    }
    free(ptail);
    ptail = NULL;
    prev->next = NULL;
}

链表的头插(相对简单)

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);
    SLTNode* newnode = SLTBuyNode(x);
    newnode->next = *pphead;
    *pphead = newnode;
}

链表的尾删

思路为先找到尾节点,将尾节点释放掉,由于释放了尾节点后,尾节点的上一个节点依然指向尾节点,所以还需将上一个节点置零

void SLTPopBack(SLTNode** pphead)
{
    assert(pphead && *pphead);  //链表不能为空
    SLTNode* prev = *pphead;
    SLTNode* ptail = *pphead;
    while (ptail->next)
    {
        prev = ptail;
        ptail = ptail->next;
    }
    free(ptail);
    ptail = NULL;
    prev->next = NULL;
}

当链表只有一个节点的时候,此时尾节点的上一个节点并不实际存在,所以需要单独讨论

void SLTPopBack(SLTNode** pphead)
{
    assert(pphead && *pphead);  //链表不能为空
    //链表只有一个节点时
    if ((*pphead)->next == NULL)
    {
        free(*pphead);
        *pphead = NULL;
    }
    else
    {
        //有多个节点时
        SLTNode* prev = *pphead;
        SLTNode* ptail = *pphead;
        while (ptail->next)
        {
            prev = ptail;
            ptail = ptail->next;
        }
        free(ptail);
        ptail = NULL;
        prev->next = NULL;
    }
}


http://www.kler.cn/a/524206.html

相关文章:

  • Unity 粒子特效在UI中使用裁剪效果
  • 17.Word:李楠-学术期刊❗【29】
  • 17 一个高并发的系统架构如何设计
  • 【每日一A】2015NOIP真题 (二分+贪心) python
  • 3、C#基于.net framework的应用开发实战编程 - 实现(三、三) - 编程手把手系列文章...
  • RocketMQ原理—5.高可用+高并发+高性能架构
  • 玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
  • 19.Word:小马-校园科技文化节❗【36】
  • QT+mysql+python 效果:
  • 八种排序算法【C语言实现】
  • 代码随想录| 动态规划188.买卖股票的最佳时机IV 309.最佳买卖股票时机含冷冻期 714.买卖股票的最佳时机含手续费
  • 技术发展视域下中西方技术研发思维方式的比较与启示
  • 传奇引擎游戏微端的作用
  • 5分钟带你获取deepseek api并搭建简易问答应用
  • AI工具灵感速递:离线ChatGPT×自然语言全栈开发×智能文件重命名,开发者效率革命!
  • DeepSeek-R1:开源Top推理模型的实现细节、使用与复现
  • 【华为OD-E卷 - 字符串解密 100分(python、java、c++、js、c)】
  • 52. TCP四次挥手
  • 动态规划<九>两个数组的dp
  • 基于SpringBoot电脑组装系统平台系统功能实现六
  • PHP If...Else 语句详解
  • 高级java每日一道面试题-2025年01月23日-数据库篇-主键与索引有什么区别 ?
  • HTML特殊符号的使用示例
  • Vue5---
  • 平衡三进制计算机基础构想
  • 单片机开发——定时器(基于51)