什么是线性表?
(可以看我之前的数据结构专题一:数据结构(C/C++)专题一:顺序表与链表-CSDN博客)
线性表是一种数据结构,其中的元素按线性顺序排列。常见的线性表包括数组和链表。
- 数组:元素在内存中是连续存放的,可以通过索引直接访问。
- 链表:元素在内存中不一定是连续存放的,每个元素(称为节点)包含数据和指向下一个节点的指针。
什么是头节点?
头节点是链表中的一个特殊节点,它通常位于链表的开头。头节点本身不一定存储实际数据(也可以存储数据),但它的主要作用是指向链表的第一个实际节点。
优点
为什么需要头节点?
统一处理边界情况
简化代码
容易实现一些特殊操作
1.统一处理边界情况
无头结点时处理链表的第一个节点和其他节点的操作(如插入、删除)会有所不同。例如,删除第一个节点需要更新链表的头指针,而删除其他节点不需要。
首先,我们举个例子以单链表的角度:
没有头节点的链表操作
插入操作
假设我们有一个没有头节点的链表,链表结构如下:
链表:1 -> 2 -> 3 -> NULL
在第一个节点前插入新节点:
- 目标:在第一个节点前插入节点0。
- 操作步骤:
- 创建新节点0。
- 将新节点0的next指针指向当前第一个节点1。
- 更新链表头指针指向新节点0。
插入后的链表:
链表:0 -> 1 -> 2 -> 3 -> NULL
在中间某个节点后插入新节点:
- 目标:在节点
2
后插入节点4
。- 操作步骤:
- 找到节点
2
。- 创建新节点
4
。- 将新节点
4
的next
指针指向节点2
的next
指针(即节点3
)。- 将节点
2
的next
指针指向新节点4
。链表:0 -> 1 -> 2 -> 4 -> 3 -> NULL
有头节点的链表操作
假设我们有一个有头节点的链表,链表结构如下:
链表:Head -> 1 -> 2 -> 3 -> NULL
插入操作
在第一个节点前插入新节点:
- 目标:在第一个节点前插入节点
0
。- 操作步骤:
- 创建新节点
0
。- 将新节点
0
的next
指向头节点的next
(即节点1
)。- 将头节点的
next
指向新节点0
。插入后的链表:
链表:Head -> 0 -> 1 -> 2 -> 3 -> NULL
在中间某个节点后插入新节点:
- 目标:在节点
2
后插入节点4
。- 操作步骤:
- 找到节点
2
。- 创建新节点
4
。- 将新节点
4
的next
指向节点2
的next
(即节点3
)。- 将节点
2
的next
指向新节点4
。插入后的链表:
链表:Head -> 0 -> 1 -> 2 -> 4 -> 3 -> NULL
关于删除,也是同理,如果没有头结点时,删除首元节点是,要处理头指针L
删除第一个节点:
- 目标:删除第一个节点
0
。- 操作步骤:
- 更新链表头指针指向第一个节点的
next
指针(即节点1
)。链表:1 -> 2 -> 4 -> 3 -> NULL
删除中间某个节点:
- 目标:删除节点
2
。- 操作步骤:
- 找到节点
2
的前一个节点(即节点1
)。- 将节点
1
的next
指针指向节点2
的next
指针(即节点4
)链表:1 -> 4 -> 3 -> NULL
有头节点
链表:Head -> 0 -> 1 -> 2 -> 4 -> 3 -> NULL
删除第一个实际节点:
- 目标:删除第一个实际节点
0
。- 操作步骤:
- 将头节点的
next
指向第一个实际节点0
的next
(即节点1
)。链表:Head -> 1 -> 2 -> 4 -> 3 -> NULL
删除中间某个节点:
- 目标:删除节点
2
。- 操作步骤:
- 找到节点
2
的前一个节点(即节点1
)。- 将节点
1
的next
指向节点2
的next
(即节点4
)。链表:Head -> 1 -> 4 -> 3 -> NULL
通过上面的例子可以看出,在没有头节点的情况下,处理链表的第一个节点和其他节点的操作是有所不同的,特别是在插入和删除第一个节点时,需要单独处理头指针。而在有头节点的情况下,所有节点的操作都可以统一处理,头节点前面始终有一个“虚拟”的节点,使得第一个实际节点的处理与其他节点一致,从而简化了代码逻辑。
2.简化代码
什么是空表和非空表的统一处理?
空表指的是链表中没有任何实际节点,而非空表指的是链表中至少有一个实际节点。在没有头节点的情况下,我们需要分别处理空表和非空表的插入和删除操作,而有了头节点之后,这些操作就可以统一处理。
没有头节点的链表
插入操作
插入到空表:
如果链表为空(即头指针为
NULL
),我们插入一个新节点:原链表:NULL
- 操作步骤:
- 创建新节点
1
。- 将链表的头指针指向新节点
1
。插入后的链表:
链表:1 -> NULL
插入到非空表:
如果链表非空(如头指针指向节点
1
),在第一个节点前插入新节点0
:
原链表:1 -> 2 -> 3 -> NULL
- 操作步骤:
- 创建新节点
0
。- 将新节点
0
的next
指向当前第一个节点1
。- 将链表的头指针指向新节点
0
。插入后的链表:
链表:0 -> 1 -> 2 -> 3 -> NULL
删除操作
删除空表中的节点:
如果链表为空,尝试删除节点会导致错误,需要检查链表是否为空:
原链表:NULL
- 操作步骤:
- 检查链表头指针是否为
NULL
,如果是,直接返回,不做任何操作。删除非空表中的第一个节点:
如果链表非空(如头指针指向节点
1
),删除第一个节点:原链表:1 -> 2 -> 3 -> NULL
- 操作步骤:
- 将链表的头指针指向第一个节点的
next
(即节点2
)。删除后的链表:
链表:2 -> 3 -> NULL
有头节点的链表
在有头节点的链表中,空表和非空表的操作都可以统一处理。头节点的
next
指针始终指向链表的第一个实际节点,若链表为空,则头节点的next
为NULL
。插入操作
插入到空表:
如果链表为空(即头节点的
next
为NULL
),我们插入一个新节点:原链表:Head -> NULL
- 操作步骤:
- 创建新节点
1
。- 将头节点的
next
指向新节点1
。插入后的链表:
链表:Head -> 1 -> NULL
插入到非空表:
如果链表非空(如头节点的
next
指向节点1
),在第一个节点前插入新节点0
:原链表:Head -> 1 -> 2 -> 3 -> NULL
- 操作步骤:
- 创建新节点
0
。- 将新节点
0
的next
指向头节点的next
(即节点1
)。- 将头节点的
next
指向新节点0
。插入后的链表:
链表:Head -> 0 -> 1 -> 2 -> 3 -> NULL
删除操作
删除空表中的节点:
如果链表为空,头节点的
next
为NULL
:原链表:Head -> NULL
- 操作步骤:
- 检查头节点的
next
是否为NULL
,如果是,直接返回,不做任何操作。删除非空表中的第一个节点:
如果链表非空(如头节点的
next
指向节点1
),删除第一个节点:原链表:Head -> 1 -> 2 -> 3 -> NULL
- 操作步骤:
- 将头节点的
next
指向第一个节点的next
(即节点2
)。删除后的链表:
链表:Head -> 2 -> 3 -> NULL
总结
有了头节点之后,无论链表是空的还是非空的,插入和删除操作都可以统一处理,简化了代码逻辑。在操作之前,不需要分别检查链表是否为空,只需对头节点的
next
进行操作。这样不仅提高了代码的可读性和可维护性,也减少了出错的机会。
3.容易实现一些特殊操作
- 头节点可以用来存储链表的元信息,例如链表的长度。这在进行一些需要知道链表长度的操作时会很方便。