既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
目录
前言
今天我们的主题是实现单向不带头不循环的单链表
链表的实现
接口的声明
typedef int SLTDataTypde;
typedef struct SLTNode
{
SLTDataTypde data;
struct SLTNode \*next;
}SLTNode;
//链表销毁
void SLTNodeDestor(SLTNode \*ps);
//创建结点
SLTNode \*BuySLTNode(int x);
//打印
void SLTprint(SLTNode \*ps);
//尾插
void SLTPushBack(SLTNode \*\*ps, int x);
//头插
void SLTPushFront(SLTNode \*\*ps,int x);
//头删
void SLTPopFront(SLTNode \*\*ps);
//尾删
void SLTPopBack(SLTNode \*\*ps);
//查找
SLTNode\* SLTFind(SLTNode \*ps, int x);
//指定位置后插入
void SLTInsertAfter(SLTNode \*pos, int x);
//指定位置前插入
void SLTInsertBefor(SLTNode \*\*ps, SLTNode \*pos, int x);
//移除指定位置之后
void SLTEraseAfter(SLTNode \*pos);
//移除指定位置之前
void SLTEraseBefor(SLTNode \*\*ps ,SLTNode \*pos);
接口的实现
#include"SLTNode.h"
//创建结点
SLTNode \*BuySLTNode(int x)
{
SLTNode \*tmp = (SLTNode \*)malloc(sizeof(SLTNode));
if (!tmp)
{
perror("BuySLTNode::malloc");
exit(1);
}
tmp->data = x;
tmp->next = NULL;
return tmp;
}
//打印
void SLTprint(SLTNode \*ps)
{
SLTNode \*cur = ps;
while (cur != NULL)
{
printf("%d->",cur->data);
cur = cur->next;
}
printf("NULL\n");
}
//链表销毁
void SLTNodeDestor(SLTNode \*ps)
{
assert(ps);
while (ps)
{
SLTNode \*del = ps;
ps = ps->next;
free(ps);
}
}
//尾插
void SLTPushBack(SLTNode \*\*ps, int x)
{
SLTNode \*newNode = BuySLTNode(x);
if (\*ps == NULL)
{
\*ps = newNode;
}
else
{
SLTNode \*tail = \*ps;
//找尾
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newNode;
}
}
//头插
void SLTPushFront(SLTNode \*\*ps,int x)
{
SLTNode \*newNode = BuySLTNode(x);
newNode->next = \*ps;
\*ps = newNode;
}
//头删
void SLTPopFront(SLTNode \*\*ps)
{
if (\*ps == NULL)
return;
SLTNode \*delNode = \*ps;
\*ps = delNode->next;
free(delNode);
delNode = NULL;
}
//尾删
void SLTPopBack(SLTNode \*\*ps)
{
SLTNode \*phead = \*ps;
//只有一个结点
if (phead->next == NULL)
{
free(phead);
\*ps = NULL;
phead = NULL;
}
else if (\*ps == NULL)
{
return ;
}
else
{
SLTNode \*cur = \*ps;
SLTNode \*prev = NULL;
//剩多个结点
while (cur->next != NULL)
{
prev = cur;
cur = cur->next;
}
free(cur);
cur = NULL;
prev->next = NULL;
}
}
//查找
SLTNode\* SLTFind(SLTNode \*ps, int x)
{
if (ps == NULL)
return NULL;
SLTNode \*cur = ps;
while (cur != NULL)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
//指定位置后插入
void SLTInsertAfter(SLTNode \*pos, int x)
{
assert(pos);
SLTNode \*next = pos->next;
SLTNode \*newNode = BuySLTNode(x);
pos->next = newNode;
newNode->next = next;
}
//指定位置前插入
void SLTInsertBefor(SLTNode \*\*ps, SLTNode \*pos, int x)
{
assert(pos);
SLTNode \*newNode = BuySLTNode(x);
if (pos == \*ps)
{
newNode->next = \*ps;
\*ps = newNode;
}
else
{
SLTNode \*cur = \*ps;
SLTNode \*prev = NULL;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = newNode;
newNode->next = cur;
}
}
//移除指定位置之后
void SLTEraseAfter(SLTNode \*pos)
{
assert(pos);
SLTNode \*next = pos->next;
pos->next = next->next;
free(next);
next = NULL;
}
//移除指定位置
void SLTEraseBefor(SLTNode \*\* ps, SLTNode \*pos)
{
assert(pos);
SLTNode \*next = pos->next;
if (\*ps == pos)
{
free(\*ps);
\*ps = NULL;
\*ps = next;
}
else
{
SLTNode \*prev = NULL;
SLTNode \*cur = \*ps;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = cur->next;
free(cur);
cur = NULL;
}
}
销毁
//链表销毁
void SLTNodeDestor(SLTNode \*ps)
{
assert(ps);
while (ps)
{
SLTNode \*del = ps;
ps = ps->next;
free(ps);
}
}
打印
遍历链表的每个结点,打印这个结点的data
//打印
void SLTprint(SLTNode \*ps)
{
SLTNode \*cur = ps;
while (cur != NULL)
{
printf("%d->",cur->data);
cur = cur->next;
}
printf("NULL\n");
}
创建结点
//创建结点
SLTNode \*BuySLTNode(int x)
{
SLTNode \*tmp = (SLTNode \*)malloc(sizeof(SLTNode));
if (!tmp)
{
perror("BuySLTNode::malloc");
exit(1);
}
tmp->data = x;
tmp->next = NULL;
return tmp;
}
尾插
创建一个新的结点newNode,找到最后一个结点的位置tail,用tail链接newNode,这里要注意的是我们的链表的实现是以不带头为目的的,所以会是一个空表,如果是空表的情况下,需要将ps初始化成为第一个结点,往后尾插就只需要找尾了,这里要传二级指针,为了改变外部的指针
//尾插
void SLTPushBack(SLTNode \*\*ps,int x)
{
SLTNode \*newNode = BuySLTNode(x);
//如果是空表
if (\*ps == NULL)
{
\*ps = newNode;
}
else
{
//尾插找尾
SLTNode \*tail = \*ps;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newNode;
}
}
传递一级指针无法改变main函数的plist的指针,因为是在不同的栈帧中开辟的,只是一份值拷贝,互不影响,所以传递二级指针
头插
头插只需要创建一个新的结点newNode,让新的结点链接ps,再让ps去做新的头,由于需要改变外面的指针,还是要传递二级指针,头插即使是面对链表为空还是不会受影响
//头插
void SLTPushFront(SLTNode \*\*ps,int x)
{
SLTNode \*newNode = BuySLTNode(x);
newNode->next = ps;
//ps去做新的头
\*ps = newNode;
}
头删
头删同样需要改变外面的指针,所以要传二级,移除第一个结点,让第二个结点做新的头
//头删
void SLTPopFront(SLTNode \*\*ps)
{
if (\*ps == NULL)
return;
SLTNode \*delNode = \*ps;
\*ps = delNode->next;
free(delNode);
delNode = NULL;
}
尾删
尾删需要考虑三种情况
1、表为空,不需要删除,直接返回
2、只剩一个结点,删除这个结点,再置空,也需要改变外面指针的指向,必须传二级指针
3、有多个结点,定义前驱指针prev,和目标指针cur,当cur指向最后一个结点的时候释放cur,cur置空,前驱指针prev指向空
//尾删
void SLTPopBack(SLTNode \*\*ps)
{
SLTNode \*phead = \*ps;
//只有一个结点
if (phead->next == NULL)
{
free(phead);
\*ps = NULL;
phead = NULL;
}
//表为空直接返回
else if (\*ps == NULL)
{
return ;
}
//剩多个结点
else
{
//定义前驱指针和目标指针
SLTNode \*tail= \*ps;
SLTNode \*prev = NULL;
while (tail->next != NULL)
{
prev = tail;
tail= tail->next;
}
//移除最后一个结点,让前一个指针指向空
free(tail);
cur = NULL;
prev->next = NULL;
}
}
查找
查找54
找到该结点就返回,没找到就返回空,空表直接返回
//查找
SLTNode\* SLTFind(SLTNode \*ps, int x)
{
if (ps == NULL)
return NULL;
SLTNode \*cur = ps;
while (cur != NULL)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
指定位置后插入
在pos后面插入一个新的结点,需要备份pos的下一个结点,先让newNode指向第二个结点再让pos指向newNode
//指定位置后插入
void SLTInsertAfter(SLTNode \*pos, int x)
{
assert(pos);
SLTNode \*next = pos->next;
SLTNode \*newNode = BuySLTNode(x);
pos->next = newNode;
newNode->next = next;
}
指定位置前插入
1、在指定pos的前面插入,如果pos和ps在同一个位置就是头插了
2、如果pos出现在链表的中间某个位置,要想在pos前插入一个新的结点就必须要找到pos前的一个结点prev,让这个结点prev指向新的结点newNode,最后让newNode指向当前结点
//指定位置前插入
void SLTInsertBefor(SLTNode \*\*ps, SLTNode \*pos, int x)
{
assert(pos);
SLTNode \*newNode = BuySLTNode(x);
if (pos == \*ps)
{
newNode->next = \*ps;
\*ps = newNode;
}
else
{
SLTNode \*cur = \*ps;
SLTNode \*prev = NULL;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = newNode;
newNode->next = cur;
}
}
指定位置后删除
next用来保存pos位置的下一个结点,pos指向next的下一个结点,释放next位置的结点
//移除指定位置之后
void SLTEraseAfter(SLTNode \*pos)
{
//表为空不做处理
if(pos == NULL)
{
return ;
}
assert(pos);
SLTNode \*next = pos->next;
pos->next = next->next;
free(next);
next = NULL;
}
移除指定位置
prev和cur一起一后,当cur与pos相遇了,就将prev链接cur的下一个结点,释放pos位置的结点,将指针置空
如果plist和pos指向一起,就是头删操作了
//移除指定位置
void SLTEraseBefor(SLTNode \*\* ps, SLTNode \*pos)
{
assert(pos);
SLTNode \*next = pos->next;
//pos等于ps,头删
if (\*ps == pos)
{
free(\*ps);
\*ps = NULL;
\*ps = next;
}
else
{
SLTNode \*prev = NULL;
SLTNode \*cur = \*ps;
while (cur != pos)
{
prev = cur;
cur = cur->next;
}
prev->next = cur->next;
free(cur);
cur = NULL;
}
}
力扣题
移除链表元素:
点我.
考虑两种场景
- 1、如果表的第一个结点的值就是val,那么就是头删了,释放第一个结点,需要将head的指向改变,指向下一个结点
- 2、如果表的所有结点全是val的情况,head的指向就可以一直改变,直到指向NULL,最后返回去的就是NULL
链表移除结点的变形题,把特殊场景处理就能搞定
struct ListNode\* removeElements(struct ListNode\* head, int val){
struct ListNode \*prev = NULL;
struct ListNode \*cur = head;
while(cur != NULL)
{
//值为val
struct ListNode \*del = NULL;
if(cur->val != val)
{
prev = cur;
cur = cur->next;
}
//值不为val
else
{
//如果cur是头的情况
struct ListNode \*next = cur->next;
if(prev == NULL)
{
free(cur);
head = next;
cur = next;
}
//cur不是头
else
{
prev->next = next;
free(cur);
cur = next;
}
}
}
return head;
}
第二种写法:带哨兵位的头节点,可以让prev指向哨兵位结点,这样就不需要考虑prev是否会存在空指针的问题了,同样的prev和cur一起一后,找到了值位val的结点,就释放该节点,cur继续指向下一个位置,最后只需要返回哨兵位结点的下一个位置
struct ListNode\* removeElements(struct ListNode\* head, int val){
struct ListNode\* guard = (struct ListNode\*)malloc(sizeof(struct ListNode));
guard->next = head;
struct ListNode \*prev = guard;
struct ListNode \*cur = head;
while(cur != NULL)
{
if(cur->val != val)
{
prev = cur;
cur = cur->next;
}
else
{
struct ListNode \*next = cur->next;
prev->next = next;
free(cur);
cur = next;
}
}
return guard->next;
}
206. 反转链表:
点我.
实现思路
使用三个指针迭代的方法,prev和cur用来逆置结点的指向,next保证cur能找到下一个结点的位置,直到cur指向空了,循环停止,返回prev指针,整个链表也就逆置了
struct ListNode\* reverseList(struct ListNode\* head){
if(head == NULL)
{
return NULL;
}
struct ListNode\* cur = head;
struct ListNode\* prev = NULL;
struct ListNode\* next = cur->next;
while(cur)
{
//逆置
cur->next = prev;
//迭代
prev = cur;
cur = next;
if(next != NULL)
next = next->next;
}
return prev;
}
思路二:必做,链表头插的变型题,将原链表遍历一遍,取原链表的结点头插到新的表中
struct ListNode\* reverseList(struct ListNode\* head){
struct ListNode \*newhead = NULL;
struct ListNode \*cur = head;
while(cur)
{
struct ListNode \*next = cur->next;
//头插
cur->next = newhead;
newhead = cur;
//迭代
cur = next;
![img](https://img-blog.csdnimg.cn/img_convert/c7c603e9629372c7c76b57cdd96c0168.png)
![img](https://img-blog.csdnimg.cn/img_convert/6716e33df4ede00b7c5f7d4487aea707.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
while(cur)
{
//逆置
cur->next = prev;
//迭代
prev = cur;
cur = next;
if(next != NULL)
next = next->next;
}
return prev;
}
思路二:必做,链表头插的变型题,将原链表遍历一遍,取原链表的结点头插到新的表中
struct ListNode\* reverseList(struct ListNode\* head){
struct ListNode \*newhead = NULL;
struct ListNode \*cur = head;
while(cur)
{
struct ListNode \*next = cur->next;
//头插
cur->next = newhead;
newhead = cur;
//迭代
cur = next;
[外链图片转存中...(img-OSXIhdeY-1715877282487)]
[外链图片转存中...(img-EYtKNquI-1715877282487)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**