算法模板之双链表图文详解

文章目录

  • 📋前言
  • [一. ⛳️使用数组模拟双链表讲解](#一. ⛳️使用数组模拟双链表讲解)
    • [1.1 🔔为什么我们要使用数组去模拟双链表?](#1.1 🔔为什么我们要使用数组去模拟双链表?)
    • [1.2 🔔用数组模拟实现双链表](#1.2 🔔用数组模拟实现双链表)
      • [1.2.1 👻整体框架说明](#1.2.1 👻整体框架说明)
      • [1.2.2 👻双链表查找和修改](#1.2.2 👻双链表查找和修改)
      • [1.2.3 👻双链表插入结点](#1.2.3 👻双链表插入结点)
      • [1.2.4 👻双链表删除结点](#1.2.4 👻双链表删除结点)
    • [1.3 🌟模板提取(重点)🌟](#1.3 🌟模板提取(重点)🌟)
      • [1.3.1 👻有详细注释版](#1.3.1 👻有详细注释版)
      • [1.3.1 👻无详细注释版](#1.3.1 👻无详细注释版)
  • [二. ⛳️题目练习](#二. ⛳️题目练习)

📋前言

🏠 个人主页:@聆风吟的个人主页

🔥系列专栏:本期文章收录在专栏《算法模板》中,大家有兴趣可以浏览和关注,后面将会持续更新更多精彩内容!

⏰寄语:少年有梦不应止于心动,更要付诸行动。

🎉欢迎大家关注🔍点赞👍收藏⭐️留言📝

🌈作者留言:文章创作不易,可能会有些地方出现错误,还希望广大读者们能够帮忙指出,让我们大家一起共同进步。


一. ⛳️使用数组模拟双链表讲解

1.1 🔔为什么我们要使用数组去模拟双链表?

由于该问题已经在第一期《算法模板之单链表讲解》这篇文章中已经叙述过了,相信看过第一期的小伙伴应该已经知道,在这里就不多阐述,感兴趣的小伙伴可以自行跳转浏览。

1.2 🔔用数组模拟实现双链表

1.2.1 👻整体框架说明

初始状态 :左边界结点指向右边界结点,右边界结点指向左边界结点

插入结点状态:

  • 创建数组valprene 分别存储某个结点的值以及它的前驱指针和后继指针;
  • 下标 0 和 1 分别存储边界结点;
  • 从下标 2 的位置开始插入结点;
  • 本文仅仅使用左右边界结点的指针,无需在意其中存的值。

综上所述,真正的结点相当于从下标为2的位置开始往后所有插入的数,左右边界结点仅起到一个指针的效果。如下图的3,4,5,6为真正的结点。

1.2.2 👻双链表查找和修改

因为是使用数组模拟出来的链表,所以对于查找和修改直接通过数组下标进行遍历查找即可,这里就不多叙述。

1.2.3 👻双链表插入结点

在第k个结点的右边插入一个数 x:如下图在第2个结点后面插入一个数 7。

代码展示(建议结合图示看注释):

cpp 复制代码
//在结点k的右边插入一个数x
void insert(int k, int x)
{
    //将待插值赋给新结点
    val[idx] = x;
    //将新结点分别指向插入位置的右结点和左结点
    ne[idx] = ne[k];
    pre[idx] = k;
    //将新结点的右边结点向左指向新结点
    pre[ne[k]] = idx;
    //将新结点的左边结点向右指向新结点
    ne[k] = idx;
    //更新结点索引
    idx++;
}

其他形式插入:在链表的最左端插入、最右端插入、在第k个结点的左边插入一个数 x

在其他位置插入,大家可以按照上面的方式自行模拟。不过,在算法竞赛中如果每种插入方式都模拟一下,显然是太浪费时间了。仔细观察可以发现,我们仍然可以使用上面的insert(int k, int x)函数实现各种位置的插入,只需要稍微变动一下传入的 k 值。

  1. 在第k个结点的左边插入一个数 x: 如下图,在第2个结点的左边插入一个数7,可以转化为在第1个结点后面插入一个数7,执行语句:insert(pre[k], x) 即可。

  2. 在链表的最左端插入一个数x: 如下图,在最左端插入一个数x,可以转化为在左边结点后面插入一个数x,执行 insert(0, x) 即可。

  3. 在链表的最右端插入一个数x: 如下图,在最右端插入一个数x,可以转化为在右边界结点的左结点后面插入一个数x,执行 insert(pre[1], x) 即可。

1.2.4 👻双链表删除结点

删除第k个结点

代码展示(建议结合图示看注释):

cpp 复制代码
//删除第k个结点
void remove(int k)
{
    //将待删除结点的左结点的后继指针指向待删除结点的右结点
    ne[pre[k]] = ne[k];
    //将待删除结点的右结点的前驱指针指向待删除结点的左结点
    pre[ne[k]] = pre[k];
}

1.3 🌟模板提取(重点)🌟

1.3.1 👻有详细注释版

模板代码

cpp 复制代码
// val[i] 表示结点i的值
// pre[i] 表示结点i的前驱指针
// ne[i] 表示结点i的后继指针
// idx 存储当前已经用到了哪个点,即记录当前下标位置
int val[N], pre[N], ne[N], idx;

//初始化
void init()
{
    //左边界结点指向右边界结点,右边界结点指向左边界结点
    ne[0] = 1;
    pre[1] = 0;
    //更新结点索引,因为下标0和1被左右边界结点占用。
    idx = 2;
}

//在结点k的右边插入一个数x
//只使用本函数可以通过改变k的值,实现其他形式的插入。
void insert(int k, int x)
{
    //将待插值赋给新结点
    val[idx] = x;
    //将新节点分别指向插入位置的右结点和左结点
    ne[idx] = ne[k];
    pre[idx] = k;
    //将新结点右边一节点向左指向新结点
    pre[ne[k]] = idx;
    //将新结点左边一节点向右指向新结点
    ne[k] = idx;
    //更新结点索引
    idx++;
}

//删除第k个结点
void remove(int k)
{
    //将待删除结点的左结点的后继指针指向待删除结点的右结点
    ne[pre[k]] = ne[k];
    //将待删除结点的右结点的前驱指针指向待删除结点的左结点
    pre[ne[k]] = pre[k];
}

1.3.1 👻无详细注释版

cpp 复制代码
int val[N], pre[N], ne[N], idx;

//初始化
void init()
{
    ne[0] = 1;
    pre[1] = 0;
    idx = 2;
}

//在结点k的右边插入一个数x
void insert(int k, int x)
{
    val[idx] = x;
    ne[idx] = ne[k];
    pre[idx] = k;
    pre[ne[k]] = idx;
    ne[k] = idx;
    idx++;
}

//删除第k个结点
void remove(int k)
{
    ne[pre[k]] = ne[k];
    pre[ne[k]] = pre[k];
}

二. ⛳️题目练习

⌈ 在线OJ链接,可以转至此处自行练习 ⌋ 说明:题目取自AcWing官网,由于题目需要付费OJ链接只有购买课程的人才可以使用,深感抱歉。后面尽量给大家多整理一些测试用例,方便大家自行测试。

题目:

输入样例:

10

R 7

D 1

L 3

IL 2 10

D 3

IL 2 7

L 8

R 9

IL 4 7

IR 2 2

输出样例:

8 7 7 3 2 9

c++代码:

cpp 复制代码
#include<iostream>

using namespace std;

const int N = 100010;
int val[N], pre[N], ne[N], idx;

//初始化
void init()
{
    ne[0] = 1;
    pre[1] = 0;
    idx = 2;
}

//在结点k的右边插入一个数x
void insert(int k, int x)
{
    val[idx] = x;
    ne[idx] = ne[k];
    pre[idx] = k;
    pre[ne[k]] = idx;
    ne[k] = idx;
    idx++;
}

//删除第k个结点
void remove(int k)
{
    ne[pre[k]] = ne[k];
    pre[ne[k]] = pre[k];
}

int main()
{
    int m;
    cin >> m;
    
    init();//切记:不要忘记进行初始化
    
    while(m--)
    {
        int k, x;
        string op;
        
        cin >> op;
        //判断执行哪种操作
        if(op == "L")//在链表的最左端插入x
        {
            cin >> x;
            insert(0, x);
        }
        else if(op == "R")//在链表的最右端插入x
        {
            cin >> x;
            insert(pre[1], x);
        }
        else if(op == "D")//把第k个插入的数删除
        {
            cin >> k;
            remove(k+1);//因为初始化从下标为2位置开始插入结点,所以第k插入数的下标为k+2-1
        }
        else if(op == "IL")//第k个插入的数左侧插入一个数
        {
            cin >> k >> x;
            insert(pre[k+1], x);
        }
        else//第k个插入的数右侧插入一个数
        {
            cin >> k >> x;
            insert(k+1, x);
        }
    }
    
    //打印链表
    for(int i = ne[0]; i != 1; i = ne[i]) cout << val[i] << " ";
    cout << endl;
    
    return 0;
}

今天的模板分享就到这里啦,你们都学会了吗?如果还有疑问的话请在评论区里多多提问,大家可以一起帮你解决,让我们共同进步。创作不易,如果对你有用的的话点个赞支持下作者,你们的支持是作者创作最大的动力。关注我不迷路,让我们下期不见不散✋✋。

相关推荐
2c237c62 小时前
[ 蓝桥 ·算法双周赛 ] 第 19 场 小白入门赛
算法·蓝桥云课·算法双周赛
猫武士水星2 小时前
微信步数C++
算法
这可就有点麻烦了2 小时前
强化学习笔记之【DDPG算法】
笔记·算法·机器学习
CocoaAndYy2 小时前
ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal原理及Demo
java·jvm·算法
gugugu.3 小时前
数据结构:AVL树
数据结构
大三觉醒push亡羊补牢女娲补天版3 小时前
数据结构之树(4)
数据结构
T0uken4 小时前
【QT Quick】C++交互:与QML类型转换
c++·qt·交互
程序猿阿伟4 小时前
《C++音频降噪秘籍:让声音纯净如初》
开发语言·c++·网络协议
极客小张4 小时前
基于STM32的智能家居语音控制系统:集成LD3320、ESP8266设计流程
c语言·stm32·物联网·算法·毕业设计·课程设计·语言识别
Tech_gis5 小时前
C++ 观察者模式
开发语言·c++·观察者模式