C C++数据结构(三) —,一线互联网大厂面试真题系统收录

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

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

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

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

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

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

正文

//双向链表打印
void ListPrint(LTNode* phead) {
assert(phead);

LTNode\* cur = phead->next; // 从头结点的后一个结点开始打印
while (cur != phead) { // 当cur指针指向头结点时,说明链表全部打印完成
	printf("%d ", cur->data);
	cur = cur->next;
}
printf("\n");

}



> 
> 注意:第一个是 **头结点**,也被称为 **带哨兵位** 的结点,它是用来 **站岗** 的,所以不需要打印。
> 
> 
> 


## 3. 查找元素


给定一个值,在链表中寻找与该值相同的结点,若找到了,则返回结点地址;


若没有找到,则返回空指针(**NULL**)。


📝 代码示例



//双向链表查找
LTNode* ListFind(LTNode* phead, LTDataType x) {
assert(phead);

LTNode\* cur = phead->next; // 从头结点的后一个结点开始查找
while (cur != phead) { // 从头结点的后一个结点开始查找
	if (cur->data == x) {
		return cur; // 返回目标结点的地址
	}
	cur = cur->next;
}
return NULL; // 没有找到目标结点

}


## 4. 插入结点


双向链表的插入操作分为 **3** 种情况:



> 
> (1)头插  
>  (2)尾插  
>  (3)在指定 **pos** 位置之前插入
> 
> 
> 


### 🍑 头插


进行头插时,需要申请一个新的结点,将 **新结点** 插入到 **头结点** 与 **头结点的后一个结点** 之间即可。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/a7e0defcd3de4fc79a0b84a644e7d438.png)


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


📝 代码示例



//双向链表头插
void ListPushFront(LTNode* phead, LTDataType x)
{
assert(phead);

LTNode\* newnode = BuyLTNode(x); // 申请一个结点,数据域赋值为 x
LTNode\* after = phead->next; // 记录头结点的后一个结点位置

/\*建立新结点与头结点之间的双向关系\*/
phead->next = newnode;
newnode->prev = phead;

//建立新结点与beHind结点之间的双向关系
newnode->next = after;
after->prev = newnode;

}



> 
> 注意:第一个是 **哨兵结点**,所以不能在它的前面插入。
> 
> 
> 


### 🍑 尾插


尾插也是一样,申请一个新结点,将新结点插入到头结点和头结点的前一个结点之间即可。


因为链表是循环的,头结点的前驱指针直接指向最后一个结点,所以我们不必遍历链表找尾。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/3e2e4d9d7b8e4de5812a44baaa8b26d4.png)


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


📝 代码示例



//双向链表尾插
void ListPushBack(LTNode* phead, LTDataType x) {
assert(phead);

LTNode\* tail = phead->prev; //尾节点就是头节点的前驱指针
LTNode\* newnode = BuyLTNode(x); // 申请一个结点,数据域赋值为x

/\*建立新结点与头结点之间的双向关系\*/
newnode->next = phead;
phead->prev = newnode;

//建立新结点与tail结点之间的双向关系
tail->next = newnode;
newnode->prev = tail;

}


### 🍑 指定位置插入


这里的指定插入,是在指定的 **pos** 位置的 **前面** 插入结点。


这里 **pos** 是指定位置的 **地址**,那么如何得到这个地址呢?很简单,需要用到上面的 **查找函数**。


然后,我们只需申请一个新结点插入到 **指定位置结点** 和其 **前一个结点** 之间即可。


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


📝 代码示例



//双向链表在pos的前面进行插入
void ListInsert(LTNode* pos, LTDataType x) {
assert(pos);

LTNode\* newnode = BuyLTNode(x); // 申请一个结点,数据域赋值为x
LTNode\* posPrev = pos->prev; // 记录 pos 指向结点的前一个结点

/\*建立新结点与posPrev结点之间的双向关系\*/
posPrev->next = newnode;
newnode->prev = posPrev;


/\*建立新结点与pos指向结点之间的双向关系\*/
newnode->next = pos;
pos->prev = newnode;

}


### 🍑 插入升级


我们仔细回顾一下,刚刚写的 **头插** 和 **尾插**,有没有发现什么规律呢?


没错,**头插** 其实就是在 **哨兵结点** 的 **下一个结点** 插入数据,所以我们可以用上面写的 **ListInsert** 函数来实现。


📝 代码升级



//头插升级
void ListPushFront(LTNode* phead, LTDataType x) {
assert(phead);
ListInsert(phead->next, x);
}


那么 **尾插** 呢?其实就是在 **哨兵结点** 的 **前面** 插入结点。


📝 代码升级



//尾删升级
void ListPopBack(LTNode* phead) {
assert(phead);
ListErase(phead->prev);
}


## 5. 删除结点


双向链表的删除操作分为 **3** 种情况:



