链表中对链表设置头结点的作用是什么意思?有什么作用?


选择擅长的领域继续答题?
{@each tagList as item}
${item.tagName}
{@/each}
手机回答更方便,互动更有趣,下载APP
提交成功是否继续回答问题?
手机回答更方便,互动更有趣,下载APP
展开全部头结点:在单链表的第一个结点之前附设一个结点,称为头结点头指针:指向链表中第一个结点(单链表由一个头指针唯一确定)的指针(指针指的是存储地址)首元结点:指链表中存储线性表中第一个数据元素a1的结点。为了操作方便,通常在链表的首元结点之前附设一个结点,称为头结点.单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。已赞过已踩过你对这个回答的评价是?评论
收起展开全部结点:数据元素的储存映象,包括数据域和指针域头结点:在单链表的第一个结点之前附设一个结点,成为头结点头指针:指向链表中第一个结点(单链表由一个头指针唯一确定)的指针(指针指的是存储地址)首元结点:指链表中存储线性表中第一个数据元素a1的结点。为了操作方便,通常在链表的首元结点之前附设一个结点,称为头结点.
推荐律师服务:
若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询
为你推荐:
下载百度知道APP,抢鲜体验使用百度知道APP,立即抢鲜体验。你的手机镜头里或许有别人想知道的答案。扫描二维码下载
×个人、企业类侵权投诉
违法有害信息,请在下方选择后提交
类别色情低俗
涉嫌违法犯罪
时政信息不实
垃圾广告
低质灌水
我们会通过消息、邮箱等方式尽快将举报结果通知您。说明
做任务开宝箱累计完成0
个任务
10任务
50任务
100任务
200任务
任务列表加载中...
}
线性表 :: “有头”单链表的设计与实现(链式存储结构) 说明:本文属于读书笔记。笔者将以讲述的方式表达全片文章。故文中提到的某些字词是非正式术语,只是笔者本人的理解性词语。线性表简介:想要了解点击此处目录从数组到链表链式存储结构的简介链表的设计链表的实现结点设置创建结点插入新结点(尾插法、头插法、指定插入)遍历链表查找链表元素(数据查找)删除链表元素修改链表元素结语最终汇总代码1. 从数组到链表 此前,笔者在前文已经基于数组实现了顺序存储结构。(点此阅读)显然,在我们实现顺序存储结构的过程中,基于性能考虑,增加和删除数据元素时的时间复杂度时O(n)。设想一个夸张的例子,在一个大小为10000,且已存储9000个数据元素,如果我们要在第二个位置增加和删除操作是什么概念?就好比你有10000本书自下而上重叠在一起,此时,你要拿从下往上数的第二本书,额~上述了例子或许不太恰当,但是我们明确一下我们的目的:能不能让增删的性能,像查改一样简单,或者说能比当前的操作速度更快?此前,我们实现的顺序存储结构其特点是:用一段 地址连续的存储单元 依次存储线性表的数据元素,且查改时间复杂度为 O(1)。 本篇内容将设计并实现链表,打破使用一段连续的内存单元存储数据,并提高增删的性能。 2. 链式存储结构的简介2.1 特点 用一组任意的存储单元存储线性表中的数据元素。其中,任意存储单元可以是连续的,也可以是不连续的。说白了,就是数据看也放在内存未被占用的任何位置,只要能让其数据连成“串”即可。(如下图)在这里插入图片描述3. 链表的设计3.1 链表结构的特点> 链表(如上图所示),就是将一些杂乱摆放的数据连成一个“串”。3.2 设计思考过程 此时问题来了!这玩意儿用眼睛看肯定是没问题,“串”就完了。那这些数据在内存中的什么位置,我怎么知道。在数组中我们是根据数组首元素地址与偏移量计算得知其他元素的位置,现在没了层关系,咋搞?设想一个情景,假设原来的数据单元就是一排连续的房子,它的地址就相当于门牌号。只不过门牌号是连续的。在链式存储结构中,此时门牌号不连续了,我们同理只要知道门牌号是多少就可以了。如上图所示,由前一个元素到后一个元素是有一个箭头指向的,说明通过前一个元素,可以去到后一个元素。那在实现链表过程中,我们把后一个元素的地址告诉前一个元素不就OK了吗。3.3 结构设计 如果要实现同时存储一个元素和另一个元素的地址,怎么做?简单嘛,数组不就完了!建立一个大小为 2 的数组(arr),首元素存储数据元素,次元素存储地址就OK。要打印元素值,访问 arr[0] ,要找其他元素,访问 arr[1]。如下图。在这里插入图片描述 以数组的方式封装元素和地址可行是可行,但是没必要,有个东西叫结构体!!!4. 链表的实现4.1 结点设置 单个结点,就是链表中的小房子,分为数据域(存储数据)、指针域(存储地址)在这里插入图片描述struct Node{
int data;
/* 数据域 */
struct Node* next;
/* 指针域 */
};4.2 创建结点 创建结点无须多言,就是给数据一套房。此时,只需要让数据入住,单个结点的创建不涉及后续结点,故指针域为空!操作步骤: 1. 动态申请内存空间。(建房) 2. 存储数据。(数据入住) 3. 指针域置空。 4. 返回结点。struct Node* CreateNode(int data){
// 1. 动态申请内存空间
struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));
// 检验申请失败(一般不会)
if(NULL == pnew) return;
// 2. 存储数据
pnew->data = data;
pnew->next = NULL;
// 3. 返回结点
return pnew;
}4.3 插入数据 数据插入是链表中的一个难点,推荐大家画图理解!!! 数据的插入方式无非三种:头部插入、尾部插入、指点插入(中间插入)4.3.1 尾部插入 咱先挑个软柿子捏!尾部插入(如下图):当在尾部插入时,我们仅需找到当前链表的尾结点,然后将新结点往上一挂就完了!在这里插入图片描述 情况分析: 1. 如何找到尾结点;可以通过遍历的方式直接实现!从头结点开始,找到 [ next ] 的指向,即可找到第一个结点;同理,访问第一个结点的 [ next ] 的指向,即可找到第二个结点以此类推!直到 [ next ] 的指向为空,此时说明到达尾部! 2. 如果插入的链表为空,怎么处理? 当链表为空时,此时我们只有一个头结点,且 [ next ] 的指向就是空!那直接挂上去就好了。参数分析:毋庸置疑,操作对象得知道吧!既然是插入,新数据得有吧!在这里插入图片描述 实现步骤: 1. 创建新结点; 2. 判断链表是否为空;(为了操作方便,下图,引入指针游标。)小难点:游标指针 ptemp 与 ptemp->next (关系如上图);说明:当 ptemp 指向某一个结点时,ptemp->next 即可表示当前结点的指针域,又可表示下一个结点。 3. 链表不为空时,遍历寻找尾结点; 难点:如何实现指针移动,思路 传递(联想三个变量实现两个数值交换)看代码! !在这里插入图片描述void AppendNode(struct Node* pHead,int data){
// 1. 创建新节点
struct Node* pnew = CreateNode(data);
// 定义一个游标指针
struct Node* ptemp = pHead;
// 2. 判断链表是否为空,为空直接挂上并退出
if (NULL == ptemp->next){
ptemp->next = pnew;
return;
}
// 3. 链表不为空时,遍历寻找尾结点
while(ptemp->next){
ptemp = ptemp->next;
/* 核心代码!!! */
}
// 4. 挂新结点
ptemp-next = pnew;
} 上述代码,是根据设计思路按部就班的实现的,不可避免有所瑕疵!!! 仔细看可以发现,我们对尾部的判断有两次(链表为空,头结点就等价于尾结点),同时,新结点的加入也是有两次。完全没必要,直接合并就好了。(实现优化,如下!(说明:后续将不再单独展现按部就班的代码,直接操作优化后的!))void AppendNode(struct Node* pHead,int data){
// 1. 创建新节点
struct Node* pnew = CreateNode(data);
// 定义一个游标指针
struct Node* ptemp = pHead;
// 2. 判断链表是否为空,为空直接挂上并退出
// 3. 链表不为空时,遍历寻找尾结点
while(ptemp->next){
ptemp = ptemp->next;
/* 核心代码!!! */
}
// 4. 挂新结点
ptemp->next = pnew;
}4.3.2 头部插入在这里插入图片描述 头插法相较于尾插法多了一个操作,就是 挂点 ,防止链表元素丢失。例如:山洪暴发后,营救员们发现,激流中的某个石块上有待救人员,于是他们采取用手拉手的方式取营救。在营救过程中发现距离不足,就需要有人加入这个营救链中(如上图中的 J )。显然,如果 J 先取牵 A ,那 A 需要腾出手与 J 相牵,很明显,如果此时 A 撒手去牵 J ,那在激流中的队友就要噶了,会被冲走。那 J 先去牵 B ( J 在岸上 ),就可以使河中队友不被冲跑,此时, A 再与 J 牵手,J 下河就可以加长营救链了。上述,瞎掰的例子虽然不切实际,额~,懂大概的意思就行。就是在链表处于非空状态时,新插入的结点的链接方式。J 新结点 应先链接 B 结点,再与 A 结点相连,从而实现数据不丢失。(代码实现如下(步骤见代码中注释!)。)void AddNode(struct Node* pHead,int data){
// 1. 申请新结点
struct Node* pnew = CreateNode(data);
#if 0
// 2. 链表为空直接返回
if(NULL == pHead->next) return;
#else
// 2. 链表为空,直接挂上(此方案可以不要)
if(NULL == pHead->next) {
pHead->next = pnew;
printf("链表为空!自动将新结点置于第一个结点!\n");
return;
}
#endif
// 3. 链表不为空,执行营救行动!
pnew->next = pHead->next;
pHead->next = pnew;
}4.3.3 指定插入 指定插入的特殊之处就是指定位置!!!假设指定位置尾 i : i <= 1:即头插法; i >= 链表长度:即尾插法; i = 其他:即中间插入思路步骤看代码注释!!!void PosNode(struct Node* pHead,int pos,int data){
// 1. 判断链表是否为空
if(NULL == pHead->next) return;
// 2. 创建新结点
struct Node* pnew = CreateNode(data);
// 3. 寻找插入位置
for(int i = 0; i < pos-1; i++){
// 如果循环过程中发现到达尾结点就直接挂新结点并退出
if(NULL == pHead->next) break;
pHead = pHead->next;
}
// 4. 挂结点(营救思路)
pnew->next = pHead->next;
pHead->next = pnew;
}4.4 遍历链表 在此前的实现过程中起始已经有了!这里直接给出代码。//遍历链表
void travel(struct Node* pHead){
if (NULL == pHead) {
printf("链表为空!\n");
return;
}
struct Node* ptemp = pHead->next;//略过头
printf("List:");
while (ptemp){
printf("%d ", ptemp->data);
ptemp= ptemp->next;//指向下一个节点
}
printf("\n");
}4.5 查找链表元素(数据查找) 说明:本篇仅实现数据查找并返回,不实现数据查找(匹配),该功能将在 LeetCode 算法系列 中实现。查找指定元素,在此由于元素可能出现重复,此处仅实现返回第一个查找到的元素。(实现思路和步骤见代码注释!)struct Node* findNode(struct Node* pHead, int findData){
// 1. 判断链表是否为空
if (NULL == pHead) return NULL;
// 2. 略过头节点(使用游标)
pHead = pHead->pNext;
// 3. 寻找指定值(找到就返回地址,没找到就返回null)
while (pHead){
if (findData == pHead->data) return pHead;
pHead = pHead->pNext;
}
return NULL;
}4.6 删除链表元素 删除又是链表操作中的一个难点!!!得找删除元素(前文中已实现);删除与添加有所区别!!!
由于找到的是删除的目标结点,理论上我们还需要找到目标结点的前后结点,便于链接,防止数据丢失!实现思路和步骤见代码注释!void DeleteNode(struct Node* pHead,int aimdata){
// 1. 查找到目标值
struct Node* pDel = FindNode(pHead,aimdata);
// 2. 如果返回为 NULL 即操作目标不在链表中
if(NULL == pDel) return;
// 3. 设置游标,目的是找到操作目标的前一个结点
struct Node* pDel_prev = pHead; /* 此处注意!!! */
while(pDel_prev->next != pDel){
pDel_prev = pDel_prev->next;
}
// 4. 上述循环结束,即找了操作对象
/*
此时 pDel_prev 表示删除结点的前一个结点
pDel_prev->next 可以表示被删除的结点
pDel 表示被删除的结点
pDel->next 表示被删除结点的后一个结点
链接思路:被删除的前一个结点先链接被删除结点的后一个结点,再释放被删除结点!!!
*/
pDel_prev->next = pDel->next;
free(pDel);
printf("删除成功!\n");
}4.7 修改链表元素 修改结点元素,无操作难度,因为前文已经实现了查找元素,找到了以后干就完了。(实现思路和步骤见代码注释!)void ChangeNode(struct Node* pHead,int changedata,int aimdata){
// 1. 查找到目标值
struct Node* pChange = FindNode(pHead,aimdata);
// 2. 如果返回为 NULL 即操作目标不在链表中
if(NULL == pChange ) return;
// 3. 直接复制覆盖即可
pChange->data = changedata;
printf("修改成功!\n");
}5. 结语链表的性能,与顺序存储或者说数组的性能恰恰相反。数组与链表都只是众多数据结构中的一种,都不是“万能”高效的,还是不得不说不要为了使用链表而用链表,应根据具体的数据与操作方式来定。比如,一个程序中,当查询修改的次数非常多,而增加删除较少时,当然选择用数组;反之,用链表呗。(当然还有其他的数据类型结构可选择。)链表在以后的实际操作中一般不会让你现敲,最多再面试(可能只是校内实验室的,企业级的别想了,太 low ),或者刷算法题时使用,因此本文主要是因重在理解设计过程和实现时优化代码的一种思路。链表嘛,写过的人那么多,人家搞好了,封装好了直接用就完了。(当然本文只是笔者根据自己的理解来设计的,不可能会适合所有人理解,甚至说我的理解都可能是有问题的,只不过能写出代码并运行。)本篇只是简单的实现链表的基础操作,而实际中我们可能对链表的操作还包括元素匹配、排序等情形!这些功能的实现将会在后续更新并收纳在 LeetCode 算法系列 中和微信公众号。 无头单链表(待更新) 循环链表(待更新) 双向链表(待更新)6. 最终汇总代码#include<stdio.h>
typedef int DataType;
/* 实现便利修改操作的数据类型 */
// 结点构造
struct Node{
DataType data;
/* 数据域 */
struct Node* next;
/* 指针域 */
};
struct Node* CreateNode(DataType);
void AppendNode(struct Node*,DataType);
void AddNode(struct Node*,DataType);
void travel(struct Node*);
struct Node* findNode(struct Node*, DataType);
void DeleteNode(struct Node*,DataType);
void ChangeNode(struct Node*,DataType,DataType);
// 主函数
int main()
{
return 0;
}
// 创建结点
struct Node* CreateNode(DataType data){
// 1. 动态申请内存空间
struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));
// 检验申请失败(一般不会)
if(NULL == pnew) return;
// 2. 存储数据
pnew->data = data;
pnew->next = NULL;
// 3. 返回结点
return pnew;
}
// 尾插法
void AppendNode(struct Node* pHead,DataType data){
// 1. 创建新节点
struct Node* pnew = CreateNode(data);
// 定义一个游标指针
struct Node* ptemp = pHead;
// 2. 判断链表是否为空,为空直接挂上并退出
// 3. 链表不为空时,遍历寻找尾结点
while(ptemp->next){
ptemp = ptemp->next;
/* 核心代码!!! */
}
// 4. 挂新结点
ptemp->next = pnew;
}
// 头插法
void AddNode(struct Node* pHead,DataType data){
// 1. 链表为空直接返回
if(NULL == pHead->next) return;
// 2. 申请新结点
struct Node* pnew = CreateNode(data);
// 3. 链表不为空,执行营救行动!
pnew->next = pHead->next;
pHead->next = pnew;
}
// 指定插入
void PosNode(struct Node* pHead,int pos,DataType data){
// 1. 判断链表是否为空
if(NULL == pHead->next) return;
// 2. 创建新结点
struct Node* pnew = CreateNode(data);
// 3. 寻找插入位置
for(int i = 0; i < pos-1; i++){
// 如果循环过程中发现到达尾结点就直接挂新结点并退出
if(NULL == pHead->next) break;
pHead = pHead->next;
}
// 4. 挂结点(营救思路)
pnew->next = pHead->next;
pHead->next = pnew;
}
// 遍历链表
void travel(struct Node* pHead){
if (NULL == pHead) {
printf("链表为空!\n");
return;
}
struct Node* ptemp = pHead->next;//略过头
printf("List:");
while (ptemp){
printf("%d ", ptemp->data);
ptemp= ptemp->next;//指向下一个节点
}
printf("\n");
}
// 查找元素
struct Node* findNode(struct Node* pHead, DataType findData){
// 1. 判断链表是否为空
if (NULL == pHead) return NULL;
// 2. 略过头节点(使用游标)
pHead = pHead->pNext;
// 3. 寻找指定值(找到就返回地址,没找到就返回null)
while (pHead){
if (findData == pHead->data) return pHead;
pHead = pHead->pNext;
}
return NULL;
}
// 删除结点
void DeleteNode(struct Node* pHead,DataType aimdata){
// 1. 查找到目标值
struct Node* pDel = FindNode(pHead,aimdata);
// 2. 如果返回为 NULL 即操作目标不在链表中
if(NULL == pDel) return;
// 3. 设置游标,目的是找到操作目标的前一个结点
struct Node* pDel_prev = pHead; /* 此处注意!!! */
while(pDel_prev->next != pDel){
pDel_prev = pDel_prev->next;
}
// 4. 上述循环结束,即找了操作对象
/*
此时 pDel_prev 表示删除结点的前一个结点
pDel_prev->next 可以表示被删除的结点
pDel 表示被删除的结点
pDel->next 表示被删除结点的后一个结点
链接思路:被删除的前一个结点先链接被删除结点的后一个结点,再释放被删除结点!!!
*/
pDel_prev->next = pDel->next;
free(pDel);
printf("删除成功!\n");
}
// 修改链表元素
void ChangeNode(struct Node* pHead,DataType changedata,DataType aimdata){
// 1. 查找到目标值
struct Node* pChange = FindNode(pHead,aimdata);
// 2. 如果返回为 NULL 即操作目标不在链表中
if(NULL == pChange ) return;
// 3. 直接复制覆盖即可
pChange->data = changedata;
printf("修改成功!\n");
}}

我要回帖

更多关于 对链表设置头结点的作用 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信