当前位置: 首页 > news >正文

数据结构:链表(Linked List)

目录

结构推导

回到最原始的问题 —— 我们如何存数据?

第二步:我们来看看数组的限制 

第三步:那我们该怎么做呢? 

第四步:我们推导链表的数据结构 

结构讲解

什么是链表?

什么是节点? 

如何创建节点?

 如何访问节点?


结构推导

🎯 目标:理解什么是链表(linked list),为什么我们需要它,以及它的结构从哪里来的。

回到最原始的问题 —— 我们如何存数据?

在 C 语言中,最基础的存数据方式是:

int a[100];

也就是 数组(array)。它非常常见,我们从一开始就用。

那我们先从数组的“第一性”特征说起:

数组的本质是:

一块连续的内存空间 + 索引访问

举个例子:

int a[5] = {10, 20, 30, 40, 50};

这个数组在内存里是这样的(假设从地址 1000 开始):

地址
100010
100420
100830
101240
101650

它的特性:

  • 地址连续

  • a[i] 的位置是:a + i × sizeof(int)

  • 可以快速通过索引访问:O(1)

详细内容可以参考:数据结构:数组(Array)_array[1..50]-CSDN博客


第二步:我们来看看数组的限制 

情况1:大小固定

如果你定义了:

int a[100];

你就固定只能存最多 100 个元素。如果只用了前 5 个元素,空间也浪费。

如果你事先不知道需要多少个元素,就很难选大小。

情况2:插入麻烦

假设你已经有数组:

int a[5] = {10, 20, 30, 40, 50};

现在你想在 第 2 个位置插入 99,就得把后面的都搬一格:

for (int i = 5; i > 1; i--) {a[i] = a[i - 1];
}
a[1] = 99;

这会导致O(n) 的时间复杂度。

情况3:删除也麻烦

类似的,如果你要删掉 a[2],也得把后面的全往前搬一格。

总结数组的问题:

问题描述
空间不灵活空间必须一开始就定下来
插入慢中间插入要整体移动元素
删除慢删除某项也要移动后面的元素
空间浪费有时只用一部分数组,但仍然占内存

第三步:那我们该怎么做呢? 

我们思考一下:

能不能只在用到数据时,才开辟空间?

能不能插入一个元素,而不影响其它元素的位置?

这就引出了一个新的思维模型:

✅ 使用“分散空间 + 指针连接”的模型

如果我们不强求内存连续,而是:

  • 每次添加一个节点时,就用 malloc 单独开一块空间

  • 每个节点都保存指向“下一个节点”的指针

我们就可以实现灵活的动态结构了。

这就是 链表(linked list) 的思想:

把所有的数据节点一个一个连起来,而不是排成连续数组


第四步:我们推导链表的数据结构 

我们要存一个元素,它需要什么?

  1. 数据本身,比如:一个整数

  2. 指向下一个节点的“线索”(指针)

所以我们设计一个节点结构:

struct Node {int data;           // 当前节点的数据struct Node* next;  // 指向下一个节点
};

那一个“链表”是什么?

一个链表是“若干个 Node 连起来”,只要有第一个节点(头结点)的地址,就能找到整个链表。

所以我们定义:

struct Node* head;  // 指向链表的起点

✅ 所以链表的基本单位是:节点 + 指针

[10 | *] --> [20 | *] --> [30 | NULL]
  • 每个方块是一个 Node

  • *next 指针

  • 最后一个节点的 next = NULL 表示链表结束

结论:为什么我们需要链表?

问题

链表怎么解决

数组大小固定

链表可以动态增加节点(malloc)

插入/删除慢

链表插入删除只需修改指针,不搬数据

空间浪费

用多少开多少,不多开


结构讲解

什么是链表?

在数组中,所有元素都在一块连续的内存空间中。你只能在一开始就分配好大小,插入/删除不方便。那么:

有没有一种数据结构,不要求数据连续存储,但依然能一个接一个地串起来?

 这就是链表的本质:

链表是由若干个节点组成的线性结构,每个节点都知道下一个节点在哪里,只需要记住第一个节点就能访问整个链表。

链表的核心思想是:

每个节点知道“谁在后面”,就像每一节车厢都知道后面那节车厢的位置。

整个链表就像是一列火车,每节车厢通过“挂钩”(指针)串联起来:

✅ 链表的特征:

  • 节点之间不要求连续内存

  • 每个节点都包含:

    • 本节点的数据

    • 指向下一个节点的指针

  • 通过“指针”把节点连接起来

 通常我们用一个指针 head 来指向第一个节点:

struct Node* head;

什么是节点? 

一个节点(Node)是链表的最基本组成单位,就是一个结构体,它包含:

  1. 一份数据(例如 int 类型)

  2. 一个指针,指向下一个节点

