文章目录
链表
什么是链表
之前我们学习过数组, 数组是在内存中一段连续的存储空间, 可以在常数时间内访问任意位置的元素, 但是数组也有缺点, 无法做到快速的插入和删除, 因为空间是连续且固定的, 想要在 p 位置插入/删除一个元素, 则 p 之后的位置的元素都需要移动。
为了能够在常数时间内实现元素的插入和删除, 我们引入 链表 这种数据结构。
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。
那么非连续、非线性有什么含义呢?这表明链表的内存是不连续的,前一个元素存储地址的下一个地址中存储的不一定是下一个元素。链表通过一个指向下一个元素地址的引用将链表中的元素串起来。
链表的分类
分类 | 描述 |
---|---|
单向链表 | 每一个节点包含了数据块和指向下一个节点的指针 |
双向链表 | 每一个节点包含了数据块和指向下一个节点的指针以及指向前一个节点的指针 |
单向链表
单向链表是最简单的链表形式。我们将链表中最基本的数据称为节点 (node) ,每一个节点包含了数据块( data )和指向下一个节点的指针( next ),链表有一个头节点,图中以 head 表示。可以看出, head 指向第一个元素,第一个元素的 next 又指向第二个元素……直到最后一个元素,该元素不再指向其他元素,它称为表尾,它的 next 为空( NULL ),链表到此结束。
可以看到,要找链表中的某一元素,必须先找到上一个元素,根据它提供的下一个元素的地址才能找到下一个元素。如果不提供头指针,则整个链表都无法访问。链表如同一条铁链一样,一环扣一环,中间是不能断开的。
我们使用数组模拟的方法来学习链表。我们创建一个结构体, 这个结构体有两个 int 型的变量, 分别是 data 和 next , data 就是链表当前节点存储的数据, next 指向下一个节点的节点编号(在数组中的编号,这样可以避免使用指针)。
struct node{
//next即后面节点的编号,data就是需要维护的数据
int next,data;
}a[10000];
int head; //head即头指针
//head节点是头结点, 不存储数据, 作用只是用来找到链表的第一个节点。
链表的常用操作
操作 | 描述 |
---|---|
查找 | 找到符合条件的节点 |
插入 | 添加一个新节点 |
删除 | 删除一个存在的节点 |
查找操作
如何根据给出的数据,找到链表中符合条件的节点呢?需要从头节点开始,逐个向后遍历节点,比较 p 的 data 同待查找的数据是否相同,相同则返回当前节点。
struct node{
//next即后面节点的编号,data就是需要维护的数据
int next,data;
}a[10000];
int head; //head即头节点的编号
node find(int value)
{
int p = head; //从
while(a[p] != NULL)
{
if(value==a[p].data)
return a[p];
p = a[p].next;
}
return NULL;
}
插入操作
链表如何实现插入操作呢? 如果我们想在第 p 个节点之后插入一个新节点, 因为我们不能像使用数组一样直接访问下标, 链表只能头遍历, 直到走到第 p 个节点。
到达第 p 个节点之后, 我们需要先将新节点的 next 指向当前 p 节点 next 指向的节点, 再将 p 节点的 next 指向这个新的节点。
struct node{
//next即后面节点的编号,data就是需要维护的数据
int next,data;
}a[10000];
int head; //head即头节点的编号
//注意要使用引用传递
void