单链表和双链表 \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的结点。
目标:将红色结点插入到红色指针后面。
①. 更新红色结点的左右指针值:![请添加图片描述](https://i-blog.csdnimg.cn/blog_migrate/109b97d3b335fce3b25edbd76de76796.png)
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
k−1,因为在静态链表中第
k
k
k结点与第
k
−
1
k - 1
k−1结点的存储位置不一定是连续的。
3. 删除第 k k k个结点
以 k k k结点为中心重新调换前后结点的指针即可。
void move(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}