数据结构——链表讲解(2)

news/2025/3/15 0:58:47/

作者:几冬雪来

时间:2023年3月5日

内容:数据结构链表讲解

目录

前言: 

剩余的链表应用: 

1.查找:

2.改写数据:

3.在pos之前插入数据: 

4.pos位置删除:

5.在pos的后面插入:

6.pos后面进行删除: 

7.代码:

 结尾:


前言: 

在上一篇博客中,我们初步认识了C语言,还完成了对链表初始化,头删头插和尾插尾删的代码书写和说明。并且对链表中代码书写过程可能会遇到的坑进行了讲解,最后还进行了链表与顺序表之间的对比。这让我们对链表了解了不少,但是链表的内容并没有结束,这篇博客我们将进一步的了解链表。

剩余的链表应用: 

在上一篇博客中,我们讲解了链表的初始化,头插头删等四种应用方法,但是和顺序表一样,链表也不只这几种应用而已。那么今天我们将会将链表剩下的程序都讲完。 

1.查找:

 首先就是在链表中查找一个数据,链表查找数据和顺序表查找数据的方法是异曲同工的,也就是暴力查找,这里就不进行过多的说明,直接上代码。

  

这个就是我们查找值的代码,先把phead的值赋给我们创立的cur,接下来写一个循环来判断,再后来在循环中嵌套一个判断语句如果cur->data为我们要寻找的值,那么就可以直接返回cur的地址,第一次没有找到的话就让cur->next并赋值给cur。相反,如果整个链表都遍历完了还是没有找到那个值,这里就直接返回空

2.改写数据:

在书写完了查找数据的代码,接下来就是增删查改中的改,也就是改写数据。但是这里改写数据并不用写一个全新的代码,我们可以沿用上面查找数据的代码,并对其略微进行添加和修改。 

这里就需要我们先在链表中找到那个值后,对其进行修改,这就是修改链表数据的方法。

3.在pos之前插入数据: 

讲解完了两个较清楚的代码之后,我们来讲在pos之前插入数据的方法。

既然了解了原理,那么下来就开始着手代码的书写了。

首先代码一进去就需要对其断言如果pos传了一个我们没有的值,那么代码下面的prev->next的循环条件就一直不满足,最后会访问到空指针。 

如果我们的pos的地址和pphead的地址一致的话,那么这里就相当于头插操作。要是不一致的话,我们先创建一个指针指向第一个结点的位置。而后进行判断,prev->next不为pos的时候,prev进行修改赋值,这里找到的为pos的前一个值

剩下的就更加简单了,找到pos前一个值后,我们想创立一个新结点,并命名为newnode

 然后让我们原先上一个结点的next指向这个新结点的地址。有因为创建结点的时候,newnode->next为0,因此这里要将pos给newnode->next,使它指向pos

 

这里就将我们4之前(第一个4)插入一个值为20的结点。 

那么在我们插入代码的过程中,只需要对pos进行断言吗?其实不止,在这里不仅仅要对pos进行断言,而且还要对pphead进行断言

这是为什么呢?还记得我们的**pphead是指向指针plist的地址,那么如果plist为空的话,pphead会为空吗?这里是一定不会的,为什么?我们画一张图来了解一下。

这里plist为指针,如果plist为空的话。但是在这里的**pphead为指针,存放的是plist这个值的地址,即使plist为空但是它还是有地址的存在,地址不为空。 

在数据结构中,断言的操作并没有什么规律可循,我们只要知道当我们一个值一定不能为空的时候要进行断言。 

既然知道了这个道理,那我们上面的头删尾删等涉及到pphead的地方都应该进行断言,为的是防止我们传错

4.pos位置删除:

下一个就是我们pos位置的删除,有了上面的基础,一开始我们就要对其进行断言。

这里如果指针pos等于pphead,pphead指向第一个结点的地址,那么这个时候这里就相当于我们的头删操作了。 

如果这里的pos不为头结点的地址,那么就进入另一个分支语句。将头结点的位置赋值给新创立的一个指针prev,如果prev->next不为我们要的值,那就对prev进行修改,找到以后将我们pos->next也就是pos下一个结点的地址给pos上一个结点的next,最后将pos进行释放即可。 

这里将pos释放之后,我们可以不用将它置为空。因为我们的pos是语句局部变量,形参的改变不影响实参。 

在这里我们pos置空的操作在这里进行即可。 

通过书写上面代码我们发现,单链表其实不太适合进行前面插入和删除当前位置,我们的单链表更适合后面插入或者删除后面位置。

