数据结构顺序表和链表(中)

void SLTPopBack(SLTNode** pphead)

大家好,我是weipyh,一个C++的初学者

上期我们讲了顺序表的内容,这期接下来讲单链表

3.单链表

3.1概念与结构

概念:链表是⼀种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的

逻辑结构一定是线性的;物理结构大多数情况下不是线性的

3.11结点(节点)

链表中的每个结点都是独立申请空间,每个结点储存两部分信息:

  1. 数据域(Data):用于存储实际的数据。

  2. 指针域(Pointer):用于存储指向下一个节点的指针(或地址)。

结点通过指针变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。
3.12链表的性质
1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续
2、结点⼀般是从堆上申请的
3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续
// 定义单链表节点数据类型
typedef int SLTDataType;

// 定义单链表节点结构体
typedef struct SListNode
{
    SLTDataType data;        // 数据域,用于存储节点的数据
    struct SListNode* next;  // 指针域,用于指向下一个节点
} SLTNode;
当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数
据,也需要保存下⼀个结点的地址(当下⼀个结点为空时保存的地址为空)。 当我们想要从第⼀个结点⾛到最后⼀个结点时,只需要在当前结点拿上下⼀个结点的地址就可以了。
3.13链表的打印

初始化链表

void test01()
{
    // 创建四个节点
    SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));  // 创建第一个节点
    SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));  // 创建第二个节点
    SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));  // 创建第三个节点
    SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));  // 创建第四个节点

    // 设置节点的数据
    node1->data = 1;  // 第一个节点数据为1
    node2->data = 2;  // 第二个节点数据为2
    node3->data = 3;  // 第三个节点数据为3
    node4->data = 4;  // 第四个节点数据为4

    // 链接节点,形成单链表
    node1->next = node2;  // 第一个节点指向第二个节点
    node2->next = node3;  // 第二个节点指向第三个节点
    node3->next = node4;  // 第三个节点指向第四个节点
    node4->next = nullptr;  // 第四个节点的指针域为空,表示链表结束

    // 使用指针 plist 指向链表的头节点
    SLTNode* plist = node1;  // plist 是链表的头指针,指向第一个节点

    // 调用 SLTPrint 函数打印链表
    SLTPrint(plist);  // 假设 SLTPrint 是一个用于打印链表的函数
}
SLTNode* pcur = phead;  // 定义一个指针 pcur,初始化为链表的头节点 phead
while (pcur)           // 遍历链表,直到 pcur 为 nullptr(链表结束)
{
    printf("%d -> ", pcur->data);  // 打印当前节点的数据
    pcur = pcur->next;             // 将指针移动到下一个节点
}
printf("nullptr\n");  // 打印链表结束标志

3.2单链表的实现

  SLTNode* SLTBuyNode(SLTDataType); //创建并初始化新结点

// 创建一个新的单链表节点,并初始化其数据
SLTNode* SLTBuyNode(SLTDataType x)
{
    // 分配内存空间给新节点
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    
    // 检查内存分配是否成功
    if (newnode == nullptr)
    {
        // 如果内存分配失败,打印错误信息并退出程序
        perror("malloc fail");
        exit(1);
    }
    
    // 初始化新节点的数据域
    newnode->data = x;
    
    // 初始化新节点的指针域为 nullptr,表示当前没有后续节点
    newnode->next = nullptr;
    
    // 返回新创建的节点
    return newnode;
}
  void SLTPushBack(SLTNode** phead, SLTDataType x);// 尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
    // 创建一个新节点并初始化
    SLTNode* newnode = SLTBuyNode(x);

    // 如果链表为空,直接将新节点设置为头节点
    if (*pphead == nullptr)
    {
        *pphead = newnode;
    }
    // 如果链表不为空,找到尾节点并将新节点链接到尾部
    else
    {
        SLTNode* ptail = *pphead;  // 从头节点开始
        while (ptail->next)  // 找到尾节点
        {
            ptail = ptail->next;
        }
        ptail->next = newnode;  // 将新节点链接到尾部
    }
}

  void SLTPushFront(SLTNode** pphead, SLTDataType x);//头插

​#include <assert.h>  // 引入 assert 宏

// 在单链表头部插入一个新节点
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    // 断言检查:确保传入的二级指针非空
    assert(pphead);

    // 创建并初始化新节点
    SLTNode* newnode = SLTBuyNode(x);

    // 将新节点的 next 指针指向当前的头节点
    newnode->next = *pphead;

    // 更新头指针,使其指向新节点
    *pphead = newnode;
}
​

  void SLTPopBack(SLTNode** pphead) //尾删

