当前位置: 首页 > news >正文

数据结构之单链表

Hello各位小伙伴们,上期我们讲解了顺序表,今天让我们一起来学习一下单链表吧。

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

 图中指针变量 plist保存的是第一个结点的地址,我们称plist此时“指向”第一个结点,如果我们希望plist“指向”第二个结点时,只需要修改plist保存的内容为0x0012FFA0。​
链表中每个结点都是独立申请的(即需要插入数据时才去申请一块结点的空间),我们需要通过指针变量来保存下一个结点位置才能从当前结点找到下一个结点。

 结合前面学到的结构体知识,我们可以给出每个结点对应的结构体代码:

struct SListNode
{int data;//每个节点中要保存的数据struct SListNode*next;//每个节点中要包含下一个节点的地址
}

实现单链表

和上期我们讲解的实现顺序表相同我们在实现单链表的时候创建3个文件,一个头文件,两个.C文件,实现单链表我们从如下几个方面来完成!

头文件:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDatatype;
typedef struct SListNode
{int data;struct SListNode* next;
}SLTNode;
//链表的打印
void SLTPrint(SLTNode* phead);
//链表的尾插
void SLTPushBack(SLTNode** phead,SLDatatype x);
//链表的头插
void SLTPushFront(SLTNode** pphead, SLDatatype x);
//链表的尾删
void SLTPopBack(SLTNode** pphead);
//链表的头删
void SLTPopBack(SLTNode** pphead);
//链表的查找
SLTNode* SLTFind(SLTNode* pphead, SLDatatype x);
//在指定位置插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLDatatype x);
//在指定位置之后插入数据
void SLTInsertBack(SLTNode* pos, SLDatatype x);
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的节点
void SLTEraseBack(SLTNode* pos);
//链表的销毁
void SListDestory(SLTNode** pphead);

1.链表的打印

实现链表的打印我们要遍历链表,同时创建一个新的结构体变量来记录头节点。

代码如下:

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

第一次学习单链表的同学一定要注意这里的pur = pur->next,这里是让pur这个节点走到下一步的关键!

2.链表的尾插

大致逻辑在进行尾插的时候注意要申请新的空间来插入数据,同时要分为两种情况 1>.当链表不为空的时候,需要将链表遍历到最后一个节点的位置进行尾插。2>.链表为空的时候,直接将插入即可。

在进行链表的尾插操作时候我们注意到函数参数传递的变量是二级指针,我们先来看看传递一级指针的时候。

void SLTPushBack(SLTNode** phead,SLDatatype x);
void SLTPushBack(SLTNode*phead, SLTDatatype x)
{SLTNode* newnode = SLTBuyNode(x);//链表为空的情况下if (phead == NULL){phead = newnode;}else {//链表不为空的情况下,需要先遍历找到尾节点SLTNode* plist = phead;while (plist->next != NULL){plist = plist->next;}//申请新的节点空间,并将新的节点空间插入到为节点plist->next = newnode;}
}

让我们进行调试!

在进行调试的过程中我们将函数的实参部分与形参部分分别进行了调试,同时不仅仅将尾插函数进行调试也将空间的申请包含进来。我们发现随着程序一步步进行形参如我们所预料的那样值发生了变化,而实参部分并没有发生变化。这时我们就需要考虑是否是传参发生了问题!

将函数传参部分改为二级指针!

我们发现传递二级指针的时候就会实现实参的改变!

3.链表的头插

进行头插操作与尾插操作类似,不同之处是不用在讨论首节点为NULL的情况!

void SLTPushFront(SLTNode** pphead, SLTDatatype x)
{assert(pphead);SLTNode* newnode = SLTBuyNode(x);//链表不为空的情况下newnode->next = *pphead;*pphead = newnode;//链表为空的情况下也是如此
}

4.链表的尾删操作

在进行链表尾删的时候我们要注意不能直接将尾节点释放掉,因为在未节点的头一个节点之前包含尾节点的指针,直接释放的化会导致野指针。

 我们应该在创建一个变量SLTNode*perv在进行遍历的时候记录尾节点的前一个节点,将perv->next与尾节点同时释放掉。

代码操作:

//链表的尾删
void SLTPopBack(SLTNode** pphead)
{//值得注意的是我们不能对空节点进行尾删assert(*pphead && pphead);if ((*pphead)->next == NULL)//当只剩下一个节点的时候{free(*pphead);*pphead = NULL;}else {//在遍历链表找尾节点的时候我们要找到尾节点的前一个节点SLTNode* perv = *pphead;SLTNode* ptail = perv->next;while (ptail->next != NULL){perv = ptail;ptail = ptail->next;}free(ptail);ptail = NULL;perv->next = NULL;}
}

5.链表的头删

在进行链表的头删操作时,要注意创建新的变量是删除操作能进行下去。

void SLTPopFront(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = (*pphead)->next;//创建新的变量记录头节点的下一个指针free(*pphead);*pphead = pcur;
}

6.链表的查找

在进行链表的查找操作中我们要遍历链表

SLTNode* SLTFind(SLTNode* pphead, SLTDatatype x)
{//直接遍历查找即可SLTNode* pcur = pphead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}

我们在test.c文件中进行测试:

void test06()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPushBack(&plist, 5);SLTNode* ret = SLTFind(plist,5);if (ret == NULL){printf("没找到了");}else {printf("找到了");}}
int main()
{//test01();//手动创建一个新节点//test02();//尾插//test03();//头插//test04();//尾删//test05();//头删test06();//查找return 0;
}

同时我们要注意一下函数的返回类型。

7.在指定位置之后插入数据

执行此操作我们不需要遍历链表,但要注意赋值的顺序。如果搞不懂的话可以创建一个临时变量来保存。

//在指定位置之后插入数据
void SLTInsertBack(SLTNode** pphead, SLTNode* pos, SLTDatatype x)
{SLTNode* newnode = SLTBuyNode(x);newnode ->next= pos->next;pos->next = newnode;
}

8.在指定位置插入数据

我们要遍历一遍链表然后找到pos之前的位置,进行节点的插入。

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDatatype x)
{assert(pphead && pos && *pphead);//要找到pos节点和pos节点的prev节点//当pos是头节点的时候,相当于头插if (pos == *pphead){SLTNode* newnode = SLTBuyNode(x);newnode->next = *pphead;*pphead = newnode;}else {SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = SLTBuyNode(x);newnode->next = pos;prev->next = newnode;}
}

9.删除指定位置的节点

删除指定位置的节点,我们需要遍历链表找到pos之前的节点,同时要注意头节点的删除。

void SLTErase(SLTNode** pphead, SLTNode* pos)
{	SLTNode* prev = *pphead;//要注意头删if (pos == *pphead){SLTPopFront(pphead);}//需要遍历pos节点的头一个节点else {while (prev->next != pos){prev = prev->next;}prev->next = pos->next;}
}

10.链表的销毁

进行链表的销毁:我们在申请空间的时候是不连续物理逻辑申请,所以在销毁时我们要对链表进行遍历销毁,同时我们要注意对头节点的销毁。

void SListDestory(SLTNode** pphead)
{SLTNode* perv = *pphead;while (perv){SLTNode* next = perv->next;free(perv);perv = next;}free(*pphead);*pphead = NULL;
}

ok,今天单链表的内容就到这里啦,各位小伙伴我们下期再见!


http://www.mrgr.cn/news/54070.html

相关文章:

  • 机器学习方向在算法优化上有哪些创新点?
  • Snort浅析
  • 【Python网络编程】学习Socket编程,打造网络应用!
  • 常用DateUtils工具类
  • 碰到这个问题请更新或重新安装fastapi版本
  • Python包---numpy1
  • 2063:【例1.4】牛吃牧草
  • CSDN Markdown 编辑器语法大全
  • 商​汤​二​面
  • 餐饮店怎么标注地图位置信息?
  • 2062:【例1.3】电影票
  • 48.旋转图像
  • FloodFill 算法(DFS)
  • [C++] C++类和对象 类的初始化列表和静态成员 类型转换/友元/内部类/匿名对象/编译器优化
  • Symbol简单介绍
  • 【电子通识】案例:两个按键同时按下把boot拉低电路如何设计?
  • VIT:论文关键点解读与常见疑问
  • mac安装jdk8
  • Linux——应用软件的生命周期
  • 0x3D service
  • 监督学习、无监督学习、半监督学习、强化学习、迁移学习、集成学习分别是什么对应什么应用场景
  • ReLitLRM: Generative Relightable Radiance for Large Reconstruction Models 论文解读
  • 二叉树刷题(JAVA)
  • NeRF三维重建—神经辐射场Neural Radiance Field(二)体渲染相关
  • Lua变量
  • 深⼊理解指针(2)