C C++数据结构(二) —,2024年最新阿里P7大牛亲自讲解

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

void SListPushBack(SLTNode** pphead, SLTDateType x) {
assert(pphead); // pphead不可以为空指针.
// 先增加一个节点
SLTNode* newnode = BuyListNode(x);

// 如果头节点是空,那么直接把newnode的地址赋值给头节点
if (\*pphead == NULL) {
	// \*pphead:对pphead解引用,其实访问的就是plist;
	\*pphead = newnode;
}
else {
	// 找到尾节点
	SLTNode\* tail = \*pphead;
	while (tail->next != NULL) {
		tail = tail->next;
	}
	tail->next = newnode;
}

}


代码解释



> 
> `SLTNode* tail = *pphead`:首先定义一个指向 **头节点** 的指针 **tail**;  
>     
>  `tail->next != NULL`:如果 **tail** 节点的 **next** 不为 **空指针** ,说明此时还没有找到尾节点,因为尾节点的 **next** 是为 **空指针** 的,则进入循环;  
>     
>  `tail = tail->next`:因为当前节点的 **next** 存放的是下一个节点的地址,那么我们就是把下一个节点的地址赋给 **tail**,换句话说,也就是让 **tail** 指向第二个节点;  
>     
>  以此类推,直到 **tail->next** 为 **空指针**,说明 **tail** 已经找到了 **尾节点**,即跳出 **while** 循环;  
>     
>  `tail->next = newnode`:然后把 **newnode** 节点的地址赋给 **tail->next**;
> 
> 
> 


### 🍑 头插


还是和尾插一样,需要改变 **phead** 的值,所以我们的形参需要用二级指针接收 **phead** 的地址。


头部插⼊,可以分成两个步骤。


第 **1** 步,把新节点 **newnode** 的 **next** 指针指向原先的头节点。


第 **2** 步,把新节点变为链表的头节点,如下图所示👇  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/cfef7bbe9da245bcb9b12defeae4394a.png)


**动图演示👇**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/85b128f21f8a445c9e1c01c176aac61f.gif#pic_center)



> 
> 注意:**头插** 就不用区分链表是否为空的
> 
> 
> 


📝 代码示例



void SListPushFront(SLTNode** pphead, SLTDateType x) {
assert(pphead);
SLTNode* newnode = BuyListNode(x); // 1、创建新节点
newnode->next = *pphead; // 2、让新节点的next指向原来的头节点
*pphead = newnode; // 3、phead头指针 指向新节点
}


代码解释



> 
> `SLTNode* newnode = BuyListNode(x)`:先增加一个新节点;  
>     
>  `newnode->next = *pphead`:对 **pphead** 解引用就是访问 **phead**,**phead** 为原来 **头指针** 指向的 **头节点**,那么把这个 **地址** 赋给新节点的 **next** 指针;  
>     
>  `*pphead = newnode`:最后再把 **头指针** 指向的 **头节点** 更新为 **新节点**,此时 **新节点** 就是链表的第一个节点啦!
> 
> 
> 


## 3. 删除数据


链表的删除操作,同样也分为 **4** 种情况。



> 
> (1)在单链表 **头部** 删除数据;  
>     
>  (2)在单链表 **尾部** 删除数据;  
>     
>  (3)在单链表指定 **pos** 位置删除数据 **x**;  
>     
>  (4)在单链表指定 **pos** 位置的 **下一个** 位置删除数据 **x**;
> 
> 
> 


### 🍑 尾删


尾部删除,是最简单的情况,但是我们要考虑几种特殊情况:



> 
> (1)如果链表为空,肯定不能删除  
>     
>  (2)如果链表只有一个节点,那么直接删除  
>     
>  (3)如果链表有两个以及两个以上的节点;
> 
> 
> 