5.在pos的后面插入:

在这里我们知道在单链表中不太适合进行前面插入和删除当前位置,我们通常运用的是后面插入和删除后面位置的值。那么现在就先来讲解——在pos的后面插入的代码是怎么样写的。

首先还是我们的断言,并且创建一个新的结点来进行插入操作。 

那么我们的代码是怎么样进行的呢? 

这里就是单链表pos后面插入一个值的方法,但是有一部分人跟着这个图写代码却掉进坑里,这又是怎么回事? 我们将它们的代码写出:

 这个代码大家看得出来哪里出错了吗?我们来看一看,在创建新结点后,newnode的值赋值给pos->next,这里的next就是指向我们newnode的值,而后再把原pos->next,也就是我们插入后newnode后面的一个结点,我们将它的地址交给newnode

看似一切都没有问题,逻辑上说得通,且看样子貌似可以运行,但是实际上这个代码是错误的。

  

在这个代码中,我们pos->next指向我们的新结点这一步是没有错误的,那么报错的就是我们接下来的一步了,这里代码的pos的next原本指向的是未修改前下一个结点的地址,但是在上一步我们在上一步就另我们的pos->next指向我们的新结点,因此这里的pos->next的赋值已经不是原来的值

 那我们的代码要怎么修改,其实很简单。

我们只需要将两个代码的书写顺序调换一下即可,这里也提醒我们写代码要注意它的执行顺序。 

6.pos后面进行删除: 

接下来就是我们的pos后面进行删除,删除的代码相较于插入的代码可能还要更简单一点。

在这里我们仅需要注意要将我们删除的值先进行一次保留,如果直接pos->next=pos->next->next的话,中间那个元素就会被省略掉。 

7.代码:

到这里我们的链表内容基本结束了,在最后我将所有的代码写上。

SList.h文件 

#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;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);void SLTErase(SLTNode** pphead, SLTNode* pos);//pos后面插入
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//pos位置后面删除
void SLTEraseAfter(SLTNode* pos);

 test.c文件

#include "SLish.h"//void TestSList1()
//{
//	SLTNode* plist = NULL;
//	SLTPushBack(&plist, 1);
//	SLTPushBack(&plist, 2);
//	SLTPushBack(&plist, 3);
//	SLTPushBack(&plist, 4);
//
//	SLTPrint(plist);
//}//void TestSList2()
//{
//	SLTNode* plist = NULL;
//	SLTPushFront(&plist, 1);
//	SLTPushFront(&plist, 2);
//	SLTPushFront(&plist, 3);
//	SLTPushFront(&plist, 4);
//	SLTPrint(plist);
//
//	SLTPopBack(&plist);
//	SLTPrint(plist);
//
//	SLTPopBack(&plist);
//	SLTPrint(plist);
//
//	SLTPopBack(&plist);
//	SLTPrint(plist);
//}//void TestSList3()
//{
//	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);
//}//void TestSList4()
//{
//	SLTNode* plist = NULL;
//	SLTPushBack(&plist, 1);
//	SLTPushBack(&plist, 2);
//	SLTPushBack(&plist, 3);
//	SLTPushBack(&plist, 4);
//
//	SLTPrint(plist);
//
//	SLTNode* ret = SLTFind(plist, 2);
//	ret->data *= 2;
//	SLTPrint(plist);
//
//	SLTInsert(&plist, ret, 20);
//	SLTPrint(plist);
//}void TestSList5()
{SLTNode* plist = NULL;SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPrint(plist);SLTNode* ret = SLTFind(plist, 2);SLTPrint(plist);SLTErase(&plist, ret);ret = NULL;SLTPrint(plist);
}int main()
{/*TestSList1();*//*TestSList2();*//*TestSList3();*///TestSList4();TestSList5();return 0;
}

 SList.c文件

