【数据结构】链表

本篇内容只讲采用数组模拟来链表!!!

一、前置知识

1. 链表本质

链表由一个个节点串联而成,每个节点包含两部分:

  • 数据域:存储当前节点的值
  • 指针域:存储下一个 / 上一个节点的位置

节点之间不要求连续内存,靠指针域关联,这是链表和数组最大的区别。

3. 数组模拟核心思想

两个数组 分别对应节点的数据域和指针域,用下标表示节点地址:

  • val[]:数据域,val[i] 表示下标为i的节点存储的值
  • next[]/prev[]:指针域,next[i] 表示下标为i的节点的下一个节点下标
  • 用变量记录头节点、尾节点、当前可用下标,管理整个链表

二、单链表

1. 单链表的结构

单链表每个节点只有一个指针域,只能向后遍历

结构如下:头节点 → 节点1 → 节点2 → ... → 尾节点 → 空

2.核心函数模版

初始化

链表初始状态:无头节点,无可用节点

cpp 复制代码
//初始化
void init(){
    head = -1;//指向空集
    idx = 0;//当前可用的节点的编号
}

在链表头部插入一个元素

步骤
  • 给新节点分配下标idx
  • 新节点的值 = 要插入的数
  • 新节点的 next 指向原头节点
  • 头节点更新为新节点下标
  • 可用下标 + 1
cpp 复制代码
//在头结点插入一个元素
void add_to_head(int x){
    //存储值
    val[idx] = x;
    next[idx] = head; //把当前这个节点指向原来头结点的指向
    head = idx;//指向这个节点
    idx++;//编号++
}

删除某个节点后面的一个元素

步骤:直接让第 k 个节点的 next,跳过它的下一个节点,指向下下个节点即可

cpp 复制代码
//删除第k个元素之后的一个元素
void delete_k(int k){
    next[k] = next[next[k]];
}

在某个节点后面插入一个元素

步骤
  • 分配新节点下标idx
  • 新节点 next 指向「第 k 个节点」的 next 节点
  • 「第 k 个节点」的 next 指向新节点
  • 可用下标 + 1

l

cpp 复制代码
//在某个节点之后插入一个元素
void add_to_k(int k,int x){
    val[idx] = x;
    next[idx] = next[k];
    next[k] = idx;
    idx++;
}

3.例题

题目描述(来源于AcWing)

思路

  • 数组模拟链表 = val 存值 + ne 存下一节点
  • 第 k 个插入节点 = 下标 k-1
  • 删除头节点 = head = ne [head]

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N = 100010;
int head;
int val[N];//存储值
int ne[N];//存储当前节点的下一个节点的位置
int idx;//表示当前用的结点


//初始化
void init(){
    head = -1;//指向空集
    idx = 0;//当前可用的节点的编号
}

//在头结点插入一个元素
void add_to_head(int x){
    //存储值
    val[idx] = x;
    ne[idx] = head; //把当前这个节点指向原来头结点的指向
    head = idx;//指向这个节点
    idx++;//编号++
}

//删除第k个元素之后的一个元素
void delete_k(int k){
    ne[k] = ne[ne[k]];
}

//在某个节点之后插入一个元素
void add_to_k(int k,int x){
    val[idx] = x;
    ne[idx] = ne[k];
    ne[k] = idx;
    idx++;
}

int main(){
    int m;
    cin>>m;
    //初始化
    init();
    while(m--){
        char c;
        cin>>c;
        if(c == 'H'){
            int x;
            cin>>x;
            //头插法
            add_to_head(x);
        }else if(c == 'D'){
            int k;
            cin>>k;
            if(k == 0){
                //k为0时删除头结点
                head = ne[head];
            }else{
                delete_k(k - 1);
            }
        }else if(c == 'I'){
            int k,x;
            cin>>k>>x;
            add_to_k(k - 1,x);
        }
    }
    
    //输出
    for(int i = head;i != -1; i = ne[i]){
        cout<< val[i] << " ";
    }
    return 0;
}

三、双链表

1. 双链表的结构

