赞
踩
单链表学习导航
本文为我对链表学习的过程与理解,适合新手学习交流。内容如题,我将结合画图和截图带领大家更好的理解单链表结构,学习和使用它增删查改的功能。(VS2019编译器所展示)
如上图,单链表是通过结构体里面放置一个新的结构体指针的形式,使用下一个结构体的地址来找到该结构体中的数据的,链表相比于顺序表具有存储数据不连续,方便修改的特点。但单链表的缺点也很明显,只能从第一个节点开始遍历,比较局限。
此目的是为了更好的理解代码,在前文中我也提到过,但模块化看起来清晰更有利于我们理解以及使用、修改代码。(还有美观...)
在该文件中,我们将链表结构体定义,设置int为链表中数据的存储形式(方便展示),同时节点中增加下一个节点的地址。重定义链表方便使用该结构体。
- #define SLTDateType int
-
- typedef struct SListNode
- {
- SLTDateType Data;
- struct SListNode* Next;
- }SListNode;
在头文件中声明这些函数后直接使用即可,先列出来目标,后面咱们一一实现。
- // 动态申请一个节点
- SListNode* BuySListNode(SLTDateType x);
-
- // 单链表打印
- void SListPrint(SListNode* phead);
-
- // 单链表尾插
- void SListPushBack(SListNode** pplist, SLTDateType x);
-
- // 单链表的头插
- void SListPushFront(SListNode** pplist, SLTDateType x);
-
- // 单链表的尾删
- void SListPopBack(SListNode** pplist);
-
- // 单链表头删
- void SListPopFront(SListNode** pplist);
-
- // 单链表查找
- SListNode* SListFind(SListNode* plist, SLTDateType x);
-
- // 单链表在pos位置之后插入x
- void SListInsertAfter(SListNode* pos, SLTDateType x);
-
- // 单链表删除pos位置之后的值
- void SListEraseAfter(SListNode* pos);
-
- // 单链表的销毁
- void SListDestroy(SListNode* plist);
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
其中,有的函数功能传入的参数为二级指针,为什么呢?因为函数传入的参数是实参的一份临时拷贝,如果传入的是一级指针,那么我们可以改变这个指针指向的数据,而如果我们想要改变这个指针的地址呢?同样的道理,我们需要一个指向这个指针的地址的指针,也就是二级指针。
该区域主要为所写功能函数的测试模块,以此来判断函数的正确性、是否完善。
- int main()
- {
- SListNode* List = NULL;
-
- SListTest1(List);
- SListTest2(List);
- SListTest3(List);
- SListTest4(List);
- SListTest5(List);
- SListTest6(List);
- SListTest7(List);
-
- return 0;
- }
类似于菜单,不过少了些富丽堂皇的界面,因为写了菜单之后很可能会影响到我们调试的效率,将功能函数实现并调试完成之后,再完成菜单界面是个更好的选择。测试区域可以自行定义,我也将在下文中结合该区域来展示函数的实现效果。
链表节点的申请可以使用malloc和calloc函数,此处我们使用malloc,记得判断返回值是否为空。
- //动态申请链表节点
- SListNode* BuySListNode(SLTDateType x)
- {
- SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
- if (newnode == NULL)
- {
- perror("BuySListNode::malloc");
- return;
- }
- newnode->Data = x;
- return newnode;
- }
此处还可以在返回节点之前将该节点的下一个地址置空,初始化也是种不错的选择。可以看个人需求选择。
将第一个节点传入函数,通过节点中的下一个指针一一遍历每个节点中存储的数据,并打印出来,最后节点为空时停止即可。
- //打印链表
- void SListPrint(SListNode* phead)
- {
- SListNode* cur = phead;
- while (cur)
- {
- printf("%d->", cur->Data);
- cur = cur->Next;
- }
- printf("NULL\n");
- }
尾插需要考虑两种情况,第一种则是链表为空的情况,这种情况直接让该链表指向开辟的节点上即可;第二种情况为非空的情况,这种情况比较复杂,我们来画图解决:
地址都是瞎编的,原理如此,代码实现如下:
- //尾插
- void SListPushBack(SListNode** pplist, SLTDateType x)
- {
- //判断是否为空链表
- if (*pplist==NULL)
- {
- *pplist = BuySListNode(x);
- (*pplist)->Next = NULL;
- }
- else
- {
- //查找尾部
- SListNode* ptr = *pplist;
- while (ptr->Next)
- {
- ptr = ptr->Next;
- }
- //插入
- ptr->Next = BuySListNode(x);
- ptr->Next->Next = NULL;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
结合打印链表函数效果如图:
头插的实现和尾插一样,分空链表和非空链表两种情况。二者区别在于非空情况,头插演示如图:
原来没有头插的非空链表,pplist指向头节点的地址。
插入头节点的非空链表演示如上图,代码实现如下图。
- //头插
- void SListPushFront(SListNode** pplist, SLTDateType x)
- {
- if (*pplist == NULL)
- {
- *pplist = BuySListNode(x);
- (*pplist)->Next = NULL;
- }
- else
- {
- SListNode* ptr = BuySListNode(x);
- ptr->Next = *pplist;
- *pplist = ptr;
- }
- }
实现效果如图:
尾删分为三种情况:
第一种为链表是否为空,若为空,可以选择报错也可以选择直接return(没有内容咋删...);
第二种为链表不为空,但只有一个节点的情况,这种情况我们需要直接将链表置空;
第三中为多节点的情况,这时我们要将NULL节点之前的节点置空,其中需要NULL节点前前个节点来判断,因为还要free掉不要用的内存,该情况如下图所示。
具体实现如下:
- //尾删
- void SListPopBack(SListNode** pplist)
- {
- assert(pplist);
- assert(*pplist);
-
- if ((*pplist)->Next == NULL)
- {
- //单个链表块
- free(*pplist);
- *pplist = NULL;
- }
- else
- {
- //2个及以上链表块
- SListNode* ptr = *pplist;
- while (ptr->Next->Next)
- {
- ptr = ptr->Next;
- }
- free(ptr->Next);
- ptr->Next = NULL;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
效果如下:
头删只需要考虑两种情况就行了。
第一种为判断链表是否为空;
第二种不论是否为多节点,指向头节点地址的指针都会向后移动到下一个节点的地址上,因此不需要考虑这下一个节点是否为空。
代码实现如下:
- //头删
- void SListPopFront(SListNode** pplist)
- {
- assert(pplist);
- assert(*pplist);
-
- SListNode* Del = *pplist;
- *pplist = (*pplist)->Next;
- free(Del);
- Del = NULL;
- }
具体效果如图:
查找目标节点并返回该节点的地址,传值选用一级指针就行了,因为有返回值为一级指针,这类函数的方法也可以代替上面的二级指针的使用(个人觉得这样不好看)。
查找需要遍历,除非找到目标节点,否则到NULL为止停下,直接返回查找节点即可。
- //查找
- SListNode* SListFind(SListNode* plist, SLTDateType x)
- {
- SListNode* find = plist;
- while (find && find->Data != x)
- find = find->Next;
- return find;
- }
实现效果如图:
这个函数和头插函数多节点的情况很像,不过该函数不需要改变头节点指针的位置,因此传入一级指针即可。
插入前需判断是否传入的为空指针,然后插入就行啦,不过写入的顺序要正确,否则可能会丢失原先的地址哦?
- //在pos位置之后插入x
- void SListInsertAfter(SListNode* pos, SLTDateType x)
- {
- assert(pos);
- SListNode* next = pos->Next;
-
- SListNode* newnode = BuySListNode(x);
- pos->Next = newnode;
- newnode->Next = next;
- }
效果如下:
这个函数还比较简单,注意传值是否为空指针和指向的下一节点是否为空就行了。
没有判断后者的话可是会非法访问的...不得不说链表对于初学者而言坑太多了。
- //删除pos位置之后的值
- void SListEraseAfter(SListNode* pos)
- {
- assert(pos);
- if (pos->Next == NULL)
- return;
- else
- {
- SListNode* NNext = pos->Next->Next;
- free(pos->Next);
- pos->Next = NNext;
- }
- }
实现效果如下:
类似于顺序表的销毁,不同的是需要free掉前者,且遍历的是链表节点的指针。
- //销毁
- void SListDestroy(SListNode* plist)
- {
- assert(plist);
- SListNode* Del = plist;
- while (Del)
- {
- plist = plist->Next;
- free(Del);
- Del = plist;
- }
- }
- #pragma once
-
- #include <stdio.h>
- #include <assert.h>
- #include <stdlib.h>
-
- #define int SLTDateType
-
- typedef struct SListNode
- {
- SLTDateType Data;
- struct SListNode* Next;
- }SListNode;
-
- // 动态申请一个节点
- SListNode* BuySListNode(SLTDateType x);
-
- // 单链表打印
- void SListPrint(SListNode* phead);
-
- // 单链表尾插
- void SListPushBack(SListNode** pplist, SLTDateType x);
-
- // 单链表的头插
- void SListPushFront(SListNode** pplist, SLTDateType x);
-
- // 单链表的尾删
- void SListPopBack(SListNode** pplist);
-
- // 单链表头删
- void SListPopFront(SListNode** pplist);
-
- // 单链表查找
- SListNode* SListFind(SListNode* plist, SLTDateType x);
-
- // 单链表在pos位置之后插入x
- void SListInsertAfter(SListNode* pos, SLTDateType x);
-
- // 单链表删除pos位置之后的值
- void SListEraseAfter(SListNode* pos);
-
- // 单链表的销毁
- void SListDestroy(SListNode* plist);
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- #include "SList.h"
-
- //动态申请链表节点
- SListNode* BuySListNode(SLTDateType x)
- {
- SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
- if (newnode == NULL)
- {
- perror("BuySListNode::malloc");
- return;
- }
- newnode->Data = x;
- return newnode;
- }
-
- //打印链表
- void SListPrint(SListNode* phead)
- {
- SListNode* cur = phead;
- while (cur)
- {
- printf("%d->", cur->Data);
- cur = cur->Next;
- }
- printf("NULL\n");
- }
-
- //尾插
- void SListPushBack(SListNode** pplist, SLTDateType x)
- {
- if (*pplist==NULL)
- {
- *pplist = BuySListNode(x);
- (*pplist)->Next = NULL;
- }
- else
- {
- //查找尾部
- SListNode* ptr = *pplist;
- while (ptr->Next)
- {
- ptr = ptr->Next;
- }
- //插入
- ptr->Next = BuySListNode(x);
- ptr->Next->Next = NULL;
- }
- }
-
- //头插
- void SListPushFront(SListNode** pplist, SLTDateType x)
- {
- assert(pplist);
- if (*pplist == NULL)
- {
- *pplist = BuySListNode(x);
- (*pplist)->Next = NULL;
- }
- else
- {
- SListNode* ptr = BuySListNode(x);
- ptr->Next = *pplist;
- *pplist = ptr;
- }
- }
-
- //尾删
- void SListPopBack(SListNode** pplist)
- {
- assert(pplist);
- assert(*pplist);
-
- if ((*pplist)->Next == NULL)
- {
- //单个链表块
- free(*pplist);
- *pplist = NULL;
- }
- else
- {
- //2个及以上链表块
- SListNode* ptr = *pplist;
- while (ptr->Next->Next)
- {
- ptr = ptr->Next;
- }
- free(ptr->Next);
- ptr->Next = NULL;
- }
- }
-
- //头删
- void SListPopFront(SListNode** pplist)
- {
- assert(pplist);
- assert(*pplist);
-
- SListNode* Del = *pplist;
- *pplist = (*pplist)->Next;
- free(Del);
- Del = NULL;
- }
-
- //查找
- SListNode* SListFind(SListNode* plist, SLTDateType x)
- {
- SListNode* find = plist;
- while (find && find->Data != x)
- find = find->Next;
- return find;
- }
-
- //在pos位置之后插入x
- void SListInsertAfter(SListNode* pos, SLTDateType x)
- {
- assert(pos);
- SListNode* next = pos->Next;
-
- SListNode* newnode = BuySListNode(x);
- pos->Next = newnode;
- newnode->Next = next;
- }
-
- //删除pos位置之后的值
- void SListEraseAfter(SListNode* pos)
- {
- assert(pos);
- if (pos->Next == NULL)
- return;
- else
- {
- SListNode* NNext = pos->Next->Next;
- free(pos->Next);
- pos->Next = NNext;
- }
- }
-
- //销毁
- void SListDestroy(SListNode* plist)
- {
- assert(plist);
- SListNode* Del = plist;
- while (Del)
- {
- plist = plist->Next;
- free(Del);
- Del = plist;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
链表内容对于新手而言还是比较多的,坑也是无处不在,需要诸位的细心与耐心,除了单链表还有很多其他形式的,不过都大同小异,自己多手搓几遍就能弄清楚了,总之,我会和各位一起加油的!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。