对于第 **3** 种情况,如果有两个以上的节点,那么只需把倒数第 **2** 个节点的 **next** 指针指向空即可。如下图所示👇  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/8f5314805c6e4b36804073786028645c.png)  
 动图演示👇  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/1d7b0c26908e4d759344196952bc8a06.gif#pic_center)


📝 代码示例



void SListPopBack(SLTNode** pphead) {
// 判断链表为空的情况(粗暴的方式)
assert(*pphead != NULL);

// 1.如果只有一个节点的情况
if ((\*pphead)->next == NULL) {
	free(\*pphead); // 释放头节点
	\*pphead = NULL;
}
else // 2.如果有两个以及两个以上的节点
{ 
	SLTNode\* prev = NULL;
	SLTNode\* tail = \*pphead;
	while (tail->next != NULL) {
		prev = tail; // 找到tial前面的那个节点
		tail = tail->next; // tial指向它的下一个节点
	}
	free(tail); // 释放最后一个节点的空间
	tail = NULL; // 把空间置为空指针
	prev->next = NULL; // 把tial上一个节点的next指向的元素置为NULL
}

}


代码解释



> 
> 我们直接看 **else** 语句的代码:  
>     
>  `SLTNode* prev = NULL`:首先定义一个指针变量 **prev**,用来指向倒数第 **2** 个节点;  
>     
>  `SLTNode* tail = *pphead`:然后再定义一个指针变量 **tail**,并把 **头节点** 的地址赋给它,让它用来找到 **尾节点**;  
>     
>  **while** 循环部分:当 **tail->next** 不等于 **空指针** 时,说明 **尾节点** 还没有找到,那么就把当前 **tail** 的地址赋给 **prev**,然后 **tail** 指向下一个节点去,以此循环,直到 **tail->next** 等于 **空指针**,然后跳出循环;  
>     
>  最后释放掉 **tail** 的空间,然后把 **prev->next** 置为 **空指针**;
> 
> 
> 


### 🍑 头删


尾部删除,也要考虑几种特殊情况:



> 
> (1)当链表为空时候,没有可以删除的元素,那么就终止程序;  
>     
>  (2)如果链表只有一个节点,那么直接删除;  
>     
>  (3)如果链表有两个以及两个以上的节点。
> 
> 
> 


对于第 **3** 种情况,如果有两个以上的节点,直接把链表的头节点设为原先头节点的 **next** 指针即可。如下图所示👇  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/cbfba3ab87744ebe8763da0df0bac37d.png)


动图演示👇  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/8c670664095a413db0113e2c55d46ba3.gif#pic_center)


📝 代码示例



void SListPopFront(SLTNode** pphead) {
assert(*pphead != NULL); // 判断链表为空的情况(粗暴的方式)
SLTNode* temp = (*pphead)->next;
free(*pphead);
*pphead = temp;
}


代码解释



> 
> 定义一个指针变量 **temp**,把头节点的 **next** 指针指向的地址赋给它,此时 **temp** 就是指向第 **2** 个元素的;  
>     
>  然后释放掉 **头节点** 的空间,再把 **temp** 的内容交给 **头指针**;
> 
> 
> 


## 4. 单链表查找



> 
> 在查找元素时,链表不像 **顺序表** 那样可以通过下标快速进⾏定位,只能从头节点开始向后⼀个⼀个节点逐⼀查找。
> 
> 
> 


例如给出⼀个链表,需要查找从头节点开始的第 **3** 个节点(如下图所示👇)。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/6b0124b79f5147c3994efd9817e886b3.png)


第 **1** 步,将查找的指针定位到头节点(如下图所示👇)。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/b1acf703d232462a9969c57c09ac773c.png)


第 **2** 步,根据头节点的 **next** 指针,定位到第 **2** 个节点(如下图所示👇)。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/5782e809ad7f4f519f0a92bb74fecb79.png)


第 **3** 步,根据第 **2** 个节点的 **next** 指针,定位到第 **3** 个节点,查找完毕(如下图所示👇)。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/62423fbb95144b948a6a6f73e692f7a5.png)