> 
> (1)头删  
>  (2)尾删  
>  (3)在指定 **pos** 位置删除
> 
> 
> 


### 🍑 头删


头删,即释 **哨兵结点** 的后一个结点,并建立 **哨兵结点** 与 **被删除结点的后一个结点** 之间的双向关系即可。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/1f4c70359c6846a2b8ac6398c09c269e.png)


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


📝 代码示例



//双链表头删
void ListPopFront(LTNode* phead)
{
assert(phead);
assert(phead->next != phead);

LTNode\* after = phead->next; // 记录头结点的后一个结点
LTNode\* newAfter = after->next; // 记录after结点的后一个结点

/\*建立头结点与newAfter结点之间的双向关系\*/
phead->next = newAfter;
newAfter->prev = phead;

free(after); // 释放after结点

}


### 🍑 尾删


尾删,即释放最后一个结点,并建立 **哨兵结点** 和 **被删除结点的前一个结点** 之间的双向关系即可。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/8138e17da3494c67a4343721f5350f5d.png)


📝 代码示例



//双链表尾删
void ListPopBack(LTNode* phead) {
assert(phead);
assert(phead->next != phead); //检查链表是否为空

LTNode\* tail = phead->prev; //记录头结点的前一个结点
LTNode\* newTail = tail->prev; // 记录tail结点的前一个结点

free(tail); // 释放tail结点
tail = NULL;

/\*建立头结点与newTail结点之间的双向关系\*/
newTail->next = phead;
phead->prev = newTail;

}


### 🍑 指定位置删除


删除指定 **pos** 位置的结点,这里不是删除 **pos** 前面的结点,也不是删除 **pos** 后面的结点,而是删除 **pos** 地址的结点。


同样,释放掉 **pos** 位置的结点后,建立该结点前一个结点和后一个结点之间的双向关系即可。


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


📝 代码示例



//双向链表删除pos位置的节点
void ListErase(LTNode* pos) {
assert(pos); //pos不为空

LTNode\* posPrev = pos->prev; // 记录pos指向结点的前一个结点
LTNode\* posNext = pos->next; // 记录pos指向结点的后一个结点

free(pos); //free是把指针指向的节点还给操作系统
pos = NULL;

/\*建立posPrev结点与posNext结点之间的双向关系\*/
posPrev->next = posNext;
posNext->prev = posPrev;

}


### 🍑 删除升级


同样,对于 **头删** 和 **尾删** 还是可以进行简化。


**头删** 实际上就是删除 **哨兵结点** 的 **下一个结点**。


📝 代码升级



//头删升级
void ListPopFront(LTNode* phead) {
assert(phead);
assert(phead->next != NULL); //只有一个节点的时候,就别删了

ListErase(phead->next);

}


**尾删** 实际上就是删除 **哨兵结点** 的 **前一个结点**。


📝 代码升级



//尾删升级
void ListPopBack(LTNode* phead) {
assert(phead);
ListErase(phead->prev);
}


## 6. 链表判空


当链表的 **头结点的前驱** 或是 **后驱** 指向的是自己时,即 双向链表为空。


换句话说,当只有 **哨兵结点** 时,链表为空。


📝 代码示例



//双向链表判空
bool ListEmpty(LTNode* phead)
{
assert(phead);
return phead->next == phead; // 当链表中只有头结点时为空
}


## 7. 获取链表中的元素个数


获取链表中的元素个数,即遍历一次链表,统计结点的个数(**哨兵结点** 不纳入总数)并返回即可。


📝 代码示例



//获取链表中的元素个数
int ListSize(LTNode* phead)
{
assert(phead);

int count = 0; // 记录元素个数
LTNode\* cur = phead->next; // 从头结点的后一个结点开始遍历
while (cur != phead) // 当cur指向头结点时,遍历完毕,头结点不计入总元素个数
{
	count++;
	cur = cur->next;
}
return count; //返回元素个数

}


## 8. 销毁链表


当链表使用完以后,要进行销毁。


销毁链表,从 **哨兵结点** 的 **后一个结点** 处开始向后遍历并释放结点,直到遍历到 **哨兵结点** 时,停止遍历并将 **哨兵结点** 也释放掉。


📝 代码示例



//销毁双链表
void ListDestory(LTNode* phead) {
assert(phead);
LTNode* cur = phead->next; // 从头结点后一个结点开始释放空间

while (cur != phead) {
	LTNode\* next = cur->next; // 释放之前先保存cur的后一个结点位置

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

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
img

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

也释放掉。

📝 代码示例

//销毁双链表
void ListDestory(LTNode\* phead) {
	assert(phead);
	LTNode\* cur = phead->next; // 从头结点后一个结点开始释放空间

	while (cur != phead) {
		LTNode\* next = cur->next; // 释放之前先保存cur的后一个结点位置


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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值