> 文档中心 > 大数据,请把它推给还不会单链表的人(数据结构)

大数据,请把它推给还不会单链表的人(数据结构)


目录

一.基本介绍:

二.单链表基本操作


                           每一个不曾起舞的日子,都是对生命的辜负! 

                                                                                                   --------尼采

目录:

链表的基本介绍

二:单链表的基本操作


 单链表只要有人和你讲过左值和右值问题,单链表so easy


一.基本介绍:

1.链表的每一个结点都包含两个域:数据域和指针

2.其实计算机中并没有指针的指向,箭头指向只是人为假象出来的,实际上指针域存储的是下一个结点的地址。

3.链表的种类:链表的种类从三对修饰词中每对修饰词中选出一个,例如我们今天讲的就是不带头单向不循环链表

 4.左值和右值问题

拿这个曾经卡死你的那个插入结点的代码为例来讲解:

 

 

备注:这里要从没有标记的一端开始修改,也就是先执行s->next=p->next,

后执行p->next=s     ,因为如果先执行p->next=s;就会找不到p原来后面的那个结点了,这和顺序表的从哪一端开始移动很像!

5.单链表较动态顺序表:

动态顺序表:

优点:只用通过数组下标访问的方式就可以随机访问某一个数组元素并进行操作,时间复杂度低;

缺点:当要删除或插入元素的时候不得不移动大量元素,时间复杂度高;

单链表(single  list 常简写为 SLT)

优点:当要删除或插入元素的时候不用移动大量元素,时间复杂度低;

缺点:当要访问某一个结点并进行操作的时候要从头开始遍历,时间复杂度高;

6.要想学会单链表你得了解这些(上面没有讲到的下面会一一讲到)


🚗🚗🚗🚗🚗🚗🚗这里是正文接口分界线🚗🚗🚗🚗🚗🚗🚗🚗🚗

二.单链表基本操作

1.打印单链表

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

备注;在对每一个接口进行测试的时候,通过打印结果来观察测试接口的正确与否


2.构造结点

每次插入前都要构造一个新结点,这一步模块化代码是不是和顺序表的插入前检查是否需要扩容很像!哈哈哈

SLTNode* BuySLTNode(SLTDataType x){SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));newnode->data = x;newnode->next = NULL;return newnode;}

注意:返回类型是SLTNode*类型


3.尾插:在单链表尾部插入一个结点

关于哨兵位的头结点:(先给结论吧)

总结:如果带头结点的单链表在进行操作时,就可以使得每一个带数据元素的结点都有前驱结点,就可以统一操作这些带 数据元素的结点,就可以不用考虑一些特殊情况。

但是如果是不带头结点的单链表在进行操作时,第一个结点(也就是第一个带数据元素的结点)没有前驱结点,所以操作起来要对第一个结点特殊考虑。

一:(不带头结点的话)还是借一借排队的例子和你讲一讲,你在食堂排队打饭,你老老实实排队(尾插),会遇到两种情况:(一个不太恰当的例子)

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;}}

 

命名:push 压入             pop弹出

          back尾                        front 头


4.尾删:删除单链表尾部的最后一个结点

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;}}

 

 


 5.头插:在存储第一个元素的结点前插入一个结点

void SListPushFront(SLTNode pplist, SLTDataType x){SLTNode* newnode = BuySLTNode(x);newnode->next = *pplist;*pplist = newnode;}

6.头删:删除存储第一个元素的结点

void SListPopFront(SLTNode pplist){if (*pplist == NULL){return;}else{SLTNode* next = (*pplist)->next;free(*pplist);*pplist = next;}}


7.在pos结点后插入一个结点

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);}}

 9.删除pos结点

SLTNode* SListFind(SLTNode* plist, SLTDataType x){SLTNode* cur = plist;while (cur){if (cur->data == x){return cur;}cur = cur->next;}return NULL;}

有小伙伴看到这里就可能会问了,为什么单链表的接口这里没有单链表的初始化和销毁,那是因为,单链表的初始化和销毁也就是对头指针进行操作,对应建立一个头指针和将头指针free掉,那个一步就能完成,所以在主函数里面就可以完成,可以但没必要创建一个专门的接口去处理。

另外插一句,单链表的头很重要,

例一:如果要想清空一个结点,只用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#includetypedef 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 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;}

打印结果:

 


                                     到这里就结束了,希望对你有收获。