【数据结构】双链表

一、双链表的基本结构

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

十、逆序遍历双链表

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

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

相关推荐
设计师小聂!1 小时前
力扣热题100-------169.多数元素
java·数据结构·算法·leetcode·多数元素
艾莉丝努力练剑1 小时前
【数据结构与算法】顺序表和链表、栈和队列、二叉树、排序等数据结构的完整代码收录
c语言·数据结构·学习·链表
啊阿狸不会拉杆2 小时前
《算法导论》第 3 章 - 函数的增长
开发语言·数据结构·c++·算法
Aczone282 小时前
数据结构(三)双向链表
java·数据结构·链表
无敌的大魔王2 小时前
数据结构 实现单链表
数据结构
MSXmiao4 小时前
2048小游戏
数据结构·c++·算法
钮钴禄·爱因斯晨5 小时前
数据结构 | 树的秘密
c语言·开发语言·数据结构
2301_763994716 小时前
c++11特性
数据结构·c++·算法
蓝澈11217 小时前
链表之leetcode19:删除链表的倒数第N个结点
数据结构·链表