双链表 -- 带头 双向 循环 链表

链表 总共可以 排列组合 8种 2*2*2 是否带头

但是只要学会了 单链表 和 双向链表 就 一通百通了,因为是相关的,两个都是对应的极端 只需要在此基础上 稍微加 减 一个部分即可

是否双向 前后 都有指针 只看有无 两个指针 一个指向前一节点

另一个 指向另一个节点

是否循环 首尾相连 在实现任意一种 数据结构时 优先是实现该结构的 结构体

双向链表的 结构体就是 这样子组成的 需要有两个指针域 一个类似单链表 指向下个节点的指针 next

另一个指向 前一个节点的指针 prev

定义结构

typedef int LTDataType;

//定义双向链表节点的结构
typedef struct ListNode
{
LTDataType data;
struct ListNode* next;
struct ListNode* prev;
}LTNode;

本节 主要学习 双链表的 创建新节点 初始化 增删查改 打印

头插 尾插 头删 尾删 打印 任意位置插入 清空 查找

1.创建新节点 并 初始化

当两种链表 为空时

去申请节点

c 复制代码
LTNode* LTBuyNode(LTDataType x)
{
    LTNode* node = (LTNode*)malloc(sizeof(LTNode));
    if (node == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }
    node->data = x;
    node->next = NULL;
    node->prev = NULL;
    return node;
}

主要 节点的 next 和 prev指针 该怎么指向呢?

//初始化 参数的形式

//void LTInit(LTNode** pphead)

//{

// //给双向链表创建一个哨兵位

// *pphead = LTBuyNode(-1);

//}

注释的是第一种写法 通过二级指针接收 直接修改头节点

LTNode* LTInit()

{

LTNode* phead = LTBuyNode(-1);

return phead;

}

第二种写法 返回值 返回

就是通过返回值,把新创建的哨兵位头结点地址交给调用者,从而让调用者的头指针指向它,实现初始化的效果。

2.简单介绍 哨兵位

超级认识 不可修改地址 不可以删除 (设定)

带头 就是因为带了这个头节点

之前我们在单链表的实现时 将第一个存储有效数据的 节点叫做 头节点 但是其实是不准确的 应该叫做 首节点的

哨兵位 又叫做 哑节点 不存放有效数据 的 辅助节点

将其作为一个哨兵一样 永远存在于链表的最前面 站岗

当然不是 有头 所以就是 带头 有两个指针 双向 但是 不循环

所以我们要让其循环起来 怎们办 ? 直接让其自循环 起来 就

3 .独家理解 就是 每个节点满足 该结构的 逻辑 双向 带头 循环

让其两个指针都指向 该节点 即可

百万撤离 --- 理解相当于每个节点都有 两只手

4.尾插 代码展示 可复制

c 复制代码
void LTPushBack(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* newnode = LTBuyNode(x);

    newnode->prev = phead->prev;
    newnode->next = phead;

    phead->prev->next = newnode;
    phead->prev = newnode;
}
 

观察 变化

先修改 newnode的两只手 的指向 让它的prev指向前一个 结构体指针

让它的next指针指向 哨兵位 头节点 1 和 2

再进行下一步操作喽 另外两条线路的解释 3 4

复制代码
  phead->prev->next = newnode;

phead->prev//这个就是原来的尾节点  

3 要这样子理解 通过引用后 得到的尾部节点 再引用改变后面一个指向 改为 新的节点 对应关系

4 改变哨兵位的前指针的指向

5.打印双链表 肯定要循环 主要是循环结束条件的控制

不是while(pcur != NULL ) 因为后面根本就没有 NULL

但是再双链表中有个特殊的节点叫做 哨兵位

c 复制代码
void LTPrint(LTNode* phead)
{
    LTNode* pcur = phead->next;
    while (pcur != phead)
    {
        printf("%d->", pcur->data);
        pcur = pcur->next;
    }
    printf("\n");
}
 

LTNode* pcur = phead->next;

别弄错了 要指向第一个有效节点 首节点 注意一下细节

6.头插 看连环画 你就会了

c 复制代码
void LTPushFront(LTNode* phead, LTDataType x)
{
    assert(phead);
    LTNode* newnode = LTBuyNode(x);

    newnode->next = phead->next;
    newnode->prev = phead;

    phead->next->prev = newnode;
    phead->next = newnode;
}
 

phead->next->prev = newnode;等价于

d1 -> prev = newnode;就是右边的绿色的线

你要计算一步看才更好的理解
phead->next = newnode;