**动图演示👇**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/866ee91d135a4a4a8e04981f5dce1b71.gif#pic_center)


📝 代码示例



SLTNode* SListFind(SLTNode* phead, SLTDateType x) {
SLTNode* cur = phead;
while (cur) {
if (cur->data == x) {
return cur;
}
else {
cur = cur->next;
}
}
return NULL; // 没找到
}



> 
> 单链表中的数据只能按顺序进⾏访问,最坏的时间复杂度是  
>  
>  
>  
>  
>  O 
>  
>  
>  ( 
>  
>  
>  n 
>  
>  
>  ) 
>  
>  
>  
>  O(n) 
>  
>  
>  O(n) 。
> 
> 
> 


## 5. 单链表指定位置操作数据


在插入数据和删除数据里面,分别还有两种方法没有讲到,那就是:



> 
> (1)指定 **pos** 位置插入和删除;  
>     
>  (2)指定 **pos** 位置的后面插入和删除;
> 
> 
> 


那么为什么到现在才讲呢?那是因为 **单链表** 不同于 **顺序表**,这里的 **pos** 位置,是通过上面的 **查找** 得来的!


### 🍑 在 pos 位置的前面插入数据


在单链表指定 **pos** 位置插入数据 **x**,实际上数据 **x** 会被插入到 **pos** 的 **前面** 位置插入数据,同样也要考虑几种特殊情况:



> 
> (1)如果链表为空,或者只有一个节点,那么就和头插一样,直接插入;  
>     
>  (2)如果链表有两个以上的节点;
> 
> 
> 


对于第 **2** 种情况,如果链表有两个以上的节点,那么可以分为两个步骤。


第 **1** 步,新节点的 **next** 指针,指向 **插⼊位置的节点**。


第 **2** 步,**插⼊位置前置节点** 的 **next** 指针,指向新节点(如下图所示👇)。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/98051768d3474af7a2e62afa1bfbfb39.png)


**动图演示👇**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/811ef838b20f4741a19e6118bcdd8f48.gif#pic_center)



> 
> 注意:首先需要调用 **查找函数** 找到 **pos** 位置的地址,然后才能进行插入;  
>     
>  另外只要内存空间允许,能够插⼊链表的元素是⽆穷⽆尽的,不需要像 **顺序表** 那样考虑扩容的问题。
> 
> 
> 


📝 代码示例



// 在pos前面的位置插入(pos是有find查找来的)
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x) {
assert(pphead);
assert(pos);
// 1. pos是第一个节点,那么就是头插
if (*pphead == pos) {
SListPushFront(pphead, x);
}
else { // 2. pos不是第一个节点
// 找到pos的前一个位置
SLTNode* posPrev = *pphead;
while (posPrev->next != pos) {
posPrev = posPrev->next;
}
SLTNode* newnode = BuyListNode(x);
posPrev->next = newnode;
newnode->next = pos;
}
}


### 🍑 删除在 pos 位置的数据


直接删除 **pos** 位置的 **当前** 数据 **x**,同样也要考虑几种特殊情况:



> 
> (1)如果链表为空,或者只有一个节点,那么就和头删一样,直接删除;  
>     
>  (2)如果链表有两个以上的节点;  
>     
>  注意:这里就是删除 **pos** 位置指向的数据,不是删除前面的节点,也不是删除后面的节点
> 
> 
> 


对于第 **2** 种情况,如果链表有两个以上的节点,同样很简单,把要删除节点的前置节点的 **next** 指针,指向要删除元素的下⼀个节点即可(如下图所示👇)。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/a35eb79bd5044acd81956720ab968ca3.png)


**动图演示👇**  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/087a495acf6a44b9bea96ada3ab8f2b5.gif#pic_center)


📝 代码示例



