91.【C语言】数据结构之单向链表的头删和尾删
目录
1.尾删函数SLTPopBack
代码示例(写入SList.c)
在SList.h中写入该函数的声明
main.c部分代码改为
编辑
分析
解决方法
方法1:双指针算法(快指针tail,慢指针pretail)
方法2
2.头删函数SLTPopFront
一个节点示意图
多个节点示意图
代码示例(写入SList.c)
在SList.h中写入该函数的声明
main.c部分代码改为
承接87.【C语言】数据结构之链表的头插和尾插文章
1.尾删函数SLTPopBack
代码示例(写入SList.c)
void SLTPopBack(SLTNode** pphead)
{
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
tail = tail->next;
}
free(tail);
tail = NULL;
}
在SList.h中写入该函数的声明
main.c部分代码改为
void TestSList1()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPopBack(&plist);
SLTPrint(plist);
}
运行后出了问题
分析
虽然为tail使用free函数并将tail置NULL,但是tail前面的节点含的指针并没有置NULL,导致其为野指针,因而出现了-572662307这样的随机值
解决方法
找到tail前的节点含的指针,并将其置NULL
方法1:双指针算法(快指针tail,慢指针pretail)
void SLTPopBack(SLTNode** pphead)
{
SLTNode* pretail = NULL;//初始化慢指针
SLTNode* tail = *pphead;
while (tail->next != NULL)
{
pretail = tail;//待tail指针内容改变前,先赋值给慢指针
tail = tail->next;
}
pretail->next = NULL;//tail的前一个节点含的指针置NULL
free(tail);
tail = NULL;
}
方法2
void SLTPopBack(SLTNode** pphead)
{
SLTNode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
tail->next->next != NULL//跨了一个节点
看起来没有问题,但是将main.c部分代码改成
void TestSList1()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
SLTPopBack(&plist);
SLTPrint(plist);
}
就会出问题
当链表为1->NULL时,tail->next->next != NULL会导致读取访问权限冲突(原因:tail->next为NULL,NULL->next不合法)
如果用方法1,也会出现同样的问题
因此要在尾删函数的一开始做出判断:1.是否为空链表? 2.链表是否只有一个节点?
修正后的代码
void SLTPopBack(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);//检查是否为空链表,空链表不可以尾删
//检查链表是否只有一个节点
if ((*pphead)->next == NULL)
{
free(*pphead);//*pphead就是plist
*pphead = NULL;
}
else
{
SLTNode* tail = *pphead;
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
注意断言的顺序:先断言pphead,再断言*pphead!
2.头删函数SLTPopFront
和尾删函数一样:一开始做出判断:1.是否为空链表? 2.链表是否只有一个节点?
空链表不可头删,直接断言
一个节点示意图
多个节点示意图
一个节点和多个节点的处理方式可以合并在一起
代码示例(写入SList.c)
void SLTPopFront(SLTNode** pphead)
{
assert(pphead);
assert(*pphead);//检查是否为空链表
SLTNode* first = *pphead;
*pphead = first->next;
first = NULL;
}
注意断言的顺序:先断言pphead,再断言*pphead!
在SList.h中写入该函数的声明
main.c部分代码改为
void TestSList1()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 1);
SLTPushBack(&plist, 2);
SLTPushBack(&plist, 3);
SLTPushBack(&plist, 4);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
SLTPopFront(&plist);
SLTPrint(plist);
}