这两行代码不能完全 交换位置

复制代码
    phead->next = newnode;
newnode->next->prev = newnode;

思考一下 交换位置 主要是第二句话 等价于 newnode->prev 指向新节点 就不是 让d1指向新节点,找不到了

**但是 可以有另外一种写法 (精妙)

phead->next = newnode;
newnode->next->prev = newnode;
就可以 之间的关系 是先利用 newnode 与 d1之间关系 找到 d1然后在 改变前指针的指向**

删除之前 要判断 双向链表是否为空

结合之前说的 双向 链表为空时 只剩下 一个头 哨兵位

7.尾删 如果只有一个哨兵位还删除个屁 没有地方删除了

c 复制代码
void LTPopBack(LTNode* phead)
{
    // 检查链表有效性及非空性(不能只有哨兵位)
    assert(phead && phead->next != phead);

    // 获取要删除的尾节点
    LTNode* del = phead->prev;
    
    // 调整指针关系
    del->prev->next = phead;
    phead->prev = del->prev;

    // 释放节点内存
    free(del);
    del = NULL;
}
 

同样的需要找尾节点 在双链表中 直接就是 哨兵位的前一节点的就是 尾部节点

重点理解 del 的运用就是 保留 原来的尾部节点的地址 然后 再删除 ,解决的是,改变指向后 找不到原来的尾部节点的 情况

8.头删 主要修改 两个 节点 主要看哪一个手 ,指针要改变

总的只有 一个节点的一边的指针指向 看看那个受到影响

只有 红绿 的 修改 别想多了 做都是方法

其次是 利用del 的临时变量 保存要删除的数据

删除的操作就是直接跳过该节点就好了

9.在指定位置之后插入

9.1得先找到 这个位置 需要遍历 查找 对比

c 复制代码
LTNode* LTFind(LTNode* phead, LTDataType x)
{
    LTNode* pcur = phead->next;
    while (pcur != phead)
    {
        if (pcur->data == x)
        {
            return pcur;
        }
        pcur = pcur->next;
    }
    return NULL;
}
 

插入的连环画

代码如图

c 复制代码
// 在pos位置之后插入数据
void LTInsert(LTNode* pos, LTDataType x)
{
    assert(pos);

    LTNode* newnode = LTBuyNode(x);
    // pos newnode pos->next
    newnode->next = pos->next;
    newnode->prev = pos;

    pos->next->prev = newnode;
    pos->next = newnode;
}
 

pos->next->prev = newnode;

别忘了 可以替换为 newnode->next->prev = newnode;

因为前面 已经改变了 新节点的指向 所以 只要能找到 对应的节点 再改变 对应的指向 就ok

10.在指定位置删除 主要 就是 搞清楚 到底哪几个会变动 然后通过 改变 指向 来跳过 就 删除了 就 前面 后面 本来位置 之间的关系 保持 链条 而且是 双链表的特点

11 清空链表 销毁

重点 这个 的问题是要在主函数里面 手动将 传入的首节点指针 置为空 NULL

c 复制代码
void LTDesTroy(LTNode** pphead) 
{
    if (*pphead == NULL) return;

    LTNode* pcur = (*pphead)->next;
    while (pcur != *pphead)
    {
        LTNode* next = pcur->next;
        free(pcur);
        pcur = next;
    }
    
    free(*pphead);
    *pphead = NULL;
}
 

世纪同框 完结撒花

相关推荐
eggrall2 小时前
Leetcode 将 x 减到 0 的最小操作数
数据结构
Lazionr2 小时前
【链表经典OJ-上】
c语言·数据结构·链表
programhelp_2 小时前
TikTok 26 Summer SDE Intern 面经分享|两轮技术面 + Timeline 复盘
数据结构·经验分享·算法·面试
无限进步_2 小时前
二叉树的前序遍历(非递归实现)
开发语言·数据结构·c++·windows·git·visual studio
01二进制代码漫游日记2 小时前
【C语言数据结构】之解锁双向链表(头插、头删等操作)
c语言·数据结构·学习·链表
WL_Aurora3 小时前
每日一题——自然倍树
数据结构·python·算法·深度优先
Lazionr3 小时前
【链表经典OJ-中】
c语言·数据结构·链表
一江寒逸3 小时前
数据结构与算法之美:绪论——构建算法思维的基石
数据结构·算法
可乐要加冰^-^3 小时前
Vscode、Pycharm快速配置Claude、CodeX
数据结构·深度学习·算法·语言模型·自动驾驶