先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
正文
//双向链表打印
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 (备注大数据)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**