双链表每个节点有两个指针域前驱指针+后继指针可以向前 / 向后遍历,解决了单链表无法逆向查找的问题。

结构:头节点 ←→ 节点1 ←→ 节点2 ←→ ... ←→ 尾节点

2.核心函数模版

初始化

cpp 复制代码
//初始化
void init(){
    l[1] = 0;
    r[0] = 1;
    idx = 2;
}

在某个节点的右边插入一个元素

步骤
  • 分配新节点idx
  • 新节点左指针指向 k,右指针指向 k 的原右节点
  • k 的原右节点的左指针指向新节点
  • k 的右指针指向新节点
  • 可用下标 + 1
cpp 复制代码
//在下标k的右边插入一个元素
void add(int k,int x){
    val[idx] = x;
    r[idx] = r[k];
    l[idx] = k;
    l[r[k]] = idx;
    r[k] = idx;
    idx++;
}

删除某个节点

步骤:让 k 的前驱节点直接指向 k 的后继节点,跳过 k 即可

cpp 复制代码
//删除下标k的后一个元素
void delete(int k){
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

3.例题

题目描述

思路

  • 用三个数组 l、r、val 模拟双链表,0、1 为虚拟头尾节点
  • idx=2 从 2 开始分配真实数据节点
  • add(k,x):在 k 右边插入节点
  • delet(k):删除 k 节点
  • 题目第 k 个插入数 = 数组下标 k+1(你代码的核心对应规则)
  • 按规则完成 5 种操作,最后从左到右遍历输出

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N = 100010;
int l[N],r[N],val[N];
int idx;

//初始化
void init(){
    l[1] = 0;
    r[0] = 1;
    idx = 2;
}

//在下标k的右边插入一个元素
void add(int k,int x){
    val[idx] = x;
    r[idx] = r[k];
    l[idx] = k;
    l[r[k]] = idx;
    r[k] = idx;
    idx++;
}

//删除下标k的后一个元素
void delet(int k){
    r[l[k]] = r[k];
    l[r[k]] = l[k];
}

int main(){
    int m;
    cin>>m;
    init();
    while(m--){
        string c;
        cin>>c;
        if(c == "L"){
            int x;
            cin>>x;
            add(0,x);
        }else if(c == "R"){
            int x;
            cin>>x;
            add(l[1],x);
        }else if(c == "D"){
            int k;
            cin>>k;
            delet(k + 1);
        }else if(c == "IL"){
            int x,k;
            cin>>k>>x;
            add(l[k + 1],x);
        }else if(c == "IR"){
            int k,x;
            cin>>k>>x;
            add(k + 1,x);
        }
    }
    
    //输出
    for(int i = r[0]; i != 1; i = r[i]){
        cout<< val[i] <<" ";
    }
}

四、对比总结

特性 单链表 双链表
指针域数量 1 个(后继指针) 2 个(前驱 + 后继指针)
遍历方向 仅向后遍历 双向遍历
插入 / 删除 仅能操作后继节点 可直接删除当前节点
内存占用 更小 更大(多一个指针数组)
适用场景 邻接表、栈、简单链表 双向遍历、LRU 缓存等
实现难度 简单 中等(哨兵节点简化)
相关推荐
剑挑星河月2 小时前
73.矩阵置零
数据结构·算法·leetcode·矩阵
Rabitebla2 小时前
【C++】手撕日期类——运算符重载完全指南(含易错点+底层逻辑分析)
java·c语言·开发语言·数据结构·c++·算法·链表
自我意识的多元宇宙2 小时前
二叉树的遍历和线索二叉树--中序线索二叉树的构造
数据结构
励志的小陈11 小时前
数据结构--二叉树知识讲解
数据结构
笨笨饿11 小时前
#58_万能函数的构造方法:ReLU函数
数据结构·人工智能·stm32·单片机·硬件工程·学习方法
li星野18 小时前
刷题:数组
数据结构·算法
故事和你911 天前
洛谷-数据结构-1-3-集合3
数据结构·c++·算法·leetcode·贪心算法·动态规划·图论
自我意识的多元宇宙1 天前
二叉树的遍历和线索二叉树--线索二叉树
数据结构