【数据结构】单链表的头节点与尾节点

在这里插入图片描述

🎈个人主页:豌豆射手^
🎉欢迎 👍点赞✍评论⭐收藏
🤗收录专栏:数据结构
🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步!

在这里插入图片描述

引言

一 概念

在数据结构中的单链表中,头节点(Head Node)和尾节点(Tail Node)是两个特殊的节点,它们在链表结构中扮演着不同的角色。

头节点(Head Node)

头节点是链表中的第一个节点,它通常用于简化对链表的操作。头节点并不总是包含实际的数据,有时候它只是一个哨兵节点(Dummy Node),用于指向链表中的第一个实际数据节点。头节点的存在可以使得链表的操作更加统一和方便,特别是在进行插入、删除和遍历操作时。

头节点的作用:
  1. 简化插入操作:当需要在链表头部插入新节点时,如果链表有头节点,就可以直接将新节点的指针指向头节点原本指向的节点,然后将头节点的指针指向新节点。如果没有头节点,就需要处理一些特殊情况,比如判断链表是否为空。
  2. 简化删除操作:类似地,当需要删除链表头部的节点时,如果有头节点,只需要将头节点的指针指向下一个节点即可。如果没有头节点,则需要特别注意链表是否为空,以及如何处理删除后的链表头部。
  3. 统一链表操作:有了头节点,无论是链表的头部还是尾部,都可以使用统一的操作方式,这使得代码更加简洁和易于维护。

尾节点(Tail Node)

尾节点是链表中的最后一个节点。在单链表中,每个节点都包含一个指向下一个节点的指针,而尾节点的这个指针通常设置为 NULL,表示链表的结束。

尾节点的作用:
  1. 标记链表结束:通过尾节点的 NULL 指针,可以方便地知道链表何时结束,这是遍历链表时的重要标识。
  2. 简化插入操作:当需要在链表尾部插入新节点时,只需要将尾节点的指针指向新节点,然后将新节点的指针设置为 NULL。这样可以避免从头节点开始遍历整个链表来找到最后一个节点。

总结

头节点和尾节点在单链表中的作用主要体现在简化链表操作和提高代码的可读性和可维护性。它们使得链表的插入、删除和遍历等操作更加高效和一致。然而,并不是所有的单链表实现都会使用头节点,这取决于具体的应用场景和需求。在某些情况下,为了节省内存空间或简化数据结构,可能会选择不使用头节点。

二 代码

在C语言中实现单链表的头节点和尾节点,我们需要首先定义链表节点的结构体,然后编写创建链表、添加节点等函数。下面是一个简单的示例,它展示了如何创建一个包含头节点和尾节点的单链表,并向其中添加数据节点。

#include <stdio.h>
#include <stdlib.h>

// 定义链表节点结构体
typedef struct Node {
    int data;           // 节点存储的数据
    struct Node* next; // 指向下一个节点的指针
} Node;

// 创建新节点
Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node)); // 为新节点分配内存
    if (newNode == NULL) {
        printf("Memory allocation failed!\n");
        exit(1); // 如果内存分配失败,退出程序
    }
    newNode->data = data;    // 设置节点的数据
    newNode->next = NULL;    // 新节点的next指针初始化为NULL
    return newNode;          // 返回新创建的节点
}

// 在链表尾部添加节点
void appendNode(Node** head, Node** tail, int data) {
    Node* newNode = createNode(data); // 创建新节点
    if (*head == NULL) {             // 如果链表为空(即头节点为空)
        *head = newNode;             // 新节点成为头节点
        *tail = newNode;             // 新节点也成为尾节点
    } else {
        (*tail)->next = newNode;    // 将新节点添加到当前尾节点的后面
        *tail = newNode;             // 更新尾节点为新节点
    }
}

