前言: 刷题和面试兼顾还得看你啊-牛客网
近几年互联网受疫情影响,许多互联网都使用牛客网在线笔试招人
很多同学因为不熟悉牛客网的环境和使用,最后在线笔试面试中屡屡受挫
牛客网提供了语言巩固,算法提高等在线OJ题,更有面试真题,大厂内推!
链接附上点击链接注册牛客网
牛客网这么好用,但是下面几个关于牛客网的知识你了解过吗?
目录
一.基本介绍:
二.单链表基本操作
每一个不曾起舞的日子,都是对生命的辜负!
--------尼采
目录:
链表的基本介绍
二:单链表的基本操作
单链表只要有人和你讲过左值和右值问题,单链表so easy
1.链表的每一个结点都包含两个域:数据域和指针域
2.其实计算机中并没有指针的指向,箭头指向只是人为假象出来的,实际上指针域存储的是下一个结点的地址。
3.链表的种类:链表的种类从三对修饰词中每对修饰词中选出一个,例如我们今天讲的就是不带头单向不循环链表
4.左值和右值问题
拿这个曾经卡死你的那个插入结点的代码为例来讲解:
备注:这里要从没有标记的一端开始修改,也就是先执行s->next=p->next,
后执行p->next=s ,因为如果先执行p->next=s;就会找不到p原来后面的那个结点了,这和顺序表的从哪一端开始移动很像!
5.单链表较动态顺序表:
动态顺序表:
优点:只用通过数组下标访问的方式就可以随机访问某一个数组元素并进行操作,时间复杂度低;
缺点:当要删除或插入元素的时候不得不移动大量元素,时间复杂度高;
单链表(single list 常简写为 SLT)
优点:当要删除或插入元素的时候不用移动大量元素,时间复杂度低;
缺点:当要访问某一个结点并进行操作的时候要从头开始遍历,时间复杂度高;
6.要想学会单链表你得了解这些(上面没有讲到的下面会一一讲到)
这里是正文接口分界线
void SListPrint(SLTNode* plist)
{
SLTNode* cur = plist;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
备注;在对每一个接口进行测试的时候,通过打印结果来观察测试接口的正确与否
每次插入前都要构造一个新结点,这一步模块化代码是不是和顺序表的插入前检查是否需要扩容很像!哈哈哈
SLTNode* BuySLTNode(SLTDataType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->data = x;
newnode->next = NULL;
return newnode;
}
注意:返回类型是SLTNode*类型
关于哨兵位的头结点:(先给结论吧)
总结:如果带头结点的单链表在进行操作时,就可以使得每一个带数据元素的结点都有前驱结点,就可以统一操作这些带 数据元素的结点,就可以不用考虑一些特殊情况。
但是如果是不带头结点的单链表在进行操作时,第一个结点(也就是第一个带数据元素的结点)没有前驱结点,所以操作起来要对第一个结点特殊考虑。
一:(不带头结点的话)还是借一借排队的例子和你讲一讲,你在食堂排队打饭,你老老实实排队(尾插),会遇到两种情况:(一个不太恰当的例子)
1.一般情况,原队伍有人,那你(待插入的结点)就要通过“指针”和这原队伍最后一个学生(最后一个结点)关联起来
2:特殊情况:原队伍没人,那你通过指针关联起来的就不再是学生,而是“食堂阿姨”,对待长辈肯定会有所拘谨,那么这就会产生细微的区别,要特殊考虑。
二:(带头结点的话)但是下一篇博客就会提到,当我们用一个学生去代替食堂阿姨打饭的位置,无论原队伍没人还是原队伍有人打饭交接的内容就可以统一(排队的同学才是相当于带头结点中那些带有数据元素的结点,需要我们进行单链表的打印等操作,所以不必再考虑头结点和头指针的关联问题)。
void SListPushBack(SLTNode** pplist, SLTDataType x)
{
SLTNode* newnode = BuySLTNode(x);
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
SLTNode* tail = *pplist;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SListPopBack(SLTNode** pplist)
{
if (*pplist == NULL)
{
return;
}
else if ((*pplist)->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
SLTNode* prev = NULL;
SLTNode* tail = *pplist;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
tail = NULL;
prev->next = NULL;
}
}
void SListPushFront(SLTNode** pplist, SLTDataType x)
{
SLTNode* newnode = BuySLTNode(x);
newnode->next = *pplist;
*pplist = newnode;
}
void SListPopFront(SLTNode** pplist)
{
if (*pplist == NULL)
{
return;
}
else
{
SLTNode* next = (*pplist)->next;
free(*pplist);
*pplist = next;
}
}
void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuySLTNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
8.在pos结点后删除一个结点
void SListEraseAfter(SLTNode* pos)
{
assert(pos);
if (pos->next == NULL)
{
return;
}
else
{
SLTNode* next = pos->next;
pos->next = next->next;
free(next);
}
}
SLTNode* SListFind(SLTNode* plist, SLTDataType x)
{
SLTNode* cur = plist;
while (cur)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
10.单链表的销毁
void SLTNodeDestory(SLTNode** pphead)
{
SLTNode* cur=*pphead;
while(cur)
{
SLTNode* next=cur->next;
free(cur);
cur=next;
}
}
有小伙伴看到这里就可能会问了,为什么单链表的接口这里没有单链表的初始化,那是因为,单链表的初始化和销毁也就是对头指针进行操作,那个一步就能完成,所以在主函数里面就可以完成,可以但没必要创建一个专门的接口去处理。
另外插一句,单链表的头很重要,
例一:如果要想清空一个结点,只用phead=NULL即可,只要头指针还在,队伍还可以重新拉起来,只要青山在,不怕没柴烧;但是要销毁单链表则需要将头指针free掉,这样这个单链表才真正的out了。
例二:只要找到了头指针,就可拉起整个单链表,遍历就可以找到每一个结点。(这就是为什么在一些操作时要始终秉承这不能修改phead这个原则的原因)
请写用无头单链表依次尾插五个数1,2,3,4,5,并且删掉2这个数,并将删除后的链表反转,(暂时还不会写的话合理利用到上面的接口实现哦,相信你能独立写出来)
反转:
before:1-->2-->3-->4-->5-->NULL;
after:5-->4-->3-->2-->1-->NULL;
代码参考:
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
typedef struct SLTNode
{
int date;
struct SLTNode* next;
}SLTNode;
SLTNode* BuySLTNode(int e)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
newnode->date = e;
newnode->next = NULL;
return newnode;
}
void SLTNodePushBack(SLTNode** pphead, int e)
{
SLTNode* newnode = BuySLTNode(e);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SLTNode* tail = *pphead;
//尾插找尾
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SLTNodePrint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->date);
cur = cur->next;
}
printf("NULL");
printf("\n");
}
SLTNode* SLTNodeFind(SLTNode* phead, int n)
{
SLTNode* cur = phead;
for (int i = 0; i < n; i++)
{
cur = cur->next;
}
return cur;
}
void SLTNodeErase(SLTNode** pphead, SLTNode* pos)
{
SLTNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
}
prev->next = pos->next;
free(pos);
}
SLTNode* SLT_Reverse(SLTNode* phead)
{
SLTNode* newphead = NULL;
SLTNode* cur = phead;
while (cur)
{
SLTNode* next = cur->next;
cur->next = newphead;
newphead = cur;
cur = next;
}
return newphead;
}
int main()
{
SLTNode* phead = NULL;
SLTNodePushBack(&phead, 1);
SLTNodePushBack(&phead, 2);
SLTNodePushBack(&phead, 3);
SLTNodePushBack(&phead, 4);
SLTNodePushBack(&phead, 5);
SLTNodePrint(phead);
SLTNode* pos=SLTNodeFind(phead, 1);
SLTNodeErase(&phead, pos);
SLTNodePrint(phead);
SLTNode* newphead=SLT_Reverse(phead);
SLTNodePrint(newphead);
return 0;
}
打印结果:
到这里就结束了,希望对你有收获。