学过链表的应该都知道向链表中插入元素存在两种插入方式:
头插法:数据插入链表后,作为链表的第一个元素;
尾插法:数据插入链表后,作为链表的最后一个元素;
本篇博客的重点在于为什么要有头结点
关于头结点和头指针的概念,请参考关于链表中头指针和头结点的理解
为什么要有头结点,在网上找了半天,解释是"为了统一插入和删除对第一个结点和对其他结点的操作"
but how,这一点没有完整且直观的代码解释,在这一点上,我之前的理解并不是很清晰,这里通过代码来验证一下:
talk is cheap,show me the code
#include<stdio.h>
#include<stdlib.h>
struct Node
{
int data;
struct Node *next;
}Node;
typedef struct LinkedList
{
struct Node *head; // 头指针
}LinkedList;
// 无头结点初始化
void Init_List(LinkedList *list)
{
list->head = NULL;
}
// 有头结点初始化
void Init_List_With_Head_Node(LinkedList *list)
{
struct Node *node = (struct Node*)malloc(sizeof(struct Node));
node->next = NULL;
list->head = node;
}
// 有头结点头插法插入数据
void Head_Insert_List(LinkedList *list,int data)
{
struct Node *node = (struct Node*)malloc(sizeof(struct Node));
node->data = data;
node->next = list->head;
list->head = node;
}
// 有头结点尾插法插入数据
void Tail_Insert_List(LinkedList *list,int data)
{
struct Node *node = (struct Node*)malloc(sizeof(struct Node));
struct Node *tmp = list->head;
node->data = data;
node->next = NULL;
if(tmp)
{
// 走到尾部
while(tmp->next)
{
tmp = tmp->next;
}
tmp->next = node;
}
else
{
// head为 NULL
list->head = node;
}
}
// 有头结点头插法插入数据
void Head_Insert_List_With_Head_Node(LinkedList *list,int data)
{
struct Node *node = (struct Node*)malloc(sizeof(struct Node));
node->data = data;
node->next = list->head->next;
list->head->next = node;
}
// 有头结点尾插法插入数据
void Tail_Insert_List_With_Head_Node(LinkedList *list,int data)
{
struct Node *node = (struct Node*)malloc(sizeof(struct Node));
struct Node *tmp = list->head;
node->data = data;
node->next = NULL;
// 走到尾部
while(tmp->next)
{
tmp = tmp->next;
}
tmp->next = node;
}
void print_list(LinkedList *list)
{
int i;
struct Node *tmp = list->head;
while(tmp)
{
printf("%d ",tmp->data);
tmp = tmp->next;
}
puts("");
}
int main()
{
LinkedList list;
puts("无头结点:");
Init_List(&list);
puts("头插法");
Head_Insert_List(&list,1);
Head_Insert_List(&list,2);
Head_Insert_List(&list,3);
print_list(&list);
puts("尾插法");
Init_List(&list);
Tail_Insert_List(&list,1);
Tail_Insert_List(&list,2);
Tail_Insert_List(&list,3);
print_list(&list);
puts("有头结点:");
Init_List_With_Head_Node(&list);
puts("头插法");
Head_Insert_List_With_Head_Node(&list,1);
Head_Insert_List_With_Head_Node(&list,2);
Head_Insert_List_With_Head_Node(&list,3);
print_list(&list);
puts("尾插法");
Init_List_With_Head_Node(&list);
Tail_Insert_List_With_Head_Node(&list,1);
Tail_Insert_List_With_Head_Node(&list,2);
Tail_Insert_List_With_Head_Node(&list,3);
print_list(&list);
return 0;
}
上面的执行结果为:
上面红圈圈处的数字是因为没有对头结点的数据进行初始化,内存中的垃圾数字.
从上面可以看出:
不带头结点的链表,因为头指针指向第一个元素结点:
- 尾插法添加元素时,需要判断当前链表是否是没有元素,如果没有元素,则需要更新头指针指向当前添加的这个元素;
- 删除元素时,需要判断删除的元素是否为第一个元素,如果是第一个元素,需要更新头指针指向下一个元素;
带头结点的链表
因为头指针指向这个头结点,不会出现头指针为NULL的情况,不会有上述的特殊考虑,对第一个元素的操作和对其他元素的操作统一;