// 打印链表
void printList(Node* head) {
    Node* current = head;
    while (current != NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}

// 释放链表内存
void freeList(Node* head) {
    Node* temp;
    while (head != NULL) {
        temp = head;
        head = head->next;
        free(temp);
    }
}

int main() {
    Node* head = NULL;    // 初始化头节点为NULL
    Node* tail = NULL;    // 初始化尾节点为NULL

    // 向链表尾部添加节点
    appendNode(&head, &tail, 10);
    appendNode(&head, &tail, 20);
    appendNode(&head, &tail, 30);

    // 打印链表
    printList(head);

    // 释放链表内存
    freeList(head);

    return 0;
}

下面是对代码的详细解释:

  1. 定义链表节点结构体 Node,包含 datanext 两个成员。

  2. createNode 函数用于创建新节点,并初始化其 datanext 成员。如果内存分配失败,则打印错误信息并退出程序。

  3. appendNode 函数用于在链表尾部添加新节点。它接受头节点和尾节点的地址作为参数,以便在添加新节点时能够更新这两个指针。

    • 如果链表为空(即头节点为 NULL),新节点既是头节点也是尾节点。
    • 如果链表不为空,将新节点添加到当前尾节点的后面,并更新尾节点为新节点。
  4. printList 函数用于打印链表的内容。从头节点开始,遍历链表直到 NULL 指针,并打印每个节点的数据。

  5. freeList 函数用于释放链表占用的内存。从头节点开始,逐个释放每个节点所占用的内存。

  6. main 函数中,我们初始化头节点和尾节点为 NULL,然后调用 appendNode 函数向链表尾部添加几个节点。接着,我们调用 printList 函数打印链表的内容。最后,调用 freeList 函数释放链表占用的内存。

注意:在这个示例中,我们并没有显式地创建一个头节点来存储链表信息,而是将头节点作为链表的一部分,它同时存储数据和指向下一个节点的指针。如果你需要一个只包含指针而不包含数据的头节点,你需要在创建链表时单独处理它,并更新相关的逻辑。

另外,上面的代码示例没有包含错误处理逻辑,比如检查 malloc 调用是否成功。在实际编程中,你应该总是检查内存分配是否成功,并在必要时采取适当的错误处理措施。

总结

这篇文章到这里就结束了

谢谢大家的阅读!

如果觉得这篇博客对你有用的话,别忘记三连哦。

我是豌豆射手^,让我们我们下次再见

在这里插入图片描述

在这里插入图片描述

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
带有节点单链表是指在单链表部添加一个虚拟节点,它的作用是方便对链表进行操作。下面是带有节点单链表的常见操作: 1. 初始化链表 ```c #include <stdio.h> #include <stdlib.h> //定义链表节点结构体 typedef struct Node { int data; //数据域 struct Node* next; //指针域 }Node; //定义带有节点的链表 typedef struct List { Node* head; //节点指针 int length; //链表长度 }List; //初始化链表 void initList(List* list) { list->head = (Node*)malloc(sizeof(Node)); //为节点动态分配内存 list->head->next = NULL; //将节点的指针域置为NULL list->length = 0; //链表长度初始化为0 } //测试代码 int main() { List list; initList(&list); return 0; } ``` 2. 插入节点 ```c //在第pos个节点之后插入元素x void insertNode(List* list, int pos, int x) { if (pos < 1 || pos > list->length + 1) { //判断插入位置是否合法 printf("插入位置不合法!\n"); return; } Node* p = list->head; for (int i = 1; i < pos; i++) { //查找插入位置的前驱节点 p = p->next; } Node* newNode = (Node*)malloc(sizeof(Node)); //创建新节点 newNode->data = x; newNode->next = p->next; //将新节点插入到链表中 p->next = newNode; list->length++; //链表长度加1 } //测试代码 int main() { List list; initList(&list); insertNode(&list, 1, 1); insertNode(&list, 2, 2); insertNode(&list, 3, 3); return 0; } ``` 3. 删除节点 ```c //删除第pos个节点 void deleteNode(List* list, int pos) { if (pos < 1 || pos > list->length) { //判断删除位置是否合法 printf("删除位置不合法!\n"); return; } Node* p = list->head; for (int i = 1; i < pos; i++) { //查找删除位置的前驱节点 p = p->next; } Node* q = p->next; //将要删除的节点保存到临时变量q中 p->next = q->next; //将前驱节点指向后继节点,实现删除操作 free(q); //释放节点的内存空间 list->length--; //链表长度减1 } //测试代码 int main() { List list; initList(&list); insertNode(&list, 1, 1); insertNode(&list, 2, 2); insertNode(&list, 3, 3); deleteNode(&list, 2); return 0; } ``` 4. 查找节点 ```c //查找值为x的节点,返回节点的位置 int searchNode(List* list, int x) { Node* p = list->head->next; int pos = 1; while (p != NULL && p->data != x) { //遍历链表,查找值为x的节点 p = p->next; pos++; } if (p == NULL) { //未找到节点,返回0 return 0; } else { //找到节点,返回位置 return pos; } } //测试代码 int main() { List list; initList(&list); insertNode(&list, 1, 1); insertNode(&list, 2, 2); insertNode(&list, 3, 3); int pos = searchNode(&list, 2); printf("节点2的位置是:%d\n", pos); return 0; } ``` 5. 遍历链表 ```c //遍历链表,输出所有元素 void traverseList(List* list) { Node* p = list->head->next; while (p != NULL) { printf("%d ", p->data); p = p->next; } printf("\n"); } //测试代码 int main() { List list; initList(&list); insertNode(&list, 1, 1); insertNode(&list, 2, 2); insertNode(&list, 3, 3); traverseList(&list); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值