链表 头插法 尾插法 为什么要有头结点

学过链表的应该都知道向链表中插入元素存在两种插入方式:

头插法:数据插入链表后,作为链表的第一个元素;
尾插法:数据插入链表后,作为链表的最后一个元素;

本篇博客的重点在于为什么要有头结点
关于头结点和头指针的概念,请参考关于链表中头指针和头结点的理解

为什么要有头结点,在网上找了半天,解释是"为了统一插入和删除对第一个结点和对其他结点的操作"
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;
}

上面的执行结果为:
运行结果

上面红圈圈处的数字是因为没有对头结点的数据进行初始化,内存中的垃圾数字.

从上面可以看出:
不带头结点的链表,因为头指针指向第一个元素结点:

  1. 尾插法添加元素时,需要判断当前链表是否是没有元素,如果没有元素,则需要更新头指针指向当前添加的这个元素;
  2. 删除元素时,需要判断删除的元素是否为第一个元素,如果是第一个元素,需要更新头指针指向下一个元素;

带头结点的链表
因为头指针指向这个头结点,不会出现头指针为NULL的情况,不会有上述的特殊考虑,对第一个元素的操作和对其他元素的操作统一;

原文地址:https://www.cnblogs.com/jamesvoid/p/9919116.html