C语言实现不带头节点的单链表

在上两篇文章中,我们介绍了基于静态数组和动态数组的顺序表,顺序表频繁查询而插入删除动作少的情况下,顺序表也适用于进行尾插的时候,因为相对于链表而言,顺序表在进行尾插时,顺序表不需要通过遍历来找到最后一个插入点,比较而言,顺序表尾插效率高。
但是,在进行头插和中插时,顺序表需要将插入元素位置之后的元素整体后移才能插入数据,这样做在有大量插入删除场景下即为麻烦且效率低,因此,提出了链表的思想。而链表在头插或者中插时,只需要创建一个新节点,然后将节点链入所插入位置即可。

链表概述

链表是一种常见的数据结构。数组可以存放数据,但是数组存放数据时必须提前指定数组包含元素个数,即数组长度。但是数组保存数据的缺点在于如果要保存元素大于数组大小时,则不能将所有内容保存入数组,而当要保存元素远小于数组大小时,又造成了空间的浪费。而链表其存储元素个数不受限定,当进行添加元素时,存储个数随之增加。
链表是一种链式存储的线性表,用一组地址任意的存储单元存放线性表的数据元素,称存储单元为一个节点。

typedef int DataType;

typedef int DataType;

typedef struct LinkNode
{
    DataType _data;//当前节点保存数据
    struct LinkNode* _pNext;//指向链表中下一结点的指针

}Node;

如下图,为链表结构示意图:
这里写图片描述
在链表中有一个头指针变量,图中head表示的就是头指针,这个指针变量保存一个地址。从图中的箭头可以看到该地址为一个变量的地址,也就是说头指针指向一个变量。这个变量称为元素,在链表中每一个元素包括两个部分:数据部分和指针部分。数据部分用来存放元素所包含的数据,而指针部分用来指向下一个元素。最后一个元素指针指向NULL,表示指向的地址为空。
从图可以看到,head头结点指向第一个元素,第一个元素中的指针又指向第二个元素,第二个元素的指针又指向第三个元素的地址,第三个元素的指针指向第四个元素,第四个元素的指针就指向为空。

链表的分类
  • 单链表
  • 双向链表
  • 双向循环链表

需要注意的是,上述三种链表都有两种形式,分别为带头结点和不带头结点。

这里写图片描述

C语言实现不带头节点的单链表

具体代码实现如下:

#pragma once 

#include <stdio.h>
#include <assert.h>
#include <malloc.h>

/***************************************/

//链表的定义
typedef int DataType;

typedef struct LinkNode
{
    DataType _data;//当前节点保存数据
    struct LinkNode* _pNext;//指向链表中下一结点的指针

}Node;

/***************************************/

//链表的初始化
void LinkListInit(Node** pHead);

//创建新结点
Node* LinkListCreatNode(DataType data);

//销毁结点
void LinkListIDestroyNode(Node** pHead);

//遍历打印链表
void LinkListPrint(Node** pHead);

//尾插元素
void LinkListPushBack(Node** pHead, DataType data);

//尾删元素
void LinkListPopBack(Node** pHead);

//头插元素
void LinkListPushFront(Node** pHead, DataType data);

//头删元素
void LinkListPopFront(Node** pHead);

//查找元素
size_t Link
<think>好的,用户之前已经询问过带头结点链表逆置的代码实现,我提供了三种方法:迭代法(头插法)、递归法和三指针原地逆置。现在用户再次提问,想查找用C语言实现带头结点单链表逆置的代码或算法。需要结合之前的回答和现有的引用内容来回应。 首先,回顾用户之前的对话,用户已经得到了三种方法的代码,所以这次可能需要更详细或同的方法。查看用户提供的引用,其中引用[1]和引用[3]是关于带头结点的逆置,而引用[2]提到了带头结点的头插法和指针反转法。用户当前的请求是带头结点的实现,所以需要重点参考引用[2]中的内容,但之前的回答已经包含了部分方法。 需要注意用户可能希望得到更多的实现方式或更详细的解释。例如,引用[2]中的代码示例是另一种头插法的变体,可能可以补充到现有的方法中。此外,用户可能希望了解同方法之间的区别,或者如何调整带头结点的代码到带头结点的情况。 需要确保回答的结构清晰,分点说明每种方法,并强调带头结点时的关键点,比如头指针的处理。同时,要结合之前的回答,避免重复,但可以补充新的角度或优化点。例如,可以比较带头结点与带头结点在逆置时的同处理方式,或者讨论边界条件,如空链表或单节点链表的处理。 另外,用户可能需要验证代码的正确性,所以可以考虑添加测试用例或验证步骤。但根据用户的问题,主要是代码实现,因此重点还是提供准确且同的方法。 最后,生成相关问题时要围绕带头结点逆置的扩展问题,比如与带头结点的区别、内存管理、错误处理等,帮助用户深入理解。</think>根据您对带头结点单链表逆置的需求,结合经典算法实现和引用内容[2][3],我将通过三种典型方法进行详细说明,重点对比带头结点与带头结点实现的差异。 ### 一、三指针遍历法(推荐方法) ```c struct ListNode* reverseList(struct ListNode* head) { struct ListNode *prev = NULL, *curr = head, *next = NULL; while (curr) { next = curr->next; // 保存后继结点 curr->next = prev; // 反转指针方向 prev = curr; // 前移prev指针 curr = next; // 前移curr指针 } return prev; // 新头指针指向原链表末尾 } ``` **核心差异**:与带头结点实现相比,带头结点需要: 1. 直接操作原头指针而非`L->next` 2. 最终返回新的头指针而是修改参数 3. 需要特别处理空链表`(head == NULL)`的情况[^2] ### 二、头插法实现 ```c struct ListNode* reverseListInsert(struct ListNode* head) { struct ListNode *newHead = NULL, *temp; while (head) { temp = head; // 取出当前结点 head = head->next; // 原链表头后移 temp->next = newHead; // 插入新链表头部 newHead = temp; // 更新新头指针 } return newHead; } ``` **特点对比**: - 时间复杂度:$O(n)$ - 空间复杂度:$O(1)$ - 与带头结点版本的主要区别在于:需要处理头结点的`next`域,直接操作头指针本身[^3] ### 三、递归实现 ```c struct ListNode* reverseListRecursive(struct ListNode* head) { if (!head || !head->next) return head; struct ListNode* newHead = reverseListRecursive(head->next); head->next->next = head; // 反转指针方向 head->next = NULL; // 断开原指针 return newHead; } ``` **注意事项**: - 递归深度与链表长度成正比,空间复杂度为$O(n)$ - 对于长链表可能引发栈溢出 - 与带头结点递归实现的差异:需要传递头结点指针参数[^1] ### 四、测试用例参考 ```c // 测试空链表 reverseList(NULL); // 测试单结点链表 struct ListNode node1 = {1, NULL}; reverseList(&node1); // 测试常规链表 struct ListNode node3 = {3, NULL}; struct ListNode node2 = {2, &node3}; struct ListNode node1 = {1, &node2}; struct ListNode* newHead = reverseList(&node1); // 3->2->1 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值