在本教程中,代码的运行环境设定为 Visual Studio 2010(以下简称 VS2010),后续所有代码的编写、调试与运行操作均在此集成开发环境(IDE)下进行,确保读者在跟随教程步骤实践时,能够获得一致且稳定的操作体验,减少因环境差异可能导致的问题和错误。
一、链表
-
链表的基本概念
链表是一种常见的数据结构,它是由一系列节点
Node
组成的。每个节点包含两个部分:数据域和指针域。数据域用于存储数据元素,如整数、字符、对象等各种类型的数据;指针域用于存储下一个节点的地址,通过这些指针将各个节点按顺序连接起来,就像一条链子一样,因此称为链表。 -
节点结构示例
// 定义链表节点结构体 typedef struct ListNode { int val; // 数据域,用于存放具体的数据,这里是整数 struct ListNode *next; // 指针域,用来指向下一个节点 } ListNode;
在上述代码中:
(1)typedef
关键字用于给结构体类型定义一个别名,这样后续在代码中就可以直接使用ListNode
来表示这个结构体类型了,方便书写。
(2)val
成员变量构成了数据域,它可以存储我们想要保存在链表节点中的具体数据,比如这里定义为int
类型,实际应用中可以根据需求改成其他数据类型,像char
、float
,甚至是自定义的结构体类型等。
(3)next
成员变量则是指针域,它的类型是指向同类型结构体(也就是ListNode
类型)的指针,通过这个指针来连接链表中的下一个节点,从而构建起链表的链式结构。
-
链表的类型
(1)单链表:这是最简单的链表形式。每个节点只有一个指针,指向它的下一个节点。链表中的最后一个节点的指针域通常指向空(NULL
),表示链表的结束。例如,假设有一个存储整数的单链表1 -> 2 -> 3 -> NULL
,第一个节点存储数据1
,它的指针指向存储数据2
的节点,依此类推,最后一个节点存储数据3
,其指针域为NULL
。
(2)双链表:每个节点有两个指针,一个指向前一个节点,一个指向后一个节点。这样的链表可以方便地进行双向遍历。例如,对于双链表NULL <- 1 <-> 2 <-> 3 -> NULL
,节点1
既有指向下一个节点2
的指针,也有指向前面空节点(因为它是第一个节点)的指针;节点2
有指向节点1
和3
的指针,方便从两个方向进行操作。
(3)循环链表:单链表的最后一个节点的指针不是指向NULL
,而是指向链表的第一个节点,就形成了循环链表。对于循环链表1 -> 2 -> 3 -> 1
,可以不断地循环遍历其中的节点。双链表也可以构成循环双链表,其首尾节点相互连接,形成一个环形结构。
-
链表与数组的对比
(1)存储方式
-
数组是连续的内存空间,所有元素在内存中是依次紧密排列的。例如,定义一个
int
类型的数组int arr[5]
,在内存中会分配一段连续的空间来存储这 5 个整数。 -
链表的节点在内存中的位置是分散的,通过指针将它们连接起来,不需要连续的内存空间。
(2)插入和删除操作
-
在数组中插入或删除元素可能会比较复杂。如果要在数组中间插入一个元素,需要将插入位置之后的所有元素都向后移动一位;删除元素时,需要将后面的元素向前移动来填补空缺。例如,在
int arr = {1, 2, 3, 4, 5}
中,如果要在位置 2(索引为 1)插入数字 6,就需要将2
、3
、4
、5
这些元素依次向后移动一位,变成{1, 6, 2, 3, 4, 5}
。 -
在链表中插入和删除节点相对比较容易。对于单链表,插入一个节点只需要修改相关节点的指针即可。比如在
1 -> 2 -> 3
的链表中,要在1
和2
之间插入一个节点4
,只需要将节点1
的next
指针指向节点4
,然后将节点4
的next
指针指向节点2
。删除节点也类似,只需调整指针绕过要删除的节点。
二、链表插入结点
1.插入结点原理分析
单链表插入结点时,需先找到插入位置。若在表头插入,新节点指针指向原头节点,再更新头指针指向新节点;若在表中,找到目标位置前一节点,调整指针使新节点融入;若在表尾,遍历至尾节点,将其指针指向新节点,以此实现插入并维持链表结构。
2.插入结点函数分析
以下这段 <