单链表-----重置版
创始人
2024-06-02 22:59:36
0

本期我们对之前讲过的单链表进行重置,增加了亿些细节,不过整体变化不大

(13条消息) 链表与其多种接口实现1-CSDN博客

目录

 链表的结构

打印

尾插

创建新节点

头插

尾删

头删

查找

任意位置前插入

任意位置删除

pos后插入

pos后删除

全部代码


我们仍然使用工程化的写法

 链表的结构

typedef int SLTDataType;
typedef struct SListNode {SLTDataType data;struct SListNode* next;
}SLTNode;

非常简单,一个存储数据的类型,一个链表节点的指针类型,用来指向下一个节点

我们用画图的方式来看看链表

我们一般画链表都会这样画,这是链表的逻辑结构,但实际当中,内存里是没有这些箭头什么的,我们来看看链表的物理结构

这是实际上链表的存储方式,大家要这样来理解,才能深刻理解链表到底是什么,这些0x112等等都是地址,链表是靠地址来找下一个节点的 

打印

void SLTPrint(SLTNode* phead)//打印
{SLTNode* cur = phead;while (cur) {printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}

我们先完成打印,依次打印链表的元素,方便我们观察,我们发现,这次我们没有使用断言,为什么?phead需要断言吗?答案是不需要,如果大家看了之前的顺序表的话,会发现顺序表进行了断言,这是为什么?因为顺序表并不是直接存在结构体里的,而是我们在顺序表的结构体里设置一个指针a,指向了一个数组,数组里才存放的是数据,如果顺序表为空,是由他的容量为0来控制的,而不是指针为空,这里的链表为空,是可以的,链表为空的话就是一个null,也是可以打印的,所以我们这里没有进行断言

尾插

void SLTPushBack(SLTNode* phead, SLTDataType x)//尾插
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL) {perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;//找尾if (phead == NULL) {phead = newnode;}else {SLTNode* tail = phead;while (tail->next != NULL) {tail = tail->next;}tail->next = newnode;}
}

我们创建一个新节点,然后将他初始化,接着我们找到原链表的尾节点,将新节点链接在尾节点即可,如果链表本身为空,那我们把新节点就设为头节点

尾插的本质是原尾节点中存储新尾节点的地址,我们来画图演示

 ​​​​​

 看着这张图,大家就可以深刻理解尾插是怎么回事了

 我们对代码进行测试

发现测试结果和我们想的并不一样,这是为什么呢?我们慢慢往下看

我们知道,形参的改变并不影响实参 

我们传x的地址就可以改变x,这是传值和传址的区别

我们来看这样一段代码

 

画在内存里就是这样的,ptr的改变并不会影响px,而且后边还会释放Fun,存在内容泄漏的问题,我们来对比上下几个函数,我们想改变x时,x是int类型,我们传的参数为int*才可以改变x,而int不可以,这里我们想改变px,px为int*类型,那我们传int*类型正确吗?显然是错误的,我们应该传int**才可以改变int*

我们看这段代码

  

我们这次传的是px的地址,所以*pptr就是px, 此时我们申请的空间就可以用px访问了

 此时再回过头来想我们上面的尾插函数,为什么错了呢?因为我们要修改SLTNode*,我们传的却还是SLTNode*,我们需要传入SLTNode**才可以改变SLTNode*

修改后的尾插为

void SLTPushBack(SLTNode** pphead, SLTDataType x)//尾插
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL) {perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;//找尾if (*pphead == NULL) {*pphead = newnode;}else {SLTNode* tail = *pphead;while (tail->next != NULL) {tail = tail->next;}tail->next = newnode;}
}

此时就可以正常运行了,这里就可能会有人迷惑,为什么自己在学习时的链表没有使用二级指针呢?这有多种原因,其中一种就可能是你学习的链表是有哨兵位的头节点的,关于哨兵位,循环链表,双向,这些结构之后会出一期专门讲解,这里不再探究

到这里可能会有人发现,我们在找尾时,链表为空使用的是二级指针,而不为空时使用的是一级指针,这是为什么呢? 

大家在这里要想清楚,我们要改变int,要用int*,要改变int*要用int**,我们要改变SLTNode*时就要用SLTNode**,而我们的PushBack函数,我们调用了四次

我们四次都传的是plist的地址,但是其实从第二次开始,我们就用不上plist的地址了,但因为我们调的是同一个函数,所以才会这样(我们总不可能多写一个函数来区分链表为空时的尾插和链表不为空时的尾插吧),只有当plist为空,也就是*pphead为空时,才会使用plist的地址,后边都用不上了,我们拿尾插4时举例