这就好比:

  • 火车的每节车厢(节点)都有:

    • 货物(数据)

    • 连接器(指针)去连下一节车厢

 用C语言表示:

struct Node {int data;           // 节点中存的数据struct Node* next;  // 指向下一个节点的指针
};
成员含义
int data存储数据,比如整数 10
Node* next保存下一个节点的地址,如果是最后一个就为 NULL

如何创建节点?

为什么我们不能直接写:

struct Node node1;

1. 这句代码在 栈(stack)上 分配了一个 Node 类型的变量。它的特点是:

  • 分配在栈上(函数调用结束后会被自动释放)

  • 内存大小在编译时就已经确定

  • 生命周期由作用域决定,作用域结束就失效

🔍 2. 链表是动态结构,不能预知节点个数

链表的本质是:

  • 你不知道总共有多少个节点(比如根据用户输入构建)

  • 每一个节点都需要“现用现开”,而不是提前一次性分配

所以我们需要在“运行时”动态分配空间: 

struct Node* newNode = (struct Node*) malloc(sizeof(struct Node));

这行代码的含义是:

  1. malloc(sizeof(struct Node))堆(heap)上动态分配一块内存,大小正好是一个 Node

  2. 返回值是 void*,强制类型转换成 struct Node*

  3. newNode 是一个指针,指向新建的节点

再从链表的设计需求来倒推

  • 节点数是动态变化的(例如用户输入10个节点)

  • 节点之间是用指针连接的

  • 每个节点可能在任何时刻被插入或删除

  • 节点需要长期存在,甚至跨越函数作用域

 所以:必须用动态内存分配,也就是 malloc()。struct Node node1; 是不能满足上述需求的


 如何访问节点?

假设我们已经创建了多个节点,并链接起来:

struct Node* head = (struct Node*) malloc(sizeof(struct Node));
head->data = 10;struct Node* second = (struct Node*) malloc(sizeof(struct Node));
second->data = 20;head->next = second;
second->next = NULL;

访问链表节点的本质逻辑是沿着 next 指针走,这就叫做遍历链表(traverse the linked list)。

举个例子,假设你有一个链表,要访问节点内容:

head → [10 | * ] → [20 | * ] → [30 | NULL]

你想访问第二个节点的值 20:

你不能说 head[1],因为它不是数组。

你只能说:

head->next->data

这个表达式的意义是:

  1. head 是第一个节点的地址

  2. head->next 是第二个节点的地址

  3. head->next->data 就是第二个节点的值

访问第二个节点的数据: 

printf("%d\n", head->next->data);  // 输出 20

遍历就是:

head 开始,沿着每个节点的 next 指针走,直到你走到 NULL(链表结尾)

struct Node* temp = head;
while (temp != NULL) {printf("%d ", temp->data);temp = temp->next;
}
printf("\n");

这段代码会访问每个节点的 data,直到遇到 NULL。 

http://www.lryc.cn/news/607153.html

相关文章:

  • 如何通过黑白棋盘进行定位配准融合?(前后安装的两个相机)
  • 【Mysql】联合索引生效分析案例
  • 【科研绘图系列】R语言绘制环状分组显著性柱状堆积图
  • 鹧鸪云:16步精控工商业光伏全流程
  • java8学习笔记-Stream流
  • GitPython08-源码解读
  • 网络编程接口bind学习
  • MySQL时间处理完全指南:从存储到查询优化
  • Java向量化
  • 如何处理Y2K38问题
  • 利用 AI 在 iPhone 上实现 App 文本情绪价值评估(上)
  • 【AI应用】 能源保供战:AI大模型如何守护万家灯火?
  • TGD第十篇:当神经网络遇到TGD特征
  • Qt 开发自动化测试框架搭建
  • 【华为机试】34. 在排序数组中查找元素的第一个和最后一个位置
  • OSPF综合大实验
  • python学智能算法(三十))|SVM-KKT条件的数学理解
  • Git基础命令大全
  • C语言数据结构(3)单链表专题1.单链表概述
  • 【论文学习】KAG论文翻译
  • Redis 中 ZipList 的级联更新问题
  • 一篇文章读懂AI Agent(智能体)
  • Python LRU缓存应用与示例
  • 三维协同:体育场馆设计与渲染的独特挑战
  • Web开发-PHP应用TP框架MVC模型路由访问模版渲染安全写法版本漏洞
  • Au速成班-多轨编辑流程
  • 在纯servlet项目中,使用@WebFilter定义了多个filter,如何设置filter的优先级
  • 【PHP 构造函数与析构函数:从基础到高级的完整指南】
  • 【音视频】WebRTC 中的RTP、RTCP、SDP、Candidate
  • 2025年Python Web框架之争:Django、Flask还是FastAPI,谁将主宰未来?