28.单链表专题

文章目录
- 链表
-
- 每个节点的组成
- test.c
- SList.c
-
- 1.链表打印SLTPrint
- 2.申请新节点
- 3.尾插:SLTPushBack
- 4.头插
- 5.尾删
- 6.头删
- [**注意优先级://-> 优先级高于***](#注意优先级://-> 优先级高于*)
- 7.查找
- 8.在指定位置之前插入数据
- 9.删除对应节点
- 10.在指定位置之后插入数据
- 11.删除pos之后的节点
- 总代码
链表
每个节点的组成
- 这个节点的数据
- 下一个节点的指针
每个节点还有自己的指针(地址)
cpp
//单链表的节点
struct SListNode
{
int data;
struct SListNode* next;
};
//////////////////////////////////////////下面是升级版本
typedef int SLDataType;
//重命名
typedef struct SListNode
{
SLDataType data;
struct SListNode* next;
}STLNode;
test.c
- 创建节点指针
- malloc,强制类型转换
- 生成4个节点
- 第n个节点赋为n
- 链接节点
- 将每个节点的next等于下一个节点(指针,定义的时候本来就是指针)
- 最后一个节点的next为NULL
- 打印链表
- 传参传第一个节点
SList.c
1.链表打印SLTPrint
-
传入第一个节点的地址
-
while循环,条件是
用于不断指向下一个节点的指针还存在(不是NULL)
cpp
void SLTPrint(SLTNode* phead)
{
SLTNode* pcur = phead;
while (pcur)//pcur != NULL
{
printf("%d->", pcur->data);
pcur = pcur->next;
}
printf("NULL\n");
}
2.申请新节点
- 返回值是节点指针类型(原因是使用malloc),参数是DataType x
- malloc,强制类型转换,
- 判断malloc是否成功
- 如果不成功,就perror+exit(1)
- 将DataType x赋值给
新节点的data - 将
新节点的next置为空NULL - return 新节点
cpp
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
3.尾插:SLTPushBack
3.1错误的想法:
如果是空链表,比较特殊,特殊处理
phead不为空的情况
传参:
- 指向头节点的链表节点指针
- 想插入的内容(类型为链节)
将尾节点的next指向newnode
- 创建新节点函数
- 找到尾节点
- 定义一个ptail(尾巴的意思)
- 从头节点开始while循环,条件是ptail->next不为NULL,循环内容是ptail不断指向对应位置的next
- 将ptail的next指向新节点
phead==NULL的情况
直接将phead指向新创建的链节
3.2错误的原因:
当第一次尾插,也就是链表里还没有元素的时候,我想在尾差函数里将 头节点(这时候是空指针)改变成第一个节点 ,也就是改变头节点本身,这在函数中是不能实现的------因为我想实现的是改变一个参数的内容,传值调用无法实现
这有个问题就是最开始链表是空的然后这时候头节点plist = NULL,这时候我们无法对指针解引用,我们要做的就不是修改这个节点的next值了,而是对plist进行修改,所以这时候我们向尾插中传的参数应该是Plist的地址。👉 你要修改的是:plist 变量自己存的地址值!!!
3.3实参形参的对应

我们在创建节点的时候,创建的就是结构体指针plist,
如果想要改变里面的内容,得先找到这个节点,也就是先对plist解引用
3.4实现
- 传参:
- 考虑到列表为空的情况,有修改
链节指针(上面错误的想法想传的参数)的可能,所以我们这时候传链节指针的地址 - 传想增加的链节类型
- 考虑到列表为空的情况,有修改
- 断言二级指针,(我们传的是二级指针,在链表为空的时候,我们想通过二级指针改变一级指针,所以至少一级指针存在------>二级指针能够解引用)
- 生成nownode
- 增加链节的两种情况:
- 链表为空:通过解引用二级指针来改变一级指针
- 链表非空:
- 创建指针,while循环找到尾节点
- 改变尾节点的next
cpp
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
//*pphead 就是指向第一个节点的指针
//空链表和非空链表
SLTNode* newnode = SLTBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
//找尾
SLTNode* ptail = *pphead;
while (ptail->next)
{
ptail = ptail->next;
}
//ptail指向的就是尾结点
ptail->next = newnode;
}
}
4.头插
- 传参:
- 因为也可能涉及到链表为空的情况(需要修改头节点指针的内容),就需要传二级指针
- 想插入的链节
- 断言二级指针,(我们传的是二级指针,在链表为空的时候,我们想通过二级指针改变一级指针,所以至少一级指针存在------>二级指针能够解引用)
- 创造新节点
- 将新节点的next指向原来的头节点
- 将头节点变成新节点
cpp
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* newnode = SLTBuyNode(x);
//newnode *pphead
newnode->next = *pphead;
*pphead = newnode;
}
5.尾删
- 传参
- 因为也可能涉及到链表只有一个链节,尾删后为空的情况(需要修改头节点指针的内容为NULL),就需要传二级指针
- 断言
- 传来的二级指针不为空
- 二级指针指向的一级指针(链表首个链节的地址)不为空,链表存在
- 尾删的两种情况
- 如果只有一个链节(二级指针解引用后的一级指针对应的链节的next 为空),free+置空
- 多个链节:
- 创造链接指针,while循环找到尾节点,(while循环条件是节点的下一个指针不为空),
- 将尾节点的指针free+置空(置空有一点点多此一举------ 它本来就是局部变量,函数一结束就自动销毁了,置不置空都不影响链表;)
- 将尾节点的上一个节点的next置空(我们必须改
prev->next = NULL,这是在解引用修改链表本身的结构,把最后一段链接断开,这才是关键!)
cpp
//尾删
void SLTPopBack(SLTNode** pphead)
{
//链表不能为空
assert(pphead && *pphead);
//链表只有一个节点
if ((*pphead)->next == NULL) //-> 优先级高于*///真是血泪教训
{
free(*pphead);
*pphead = NULL;
}
else {
//链表有多个节点
SLTNode* prev = *pphead;
SLTNode* ptail = *pphead;
while (ptail->next)
{
prev = ptail;//有一点点 多此一举
ptail = ptail->next;//这才是关键!
}
//prev ptail
free(ptail);
ptail = NULL;
prev->next = NULL;
}
}
6.头删
-
传参*pphead(因为头删会改变
使用malloc时得到的链节的地址,如果想在函数中改变一个地址,传参就要是二级指针) -
assert二级指针+二级指针解引用(原指针不能为空,因为想删除)
-
创建一个next,保留首链节的next,(类型是链节指针类型)
注意优先级://-> 优先级高于*
-
free首链节的指针
-
将首链节的指针改为next
cpp//头删 void SLTPopFront(SLTNode** pphead) { //链表不能为空 assert(pphead && *pphead); SLTNode* next = (*pphead)->next; //-> 优先级高于* free(*pphead); *pphead = next; }
7.查找
-
返回值:看情况吧,老师写的是指针(,是为了给后面的指定位置插入函数服务,因为后面在指定位置之前插入数据对位置进行查找的时候,用的是节点的指针,如果这时候我们的返回值是节点的指针,到后面就可以直接使用find函数来确定有没有和要不要继续)
-
传参:
- 指针不传二级指针,只传一级指针,就是phead(因为不对一级指针进行修改)
- 传想查找的SLLDataType类型的x
-
新创建一个参数,pcur,更形象,不会影响phead
-
while循环,条件是pcur不为空
-
比较pcur中的data和x是否相等
-
相等就返回pcur
cpp//查找 SLTNode* SLTFind(SLTNode* phead, SLTDataType x) { assert(phead); SLTNode* pcur = phead; while (pcur) { if ( pcur->a== x ) return pcur; pcur = pcur->next; } return NULL; }
-
8.在指定位置之前插入数据
- 传参:
- 二级指针(如果在第一个节点之前插入数据,会改变头节点的指针,改变指针,用二级指针)
- 位置:传的是节点指针类型
- 想插入的数据:节点数据类型类型的
cpp
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead&&pos);
//首先是头插的情况
if (pos == *pphead)
{
SLTPushFront( pphead, x);
}
else
{
SLTNode* ptail = (*pphead)->next;
SLTNode* pre = (*pphead);
while (ptail!= pos)
{
pre = ptail;
ptail = ptail->next;
}
SLTNode* pnewnode = SLTBuyNode(x);
pnewnode->next = pre->next;
pre->next = pnewnode;
}
}
9.删除对应节点
cpp
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead&&pos);
if (*pphead == pos)
{
SLTPopFront( pphead);
}
else
{
SLTNode* ptail = (*pphead)->next;
SLTNode* pre = (*pphead);
while (ptail != pos)
{
pre = ptail;
ptail = ptail->next;
}
pre->next = ptail->next;
free(ptail);
ptail = NULL;
}
}
10.在指定位置之后插入数据
cpp
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode*pnewnode= SLTBuyNode( x);
pnewnode->next = pos->next;
pos->next = pnewnode;
}
11.删除pos之后的节点
cpp
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
SLTNode* next = pos->next;
pos->next = pos->next->next;
free(next);
next = NULL;
}
12.销毁链表
cpp
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* pre = pcur;
pcur = pcur->next;
free(pre);
}
*pphead = NULL;
}
总代码
test.c
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"reviseLLC.h"
int main()
{
SLTNode* plist = NULL;
SLTPushBack(&plist, 666);
SLTPushBack(&plist, 666);
SLTPushBack(&plist, 666);
SLTPushFront(&plist, 888);
SLTPushFront(&plist, 888);
SLTPopBack(&plist);
SLTPopBack(&plist);
SLTPopFront(&plist);
SLTPopFront(&plist);
SLTInsert(&plist, SLTFind(plist,666), 6868);
SLTInsert(&plist, SLTFind(plist,666), 123);
SLTInsert(&plist, SLTFind(plist,6868), 0 );
SLTErase(&plist, SLTFind(plist, 6868));
SLTErase(&plist, SLTFind(plist, 0));
SLTInsertAfter(SLTFind(plist,666), 888);
SLTInsertAfter(SLTFind(plist, 123), 666);
SLTEraseAfter(SLTFind(plist, 123));
SLTEraseAfter(SLTFind(plist, 666));
SLTPrint(plist);
SListDesTroy(&plist);
return 0;
}
SList.c
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"reviseLLC.h"
SLTNode* SLTBuyNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
perror("malloc newnode");
exit(1);
}
newnode->a = x;
newnode->next = NULL;
return newnode;
}
void SLTPrint(SLTNode* phead)
{
assert(phead);
SLTNode* pcur = phead;
while (pcur)
{
printf("%d ", pcur->a);
pcur = pcur->next;
}
printf("\n");
}
//头部插入删除/尾部插入删除
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
if (*pphead == NULL)
{
*pphead = SLTBuyNode(x);
}
else
{
SLTNode* pnewnode= SLTBuyNode(x);
SLTNode* pcur = *pphead;
while (pcur->next)
{
pcur = pcur->next;
}
pcur->next = pnewnode;
}
}
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
assert(pphead);
SLTNode* pnewnode = SLTBuyNode(x);
pnewnode->next = *pphead;
*pphead = pnewnode;
}
void SLTPopBack(SLTNode** pphead)
{
assert(pphead && *pphead);
//找到最后一个元素的前一个元素,改其next
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SLTNode* ptail = (*pphead)->next;
SLTNode* pre = (*pphead) ;
while (ptail->next)
{
pre = ptail;
ptail = ptail->next;
}
free(ptail);
pre->next = NULL;
}
}
void SLTPopFront(SLTNode** pphead)
{
assert(pphead && *pphead);
SLTNode* tmp = (*pphead)->next;
free(*pphead);
*pphead = tmp;
}
////查找
//SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
//{
// SLTNode* pcur = phead;
// while (pcur)//等价于pcur != NULL
// {
// if (pcur->data == x)
// {
// return pcur;
// }
// pcur = pcur->next;
// }
// //pcur == NULL
// return NULL;
//}
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
assert(phead);
SLTNode* pcur = phead;
while (pcur)
{
if ( pcur->a== x )
return pcur;
pcur = pcur->next;
}
return NULL;
}
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pphead && *pphead&&pos);
//首先是头插的情况
if (pos == *pphead)
{
SLTPushFront( pphead, x);
}
else
{
SLTNode* ptail = (*pphead)->next;
SLTNode* pre = (*pphead);
while (ptail!= pos)
{
pre = ptail;
ptail = ptail->next;
}
SLTNode* pnewnode = SLTBuyNode(x);
pnewnode->next = pre->next;
pre->next = pnewnode;
}
}
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pphead && *pphead&&pos);
if (*pphead == pos)
{
SLTPopFront( pphead);
}
else
{
SLTNode* ptail = (*pphead)->next;
SLTNode* pre = (*pphead);
while (ptail != pos)
{
pre = ptail;
ptail = ptail->next;
}
pre->next = ptail->next;
free(ptail);
ptail = NULL;
}
}
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode*pnewnode= SLTBuyNode( x);
pnewnode->next = pos->next;
pos->next = pnewnode;
}
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
SLTNode* next = pos->next;
pos->next = pos->next->next;
free(next);
next = NULL;
}
//销毁链表
void SListDesTroy(SLTNode** pphead)
{
SLTNode* pcur = *pphead;
while (pcur)
{
SLTNode* pre = pcur;
pcur = pcur->next;
free(pre);
}
*pphead = NULL;
}
SList.h
cpp
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType a;
struct SListNode* next;
}SLTNode;
SLTNode* SLTBuyNode(SLTDataType x);
void SLTPrint(SLTNode* phead);
//头部插入删除/尾部插入删除
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);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDesTroy(SLTNode** pphead);
