单链表和双链表笔记

单链表和双链表 \color{red}{\huge{单链表和双链表}} 单链表和双链表

背景

提到链表首先想到的应该是:

struct Node
{
    int val;
    Node *next
};

这种使用结构体来表示的链表看起来非常的直观,但是有一个致命的缺点:调用了 N o d e Node Node的构造函数。 N o d e Node Node函数的时间复杂度非常的高,如果是平时的工程,对时间复杂度的要求没有那么高,使用 N o d e Node Node一般没有关系,而且利于后期的代码维护。但是如果在编程题中使用 N o d e Node Node,因为它的复杂度太高,很有可能使用它所构造的链表的时候,仅仅是构造链表就已经超过时间了,更不用提操作了,所以此处提到的链表都是使用数组进行模拟的静态链表,只是通过改变数组的下标来模拟指针指向的改变,能够大大提高运行速度。

用途框架

请添加图片描述

单链表

构成组件

请添加图片描述

上图是数组模拟单链表的结构示意图,从图中可以看出,想要完整的对一个链表进行模拟,需要以下几个组件:
①. 原数据数组 e [ N ] e[N] e[N]: 用来存放要存储的数据。
②. 下标表示数组 n e [ N ] ne[N] ne[N]:用来存放对应点后面一个点的下标的数组,注意 n e [ N ] ne[N] ne[N]中存放的是下标值。
③. 头指针 h e a d head head:用于标明链表开始的结点。
④. i d x idx idx:用于存放下一个能够使用的结点的地址
❗❗❗❗ i d x idx idx非常重要,不单是静态链表,后面一些线性数组模其他数据结构的时候也会有涉及

基本操作

1. 链表初始化
void init()
{
    head = -1;
    idx = 0;
}

静态链表的初始化将头指针 h e a d head head指向 − 1 -1 1 i d x idx idx初始化为 0 0 0即可。

2. 头插法

请添加图片描述

目标:将图中红色的点插入到原静态链表的头部,更改 h e a d head head
首先将要赋的值赋值给该红色结点,之后该红色结点的后继修改成 h e a d head head指向的位置,最后修改 h e a d head head指针即可。

void add_to_head(int x)
{
    e[idx] = x;                 //idx就是下一个能够使用的位置的下标
    ne[idx] = head;
    head = idx;                 //修改head的值指向新的头节点
    idx++;                  //更新idx的值,指向下一个能够使用的新的结点位置
}
3. 在下标为 k k k的结点之后添加一个结点

与步骤 2 2 2近乎完全一致,唯一改变的就是更新的时候更新的是 k k k结点。

void add(int k,int x)
{
    e[idx] = x;
    ne[idx] = ne[k];
    ne[k] = idx;
    idx++;
}
4. 删除下标为 k k k的结点之后的一个结点

单项链表的结构就决定了,删除已知点之后的结点操作非常简单,但是删除之前的结点非常困难。

void remove(int k)
{
    ne[k] = ne[ne[k]];
}

双链表

双链表就是双向链表,可以两个方向进行遍历。

构成组件

请添加图片描述

在图中可以看出双链表的构成组件为:
①. 原数据数组 e [ N ] e[N] e[N]
②. 存储对应下标结点右侧结点下标的数组 r [ N ] r[N] r[N]
③. 存储对应下标结点左侧结点下标的数组 l [ N ] l[N] l[N]
④. i d x idx idx存放下一个能够使用的下标的地址值。

基本操作

1. 初始化

这里双向链表的初始化的时候,不只是初始化一个值,而是初始化一个结构。请添加图片描述

图中就是双向链表的初始结构, h e a d head head指向的就是左侧结点, t a i l tail tail指针指向的就是右侧结点。

void init()
{
    l[1] = 0;   //右侧指向左侧
    r[0] = 0;   //左侧指向右侧
    idx = 2;    //前两个结点都使用过了,idx从2开始使用
}
2. 在第 k k k个结点右边插入一个值为 x x x的结点。

请添加图片描述

目标:将红色结点插入到红色指针后面。

①. 更新红色结点的左右指针值:请添加图片描述
l[idx] = k;
r[idx] = r[k];
②. 重新调整 k k k r [ k ] r[k] r[k]和红色结点三者指针关系

请添加图片描述

l[r[k]] = idx;
r[k] = idx;

❗❗❗❗这里两条语句千万不能够写反了,不能够先更新 r [ k ] r[k] r[k]这样 r [ k ] r[k] r[k]指向的结点就丢失了
代码总和

void add(int k,int x)
{
    e[idx] = x;
    l[idx] = k;
    r[idx] = r[k];
    l[r[k]] = idx;
    r[k] = idx;
    idx++;
}
删除第 k k k结点的左侧结点

按照删除右侧结点的思路也可以写出来,不过可以复用删除右侧的方法。
删除 k k k结点的左侧结点就是在 l [ k ] l[k] l[k]的右侧插入结点,直接调用 a d d ( l [ k ] , x ) add(l[k],x) add(l[k],x)就可以了。
❗❗❗❗这里一定要写 l [ k ] l[k] l[k],而不能够写 k − 1 k-1 k1,因为在静态链表中第 k k k结点与第 k − 1 k - 1 k1结点的存储位置不一定是连续的。

3. 删除第 k k k个结点

k k k结点为中心重新调换前后结点的指针即可。

void move(int k)
{
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值