一、双链表的基本结构
cpp
const int N = 1e5+11;
// head: 表示头节点的索引
// tail: 表示尾节点的索引
// idx: 表示当前节点索引
// val[i]: 表示节点i的值
// prv[i]: 表示节点i的prev指针
// nxt[i]: 表示节点i的next指针
int head, tail, idx;
int val[N], prv[N], nxt[N];
与单链表相比,双链表增加了:
-
tail:tail是尾节点的下标(即尾节点的位置),根据tail我们可以找到从数组的哪里开始访问
-
prv:prv存储每个位置的prev指针(存储了上一个节点的下标,若为-1则代表没有前驱节点,即该节点为链表的第一个节点)
二、初始化双链表
cpp
// 初始化
void init()
{
head = -1; // 表示链表是空集
tail = -1;
idx = 0; // 表示从第0个节点开始
}
三、判断双链表是否为空
cpp
// 判断链表是否为空
bool isEmpty()
{
return head == -1;
}
理解:若头节点的下标为-1,说明还没有添加过节点,即链表为空
四、头插法添加节点
cpp
// 在链表头部添加节点n
void addHead(int n)
{
// 新节点赋值
val[idx] = n;
prv[idx] = -1; // 新头节点无前驱节点
nxt[idx] = head; // 新头节点的后继为原头节点
if (!isEmpty()) {
prv[head] = idx; // 链表不为空: 原头节点的前驱指向新节点
} else {
tail = idx; // 链表为空: 新节点也是尾节点
}
head = idx; // 更新头节点
idx++; // 索引自增
}
理解:将新插入的节点变为头节点,然后将新节点的next指针指向原头节点;因为新节点已经是第一个节点,所以没有前驱节点,因此prev指针为-1
五、尾插法添加节点
cpp
// 在链表尾部添加节点n
void addTail(int n)
{
// 新节点赋值
val[idx] = n;
prv[idx] = tail; // 新尾节点前驱为原尾节点
nxt[idx] = -1; // 新尾节点无后继节点
if (!isEmpty()) {
nxt[tail] = idx; // 链表不为空: 原尾节点的后继指向新节点
} else {
head = idx; // 链表为空: 新节点也是头节点
}
tail = idx; // 更新尾节点
idx++; // 索引自增
}
理解:将新插入的节点变为尾节点,然后将新节点的prev指针指向原尾节点;因为新节点已经是最后一个节点,所以没有后继节点,因此next指针为-1
六、在目标位置后插入节点
cpp
// 在pos后插入new节点
void insertNext(int pos, int n)
{
val[idx] = n;
prv[idx] = pos;
nxt[idx] = nxt[pos];
if (nxt[pos] != -1) {
prv[nxt[pos]] = idx; // pos位置不是尾节点: pos的后继节点的前驱指向新节点
} else {
tail = idx; // pos位置是尾节点: 将新节点标记为尾节点
}
nxt[pos] = idx;
idx++;
}
理解:为新节点赋值,更新前驱节点和后继节点;为目标位置的节点更新后继节点;为原目标位置的后继节点更新前驱节点。同时注意边界问题,处理新节点是尾节点的情况
七、在目标位置前插入节点
cpp
// 在pos前插入new节点
void insertPrev(int pos, int n)
{
val[idx] = n;
prv[idx] = prv[pos];
nxt[idx] = pos;
if (prv[pos] != -1) {
nxt[prv[pos]] = idx;
} else {
head = idx;
}
prv[pos] = idx;
idx++;
}
理解:为新节点赋值,更新前驱节点和后继节点;为目标位置的节点更新前驱节点;为原目标位置的前驱节点更新后继节点。同时注意边界问题,处理新节点是头节点的情况
八、将目标位置的节点删除
cpp
// 将pos位置的节点删除
void remove(int pos)
{
if (prv[pos] == -1) { // 若pos位置的节点为头节点
head = nxt[pos]; // 更新头节点指针
} else { // 若不是头节点,说明有前驱节点
nxt[prv[pos]] = nxt[pos]; // 更新前驱节点的后继节点
}
if (nxt[pos] == -1) { // 若pos位置的节点为尾节点
tail = prv[pos]; // 更新尾节点指针
} else { // 若不是尾节点,说明有后继节点
prv[nxt[pos]] = prv[pos]; // 更新后继节点的前驱节点
}
}
理解:根据目标位置的节点在链表中的位置,更新前驱节点的后继节点和后继节点的前驱节点
九、顺序遍历双链表
cpp
// 顺序遍历
void traverseForward() {
for (int i = head; i != -1; i = nxt[i]) {
cout << val[i] << " ";
}
}
理解:从头节点的位置开始遍历,直到遍历到空节点循环结束,每循环一次 i 都指向双链表中第 i 个节点的next指针指向的节点,val[i] 即单链表中第 i 个节点的值
十、逆序遍历双链表
cpp
// 逆序遍历
void traverseBackward() {
for (int i = tail; i != -1; i = prv[i]) {
cout << val[i] << " ";
}
}
理解:从尾节点的位置开始遍历,直到遍历到空节点循环结束,每循环一次 i 都指向双链表中倒数第 i 个节点的prev指针指向的节点,val[i] 即单链表中第 i 个节点的值