void SLTPopBack(SLTNode** pphead)
{
    // 断言检查:确保传入的二级指针非空且链表不为空
    assert(pphead && *pphead);

    // 如果链表只有一个节点,直接释放该节点并清空头指针
    if ((*pphead)->next == nullptr)
    {
        free(*pphead);  // 释放头节点
        *pphead = nullptr;  // 清空头指针
    }
    else
    {
        // 如果链表有多个节点,找到倒数第二个节点和最后一个节点
        SLTNode* prev = nullptr;  // 初始化前驱节点指针为nullptr
        SLTNode* ptail = *pphead; // 初始化尾节点指针为头节点

        while (ptail->next)  // 遍历链表直到最后一个节点
        {
            prev = ptail;  // 更新前驱节点
            ptail = ptail->next;  // 移动尾节点指针到下一个节点
        }

        // 删除最后一个节点
        prev->next = nullptr;  // 将前驱节点的next指针设为nullptr,断开与尾节点的连接
        free(ptail);  // 释放最后一个节点的内存
        ptail = nullptr;  // 防止悬空指针,将尾节点指针设为nullptr
    }

  void SLTPopFront(SLTNode** pphead); //头删

void SLTPopFront(SLTNode** pphead)
{
    // 断言检查:确保传入的二级指针非空且链表不为空
    assert(pphead && *pphead);

    // 保存下一个节点的地址,以便后续操作
    SLTNode* next = (*pphead)->next;

    // 释放当前头节点的内存
    free(*pphead); 

    // 更新头指针,使其指向下一个节点
    *pphead = next;
}

  SLTNode* SLTFindNode(SLTNode* phead, SLTDataType x) //查找

// 在链表中查找值为 x 的节点
SLTNode* SLTFindNode(SLTNode* phead, SLTDataType x)
{
    SLTNode* pcur = phead; // 初始化当前节点指针为头节点
    while (pcur)          // 遍历链表直到末尾(nullptr)
    {
        if (pcur->data == x) // 如果当前节点的数据等于 x
        {
            return pcur;    // 返回当前节点
        }
        pcur = pcur->next;  // 移动到下一个节点
    }
    return nullptr;        // 如果没有找到,返回 nullptr
}

  void SLTInsert(SLTNode** pphead, SLTNode * pos, SLTDataType x); //指定位置之前插入数据

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
    // 断言检查:确保传入的二级指针非空且链表不为空,且 pos 节点在链表中
    assert(pphead && *pphead);
    assert(pos);

    // 如果 pos 是头节点,直接在头部插入新节点
    if (pos == *pphead)
    {
        SLTPushFront(pphead, x);
    }
    else 
    {
        // 创建并初始化新节点
        SLTNode* newnode = SLTBuyNode(x);

        // 找到 pos 节点的前驱节点
        SLTNode* prev = *pphead;
        while (prev->next != pos)
        {
            prev = prev->next;
            // 确保 prev 不会越界,即 pos 节点确实在链表中
            assert(prev != nullptr);
        }

        // 将新节点插入到 pos 节点之前
        newnode->next = pos;
        prev->next = newnode;
    }
}

  void SLTErase(SLTNode** pphead, SLTNode* pos); // 指定位置删除结点

// 在链表中删除指定节点 pos
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
    // 断言检查:确保 pos 节点和传入的二级指针非空,且链表不为空
    assert(pos && pphead && *pphead);

    SLTNode* prev = *pphead;  // 初始化前驱节点指针为头节点
    // 遍历链表找到 pos 节点的前驱节点
    while (prev->next != pos)
    {
        prev = prev->next;
        // 确保 prev 不会越界,即 pos 节点确实在链表中
        assert(prev != nullptr);
    }

    // 删除 pos 节点
    prev->next = pos->next;  // 将前驱节点的 next 指针指向 pos 节点的下一个节点
    free(pos);               // 释放 pos 节点的内存
    pos = nullptr;           // 防止悬空指针,将 pos 指针设为 nullptr
}

  void SLTInsertAfter(SLTNode* pos, SLTDataType x); //指定位置之后插入数据

// 在链表中指定节点 pos 之后插入一个新节点
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
    // 断言检查:确保 pos 节点非空
    assert(pos);

    // 创建并初始化新节点
    SLTNode* newnode = SLTBuyNode(x);

    // 将新节点插入到 pos 节点之后
    newnode->next = pos->next;  // 新节点的 next 指针指向 pos 节点的下一个节点
    pos->next = newnode;       // 将 pos 节点的 next 指针指向新节点
}

  void SLTEraseAfter(SLTNode* pos); //删除pos之后的结点

// 在链表中删除指定节点 pos 之后的节点
void SLTEraseAfter(SLTNode* pos)
{
    // 断言检查:确保 pos 节点非空且 pos 之后有节点
    assert(pos && pos->next);

    // 保存要删除的节点
    SLTNode* del = pos->next;

    // 将 pos 节点的 next 指针指向 del 节点的下一个节点,从而跳过 del 节点
    pos->next = pos->next->next; // (pos.next = del.next)

    // 释放要删除的节点的内存
    free(del);

    // 防止悬空指针,将 del 指针设为 nullptr
    del = nul

  void SListDestroy(SLTNode** pphead); //销毁链表

// 销毁整个单链表,释放所有节点的内存
void SListDestroy(SLTNode** pphead)
{
    // 断言检查:确保传入的二级指针非空
    assert(pphead);

    // 初始化当前节点指针为头节点
    SLTNode* pcur = *pphead;
    // 遍历链表直到末尾(nullptr)
    while (pcur != nullptr)
    {
        // 保存下一个节点的地址,以便后续操作
        SLTNode* next = pcur->next;
        // 释放当前节点的内存
        free(pcur);
        // 移动到下一个节点
        pcur = next;
    }
    // 将头指针设为 nullptr,表示链表已被销毁
    *pphead = nullptr;
}

   这篇的内容就到这里了,欢迎大家讨论,感谢观看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值