void SListErase(SLTNode** pphead, SLTNode* pos) {
assert(pphead);
assert(pos);
// 如果pos是头节点,那么用头删除
if (*pphead == pos) {
SListPopFront(pphead);
}
else { // 如果pos在中间或者尾节点,那么就走else
SLTNode* posPrev = *pphead;
while (posPrev->next != pos) {
posPrev = posPrev->next;
}
posPrev->next = pos->next;
free(pos);
pos = NULL;
}
}


## 6. 单链表指定位置的后面操作数据


在单链表指定的 **pos** 位置的后面 **插入数据** 和 **删除数据**;


### 🍑 在 pos 位置的后面插入数据


这里与在 **pos** 位置的前面插入数据不同的是,这次插入数据,是直接插到 **pos** 的后面位置的,这里不需要考虑什么特殊情况;


第 **1** 步,定义一个指针变量 **posNext**,用于存放 **pos** 位置的 **next** 指针地址


第 **2** 步,把新节点 **newnode** 的地址赋给 **pos** 位置的 **next** 指针,然后把 **posNext** 的地址赋给新节点 **newnode** 的指针 **next**(如下图所示👇)。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/3c2e3aa71fd44455af42733d3e2f0a28.png)


📝 代码示例



void SListInsertAfter(SLTNode* pos, SLTDateType x) {
assert(pos);
SLTNode* posNext = pos->next;
SLTNode* newnode = BuyListNode(x);
pos->next = newnode;
newnode->next = posNext;
}


### 🍑 删除在 pos 位置后面的数据


这个也很简单,只需要考虑 **pos** 是否存在,如果 **pos** 为空,肯定不能删除;


如果 **pos** 不为空,正常输出就好如下图所示👇  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/db451d11404e48dc9f4dbbb15dd824c4.png)


📝 代码示例



void SListEraseAfter(SLTNode* pos) {
assert(pos);
SLTNode* posNext = pos->next;
if (posNext != NULL) {
pos->next = posNext->next;
free(posNext);
posNext = NULL;
}
}


## 7. 单链表打印


单链表的打印很简单,直接用 **循环** 依次打印 **单链表** 内的元素个数就好了。


📝 代码示例



void SListPrint(SLTNode* phead) {
SLTNode* cur = phead;
while (cur != NULL) {
printf(“%d->”, cur->data);
cur = cur->next;
}
printf(“NULL\n”);
}


## 8. 单链表销毁


和 **顺序表** 一样,都是使用动态内存开辟的空间,用完以后,需要释放


📝 代码示例



// 销毁链表
void SListDestroy(SLTNode** pphead) {
assert(pphead);
SLTNode* cur = *pphead;
while (cur != NULL) { // 当前的节点不等于空指针
SLTNode* curNext = cur->next; // 保存 当前节点的第一个地址
free(cur); // 把当前节点置为空指针
cur = curNext;
}
*pphead = NULL; // 最后释放头节点
}


## 9. 总结


**总结:**



> 
> 1、单链表结构,适合头插头删  
>     
>  2、尾部或者中间某个位置的插入和删除不适合  
>     
>  3、如果要使用链表单独存储数据,那么双向链表更适合
> 
> 
> 


**单链表学习的意义:**



> 


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
![img](https://img-blog.csdnimg.cn/img_convert/adea6adf8f39369f8bad6d456a20f6a9.png)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

pphead;
	while (cur != NULL) { // 当前的节点不等于空指针
		SLTNode\* curNext = cur->next; // 保存 当前节点的第一个地址
		free(cur); // 把当前节点置为空指针
		cur = curNext; 
	}
	\*pphead = NULL; // 最后释放头节点
}

9. 总结

总结:

1、单链表结构,适合头插头删
 
2、尾部或者中间某个位置的插入和删除不适合
 
3、如果要使用链表单独存储数据,那么双向链表更适合

单链表学习的意义:

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-0RhWNBQF-1713275047653)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值