此时的大概就是这样一个情况,我们此时要把tail的next指向newnode,我们这里需要改变的是结构体还是结构体的指针?我们要改变的是结构体,tail是指向这个结构体的,我们有了结构体的指针,是可以改变结构体的,把结构体的next改变,我们现在要改变的是Node,改变的是这个节点,所以我们用的是Node*,大家看看链表的结构体,再画一下图就可以想明白了

此时我们回过头来再看Print函数,需要传二级吗?可以,但没必要,因为我们并没有改变任何东西

创建新节点

我们在写头插之前,发现我们的头插和尾插都需要创建新的节点,所以我们把他提取出来,写成一个函数

SLTNode* BuySLTNode(SLTDataType x)//创建新节点
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL) {perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}

将尾插的前一段代码提取即可,再修改一下malloc失败的情况,返回NULL即可

头插

void SLTPushFront(SLTNode** pphead, SLTDataType x)//头插
{SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}

头插就非常简单,三句搞定,我们让新节点的next指向plist(即*pphead),然后再把新节点变为头节点即可,而且,这段代码也可以处理plist为空的情况

尾删

void SLTPopBack(SLTNode** pphead)//尾删
{SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL) {prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;
}

尾删我们除了要找到尾节点,还需要找到前一个节点,不然我们释放尾节点的空间后,前一个节点的指向不是NULL,就是野指针了

还可以这样写

void SLTPopBack(SLTNode** pphead)//尾删
{SLTNode* tail = *pphead;while (tail->next->next != NULL) {tail = tail->next;}free(tail->next);tail->next = NULL;
}

当我们进行测试后,其实是会出现问题的,两种写法都有问题,当链表只剩一个节点时,第二种写法首先出错,tail为空时也会出问题,当只剩一个节点时,第一种写法不会进入循环,直接free掉节点,此时最后一句代码,prev->next就会出现问题,所以我们进行修改

void SLTPopBack(SLTNode** pphead)//尾删
{//暴力检查//assert(*pphead != NULL);//温柔的检查if (*pphead == NULL) {return;}//只有一个节点if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;}else {//多个节点/*SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL) {prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;*/SLTNode* tail = *pphead;while (tail->next->next != NULL) {tail = tail->next;}free(tail->next);tail->next = NULL;}
}

我们分情况讨论即可,对于链表为空时,我们进行检查,大家自行选择即可

头删

void SLTPopFront(SLTNode** pphead)//头删
{//严厉的检查//assert(*pphead != NULL);//温柔的检查if (*pphead == NULL) {return;}SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}

有了尾删的经验,头删就非常简单了

查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)//查找
{SLTNode* cur = phead;while (cur) {if (cur->data == x) {return cur;}cur = cur->next;}return NULL;
}

我们有时也需要查找数据,所以我们写出查找函数,找到的话返回该节点,否则返回NULL,有了查找我们就可以完成许多事情了

任意位置前插入

void SLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x)//pos位置前插入
{assert(pos);assert(pphead);if (pos == *pphead) {SLTPushFront(pphead, x);}else {//找到pos前的位置SLTNode* prev = *pphead;while (prev->next!=pos) {prev = prev->next;}SLTNode* newnode = BuySLTNode(x);prev->next = newnode;newnode->next = pos;}
}

当我们有了查找,就可以和任意位置插入一起使用,我们写出任意位置插入,当pos是头节点时,我们直接头插即可,否则我们找到pos的前一个节点,接着我们创建一个新节点,将新节点插入到pos前即可,另外,我们还需要在开始时进行断言,防止传的pos是不存在的,还要判断一下pphead,我们的链表是plist,pphead是plist的地址,所以pphead绝对不会为空,所以需要进行断言,这里就可以发现,我们之前写的函数很多都是要判断pphead的,我们这里对之前的函数进行补充,尾插尾删头插头删这些需要plist的,pphead都不可能为空,所以都需要加上断言,大家进行补充即可,另外,在尾删时,我们还判断了*pphead不能为空,我们需要把判断pphead放在*pphead之前,否则判断pphead就没有意义(因为已经解引用了),头删同理,大家发现,头插和尾插没有判断*pphead,为什么呢?当链表为空时,我们是可以插入的,而链表为空时,我们是不能删除的,所以不是所有的情况都需要断言,大家要根据实际情况来进行判断

任意位置删除

void SLTErase(SLTNode** pphead, SLTNode* pos)//pos位置删除
{assert(pphead);assert(pos);//assert(*pphead);if (pos == *pphead) {SLTPopFront(pphead);}else {SLTNode* prev = *pphead;while (prev->next != pos) {prev = prev->next;}prev->next = pos->next;free(pos);}
}

接着,我们来完成pos位置的删除,我们先进行断言,这里大家发现,我们没有对*pphead进行断言,这是因为对pos进行断言的话,间接对*pphead进行了断言,如果pos没有传错的话,那么*pphead就绝对不会为空,所以我们这里没有进行断言,不过进行断言也是可以的,没有影响,删除pos位置的函数也非常简单,如果pos是头,我们进行头删即可,否则找到pos之前的位置,将pos之前的节点的next指向pos的next,接着free掉pos即可,这里释放空间后,pos没有置空,是因为形参的改变不影响实参

这里再给各位开拓一下思维,如果我们只给了pos位置,没有给pphead,我们可以在pos前插入吗?答案是可以的,我们先在pos后插入,然后交换pos和这个节点的值即可

如果只给了pos位置,没有给pphead,我们可以进行删除pos位置吗?也是可以的,我们将pos后一个节点的值给pos,然后删掉pos后一个节点即可,不过这个写法是不能删尾的

这里我们发现,单链表在之前插入和某个位置的删除是非常麻烦的,而在某个位置后删除和插入是比较舒服的

pos后插入

void SLTInsertAfter(SLTNode* pos, SLTDataType x)//pos之后插入
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}

