【数据结构】双链表

一、双链表的基本结构

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];

与单链表相比,双链表增加了:

  1. tail:tail是尾节点的下标(即尾节点的位置),根据tail我们可以找到从数组的哪里开始访问

  2. 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指针指向的节点,vali 即单链表中第 i 个节点的值

十、逆序遍历双链表

cpp 复制代码
// 逆序遍历
void traverseBackward() {
    for (int i = tail; i != -1; i = prv[i]) {
        cout << val[i] << " ";
    }
}

理解:从尾节点的位置开始遍历,直到遍历到空节点循环结束,每循环一次 i 都指向双链表中倒数第 i 个节点的prev指针指向的节点,vali 即单链表中第 i 个节点的值

相关推荐
Darling噜啦啦2 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
小小工匠3 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
玖玥拾3 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
Qres8213 天前
算法复键——树状数组
数据结构·算法
牛油果子哥q4 天前
并查集(DSU)超精讲,路径压缩、按秩合并、万能模板、连通性判定、最小生成树与刷题实战全解
数据结构·c++·最小生成树·并查集
凌波粒4 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
WL学习笔记4 天前
单项不带头不循环链表
数据结构·链表
小糯米6014 天前
JS 数组
数据结构·算法·排序算法
小欣加油4 天前
leetcode3612 用特殊操作处理字符串I
数据结构·c++·算法·leetcode·职场和发展
凌波粒4 天前
LeetCode--90.子集II(回溯算法)
数据结构·算法·leetcode