#include "SLish.h"void SLTPrint(SLTNode* phead)
{SLTNode* cur = phead;while (cur != NULL)//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 = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;*/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)
{SLTNode* newnode = BuySLTNode(x);newnode->next = *pphead;*pphead = newnode;
}void SLTPopBack(SLTNode** pphead)
{assert(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;}//while(tail->next->next!=NULL)//{//	tail = tail->next;//}//free(tail->next);//tail->next = NULL;free(tail);tail = NULL;prev->next = NULL;}
}void SLTPopFront(SLTNode** pphead)
{assert(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)
{assert(pos);assert(pphead);if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}SLTNode* newnode = BuySLTNode(x);prev->next = newnode;newnode->next = pos;}
}//pos位置删除
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pos);assert(pphead);assert(*pphead);if (*pphead == pos){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)
{assert(pos);SLTNode* newnode = BuySLTNode(x);newnode->next = pos->next;pos->next = newnode;
}void SLTEraseAfter(SLTNode* pos)
{assert(pos);assert(pos->next);//SLTNode* del = pos->next;//pos->next = pos->next->next;SLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

 结尾:

这篇博客的结束也意味着我们在链表方面的知识学习结束了,但是进入了数据结构的学习中,每次学习不再是一次就能吃透,代码的难度都得到了提升,在这种情况下我们更应该反复学习巩固知识。随着难度的提高,我的表达能力一定程度被限制了,这可能会使博客质量会下滑,这一点望理解,最后还是希望这篇对在学习链表的人有所帮助。 


http://www.ppmy.cn/news/30242.html

相关文章

【数据挖掘与商务智能决策】第一章 数据分析与三重工具

numpy基础 numpy与数组 import numpy as np # 用np代替numpy,让代码更简洁 a [1, 2, 3, 4] # 创建列表a b np.array([1, 2, 3, 4]) #从列表ach print(a) print(b) print(type(a)) #打印a类型 print(type(b)) #打印b类型[1, 2, 3, 4] [1 2 3 4] <class ‘list’>…

pv和pvc

一、PV和PVC详解当前&#xff0c;存储的方式和种类有很多&#xff0c;并且各种存储的参数也需要非常专业的技术人员才能够了解。在Kubernetes集群中&#xff0c;放了方便我们的使用和管理&#xff0c;Kubernetes提出了PV和PVC的概念&#xff0c;这样Kubernetes集群的管理人员就…

【代码随想录训练营】【Day32】第八章|贪心算法|122.买卖股票的最佳时机II |55. 跳跃游戏|45.跳跃游戏II

买卖股票的最佳时机II 题目详细&#xff1a;LeetCode.122 买卖股票的最佳时机&#xff0c;怎么都能够想出来个思路&#xff0c;假如我们每天都能预知明天的股票是涨是降&#xff0c;那么贪心策略就是在涨之前买股票&#xff0c;在降的前一天卖掉&#xff0c;这就是买卖股票的…

百度Apollo规划算法——轨迹拼接

百度Apollo规划算法——轨迹拼接引言轨迹拼接1、什么是轨迹拼接&#xff1f;2、为什么要进行轨迹拼接&#xff1f;3、结合Apollo代码为例理解轨迹拼接的细节。参考引言 在apollo的规划算法中&#xff0c;在每一帧规划开始时会调用一个轨迹拼接函数&#xff0c;返回一段拼接轨迹…

四轮两驱小车(五):蓝牙HC-08通信

前言&#xff1a; 在我没接触蓝牙之前&#xff0c;我觉得蓝牙模块应用起来应该挺麻烦&#xff0c;后来发觉这个蓝牙模块的应用本质无非就是一个串口 蓝牙模块&#xff1a; 这是我从某宝上买到的蓝牙模块HC-08&#xff0c;价格还算可以&#xff0c;而且可以适用于大多数蓝牙调试…

webgpu思考obj携带属性

今天在搞dbbh.js的时候&#xff0c;想到一个问题&#xff0c;啥问题呢&#xff0c;先看看情况 画2个材质不相同的box的时候 首先开始createCommandEncoder,然后beginRenderPass&#xff0c;分歧就在这里了 第一个box,他有自己的pipeline&#xff0c;第二个也有&#xff0c;那么…

CSS 选择器以及CSS常用属性

目录 &#x1f407;今日良言:可以不光芒万丈,但不要停止发光 &#x1f42f;一、写CSS的三种方法 &#x1f42f;二、CSS选择器的常见用法 &#x1f42f;三、CSS常用属性 &#x1f407;今日良言:可以不光芒万丈,但不要停止发光 &#x1f42f;一、写CSS的三种方法 CSS的基本语…

Go项目(商品微服务-2)

文章目录简介handler商品分类轮播图品牌和品牌与分类oss前端直传库存服务数据不一致redis 分布式锁小结简介 开发商品微服务 API 层类似的&#xff0c;将 user-web 目拷贝一份&#xff0c;全局替换掉 user-web修改 config 去掉不用的配置更新本地和远程 nacos 配置文件 把 pro…