这个函数和任意位置插入的区别在于这个不需要pphead,但缺点也是不能头插

pos后删除

void SLTEraseAfter(SLTNode* pos)//pos之后删除
{assert(pos);assert(pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

与上面的函数相对,还有pos后的删除,也很简单,我们要注意断言一下

以上即为本期的全部内容,希望大家有所收获,最后在这里附上全部代码

全部代码

#pragma once
//SList.h
#include 
#include
#includetypedef int SLTDataType;
typedef struct SListNode {SLTDataType data;struct SListNode* next;
}SLTNode;void SLTPrint(SLTNode* phead);//打印
SLTNode* BuySLTNode(SLTDataType x);//创建新节点
void SLTPushBack(SLTNode** pphead,SLTDataType x);//尾插
void SLTPushFront(SLTNode** pphead, SLTDataType x);//头插
void SLTPopBack(SLTNode** pphead);//尾删
void SLTPopFront(SLTNode** pphead);//头删
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);//查找
void SLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x);//pos位置前插入
void SLTErase(SLTNode** pphead, SLTNode* pos);//pos位置删除
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//pos之后插入
void SLTEraseAfter(SLTNode* pos);//pos之后删除
#include"SList.h"
//SList.cvoid SLTPrint(SLTNode* phead)//打印
{SLTNode* cur = phead;while (cur) {printf("%d->", cur->data);cur = cur->next;}printf("NULL\n");
}SLTNode* BuySLTNode(SLTDataType x)//创建新节点
{SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL) {perror("malloc fail");return NULL;}newnode->data = x;newnode->next = NULL;return newnode;
}void SLTPushBack(SLTNode** pphead, SLTDataType x)//尾插
{assert(pphead);SLTNode* newnode = BuySLTNode(x);if (*pphead == NULL) {*pphead = newnode;}else {//找尾SLTNode* tail = *pphead;while (tail->next != NULL) {tail = tail->next;}tail->next = newnode;}
}void SLTPushFront(SLTNode** pphead, SLTDataType x)//头插
{assert(pphead);SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}void SLTPopBack(SLTNode** pphead)//尾删
{assert(pphead);//严厉的检查assert(*pphead);//温柔的检查/*if (*pphead == NULL) {return;}*///只有一个节点if ((*pphead)->next == NULL) {free(*pphead);*pphead = NULL;}else {//多个节点/*SLTNode* prev = NULL;SLTNode* tail = *pphead;while (tail->next != NULL) {prev = tail;tail = tail->next;}free(tail);tail = NULL;prev->next = NULL;*/SLTNode* tail = *pphead;while (tail->next->next != NULL) {tail = tail->next;}free(tail->next);tail->next = NULL;}
}
void SLTPopFront(SLTNode** pphead)//头删
{assert(pphead);//严厉的检查assert(*pphead);//温柔的检查/*if (*pphead == NULL) {return;}*/SLTNode* first = *pphead;*pphead = first->next;free(first);first = NULL;
}SLTNode* SLTFind(SLTNode* phead, SLTDataType x)//查找
{SLTNode* cur = phead;while (cur) {if (cur->data == x) {return cur;}cur = cur->next;}return NULL;
}void SLTInsert(SLTNode** pphead, SLTNode* pos,SLTDataType x)//pos位置前插入
{assert(pos);assert(pphead);if (pos == *pphead) {SLTPushFront(pphead, x);}else {//找到pos前的位置SLTNode* prev = *pphead;while (prev->next!=pos) {prev = prev->next;}SLTNode* newnode = BuySLTNode(x);prev->next = newnode;newnode->next = pos;}
}void SLTErase(SLTNode** pphead, SLTNode* pos)//pos位置删除
{assert(pphead);assert(pos);//assert(*pphead);if (pos == *pphead) {SLTPopFront(pphead);}else {SLTNode* prev = *pphead;while (prev->next != pos) {prev = prev->next;}prev->next = pos->next;free(pos);}
}void SLTInsertAfter(SLTNode* pos, SLTDataType x)//pos之后插入
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}void SLTEraseAfter(SLTNode* pos)//pos之后删除
{assert(pos);assert(pos->next);SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

如有错误,还请指正

相关内容

热门资讯

工业遗存改造催生京津冀文旅新地... 转自:北京日报客户端炼钢高炉变身科幻体验馆,运料通廊改造为极限运动场,工业厂房里开出网红餐厅……北京...
全国游泳冠军赛潘展乐男子400... 中国青年报客户端湖州5月17日电(中青报·中青网记者 慈鑫)今晚在深圳举行的2025全国游泳冠军赛男...
突发!广西山洪,3人遇难1人失... 转自:央视财经记者从广西百色市隆林各族自治县应急管理局获悉,5月16日至17日,受高空槽和低层切变线...
国羽包揽泰国公开赛混双冠亚军 ... 20岁的高家炫和22岁的吴梦莹是今年年初通过调赛成功进入国羽一队的年轻混双组合。今年2月,两人首次代...
“先增聘再离任”又添一例,景顺... 转自:扬子晚报网澎湃新闻记者 丁欣晴景顺长城基金百亿基金经理鲍无可离职传言终成真。5月17日,景顺长...
湾区法律专家共议仲裁制度创新 ... 本报记者 赵毅 广州报道在全球经济深度调整与国内产业转型升级的双重驱动下,中国企业正加速“走出去”。...
科普帖!运动和不运动的人差别有... 【科普帖!#运动和不运动的人差别有多大#?】坚持跑马拉松后更“显老”了?#运动加速衰老还是延缓衰老#...
锁价≠最低价!南航9.9元起的... “提前10天至3个月,支付9.9元起的费用,用户就可将意向行程机票价格锁住3天或7天”。继“锁座”之...
体验堪称“小15” Xiaom...   轻薄成为目前手机行业的主旋律,特别是针对年轻用户打造的时尚手机,为此在全新推出的Xiaomi C...
下周开始,涉及长春这些小区! 长春燃气股份有限公司于2025年5月19日至5月25日对以下小区居民用户的户内燃气设施进行入户安全检...
青少年舞者竞逐梦想,用足尖点亮... 转自:上观新闻时光荏苒,卢湾体育馆的穹顶下,今日再度被少年的足尖叩响。5月17日,上海市民体育舞蹈节...
宁德时代:山东时代电池生产基地... 人民财讯5月17日电,近日,山东时代储能及动力电池生产基地在济宁市兖州区正式投产,这是宁德时代在北方...
蔡奇在河北唐山市调研时强调 坚... 转自:北京日报客户端新华社石家庄5月17日电中共中央政治局常委、中央书记处书记蔡奇16日至17日在河...
滴水湖旅游度假区主题音乐会唱响... 今天(5月17日),滴水湖畔,“年轻的城”为迎接第14个“中国旅游日”及520“临港遇见爱”主题月,...
“俄罗斯礼品季”亮相哈尔滨 数... 中新社哈尔滨5月17日电 (记者 王妮娜)“俄罗斯制造”节展17日在哈尔滨举行开幕式,来自俄罗斯35...
绝味鸭脖被指缺斤少两? 转自:荔枝新闻 【#绝味鸭脖被指缺斤少两#?】#女子称绝...
“点亮网络文明之光”2025年... 5月16日,“点亮网络文明之光”2025年网上主题宣传活动在滨海城市大连正式启动。本次活动联动大连、...
「教」量|高途一季度突然盈利:... 21世纪经济报道记者王峰北京报道  近日,高途教育科技集团(NYSE:GOTU)发布2025财年第一...
航行警告 黄海水域有海上火箭发... 转自:财联社【航行警告 黄海水域有海上火箭发射】财联社5月17日电,据中国海事局网站消息,日照海事局...
广西隆林突发山洪致3人遇难,1...   记者从广西百色市隆林各族自治县应急管理局获悉,5月16日至17日,受高空槽和低层切变线共同影响,...