我们已经了解了顺序表,那么既然有了顺序表为什么还需要单链表呢,其实顺序表在空间还是存在很大的浪费,我们上一篇文章了解到,每次都要申请比原先空间大两倍的新空间,这样是不合理的,所以我们引入单链表。
那么单链表是怎么样的呢,他依旧是一种线性表,在逻辑结构是线性的,但在物理结构上并不是线性的。
单链表是由一个一个的节点构成的,那么节点是什么样的呢
后面会附上源代码方便大家学习
typedef int SLTdatetype;
typedef struct SListNode
{
SLTdatetype x;
struct SListNode* next;
}SLNode;
首先把int重定义为SLTdatetype方便之后替换变量,之后创建节点,节点里由两个部分组成,一个是数据,另一个是这个节点类型的指针next;
我们把这行代码放入新创建的头文件中,并创建两个源文件,那么有了节点之后链表又是怎么形成的呢。
如图每个节点都有自己的地址,自己节点内的指针指向下个节点的地址,这样就完成了节点的链接,我们首先完成链表的打印来练练手;
1,链表的打印
首先在.h文件中声明
接下来在test.c文件中,创建一个test01()函数来测试我们的打印,我们在链表中依旧要用malloc函数来申请空间,那么有人要问了,之前讲顺序表的时候不也是malloc申请的空间吗,能差多少呢,
在单向链表中,我们都是按需申请的,要用多少就申请几个节点,而顺序表就不一样了,2倍2倍的很大的。
我们创建3个节点并将每个节点依次赋值,这样3个节点就创建我完了,我们只需像图片一样将节点链接起来即可,第一个节点中的指针指向下一个地址,下一个节点的指针也只向下一个节点的地址,最后一个节点的next指向NULL;
将我们创建好的链表传入SLTprint函数。
接下来我们就可以实现链表打印函数啦,如图嗷,我们传来了链表的首地址在phead中,我们要遍历链表来打印他,如果节点不是空就打印他,在将下一节点的地址放到plist如此循环直到最后;
完成啦。
2,链表的尾插和头插
如图嗷,画的不好看,我们要在链表的尾部添加一个新的节点newnode,那么我们就要找到前一个节点,plist,所以我们还需要遍历链表,让plist的next指针指向newnode,在让newnode的next指针指向NULL;在实现尾插之前我们先实现一个函数来保证申请新的节点。
函数返回值为SLNode* ,返回一个节点的指针,参数为x,用malloc申请新节点的空间,用assert来判断是否创建成功,避免创建了空节点就返回;
先声明函数;
在创建一个test02()函数,完了创建一个空指针,这里忘了说了,在链表尾插中传的是一级指针的地址,接收的是二级指针,为啥呢,大家有没有听过,形参是实参的一份临时拷贝,如果要把他彻底改变,那么一定要进行,传址调用,而非传值调用。
看图,完了我来讲解嗷,我们这个函数接收了链表首指针的地址和要尾插的数据X;
判断传来的是否为空地址,要不没有意义,用创建节点函数创建newnode;plist接收首地址,
在尾插的时候我们要分两种情况,一种是链表就为空,第二种是存在数据,第一种的时候我们直接吧新节点赋给节点首地址即可,第二种情况我们就像刚才说的先遍历链表,找到最后一个节点,让最后一个节点的指针指向新节点;完了就完成尾插啦。
程序成功啦。
下面进行头插
先进行函数声明
传地址调用,
如何实现头插呢,我们要创建一个新的1节点newnode,完了让newnode的next指针指向我们链表的第一个节点,完了就ok了不,不对嗷,因为我们链表是从头节点遍历的,所以我们头插了新指针后我们要把新的节点的地址放到头节点的地址;
函数很简单嗷,我们下面来看运行结果;
very nice 嗷
3,链表的头删,尾删
下面我们来进行链表的头删和尾删,先来尾删;
如图我们要删掉pcur这一节点,我们都需要做什么准备呢,先确保链表不为空,为空还删啥了,
让plist指针从头遍历,找到pcur前一个节点plist,让plist中的next指向空就ok了;
进行函数声明;
我们把节点一个一个删除
函数实现,我们逐步来讲解下嗷,首先assert(pphead和*pphead)为了防止直接传来空的链表地址,没有意义,我们进行的是尾删,如过*pphead为空就说名,链表为空,都为空了我们还删啥呢,下一步就像刚才说的创建pcur,plist,这个if,else是噶和呢,不急我们先看else内的语句,当pcur中的next指向的为空指针,循环结束,我们这样就得到了最后一个节点,循环里我们让plist接收pcur还没有变化的节点地址,这样就得到了pcur前一个节点的地址;pcur = pcur->next的意思就是链表的遍历,一下一下往后,我们让第二个节点的指针让他的next指向NULL,在释放最后一个节点的地址,就完成啦;
我们看看程序运行效果图
0k啦,我们下面进行头删,
看我们现在要删除第一个节点plist,让plist->next赋给*pphead让第二个节点当成头节点,完了在对第一个节点进行释放操作
先进行函数声明
我们还是把节点一个一个删掉;
下面来进行函数实现
这个就简单多了还是assert来保证删除不为空,找到第一个节点和第二个节点,先让第二个节点指向头节点的地址,再释放抛弃的那个头节点;
下面看运行效果图
头删也完成啦;
4,链表的查找,链表在指定位置前添加数据,链表在指定位置后添加数据
麻了吧,我们先来实现链表的查找,链表的查找是为了服务后面几个内容的;
函数声明,我们找到的时候要返回这个节点的地址,和顺序表不一样,链表是指针连起来的,所以我们找到的一定是节点的地址;
在节点内找1
我们这个函数接收了链表的头结点的地址,先assert()避免去找空指针,创建plist指针来遍历节点和x我们要找节点中是否存在x如果存在就返回节点的地址,如果不存在,就返回空指针;
成功找到了1
我们改找5
没了;
OK了
下面我们来进行指定位置之前添加数据
那么这个指定位置到底指的是哪里呢,
我们需要find函数来找到指定节点的指针,再考虑是再后面添加还是在前面添加,
这么长我靠,我们首先要传链表的头结点指针的地址,和我们用find函数找到的pos节点的指针,还有要添加的数据
画太丑了,,,首先啊,我们还是遍历链表找到pos指针前一个节点的指针,我们叫plist,
让plist节点的next指向newnode的地址,再让newnode的next指向pos的地址,不对我们要想想是先蓝线还是先绿线呢,其实这个部分我们必须先绿线在蓝线,为啥呢,你想如果先蓝线的话,蓝线的next指向新的节点的地址,那蓝线之前next指向的呢,没了呀,迷失在茫茫大海中,所以这里的顺序也要注意,下面进行函数实现。
我们这个分为两种情况,一种为头节点就是pos节点,这是我们直接头插一个新节点即可,第二种情况就是存在多个节点,这时我们就来遍历节点循环的限制条件为plist->next不为pos这样plist中的next指针就为pos的前一个节点,让newnode节点的next指向pos,再让plist节点的next指向newnode这样就完成了,我们下面来看执行效果
我们再对尾部进行一次添加
也成功啦,下面我们来进行指定位置之后添加数据
看哈,我们首先要创建一个newnode节点完了让newnode节点的next指向pos->next节点的地址也就是POS后面那个节点的地址,跟刚才的思路一样,不然后面的节点就迷失在茫茫大海中了,pos节点中的next在指向next节点我们下面来进行函数实现。
还是一样先进行函数声明
我们找4这个节点在他屁股后面天一个31
下面我们来进行函数实现
跟刚才解释的一样很简单的
成功找到
接下来试试开头
也是ok的;
5,链表在指定位置pos的删除,链表在指定位置pos之后节点的删除
如图哈我们要删除pos的节点,要先遍历链表找到pos的前一个节点的地址我们叫它plist,再找到pos的后一个节点,将plist节点和pos->next节点链接到一块,释放pos节点
先进行函数声明
找到date ==1节点的地址完了删掉它
来吧,我来解释下,首先还是assert避免传来空链表的地址和避免去删除空节点,如果传来要删除的pos节点就为头节点,那怎么办呢,直接调用我们之前的头删操作即可,如果不是头节点的话就像刚才说的遍历链表来找plist和pos->next链接起来再释放pos节点
我们来看任务效果图
成功啦,我们来试试删掉4
太成功啦,孩子们;
接下来我们来实现pos节点之后节点的删除
来轻车熟路了嗷,我们首先找到pos节点和pos的下下的节点我们管中间的节点叫plist,让pos节点和pos->next->next节点链接起来完了释放节点plist
先函数声明
我们来尝试删到3后面这个4
这个也是很简单嗷,先assert避免删除空,和刚才说的思路一模一样;
、都是ok的
6,链表的彻底销毁
来了来了先声明函数
如图我们要一个一个销毁创建的每一个节点,
我们首先创建一个节点指向头节点完了我们就开始遍历链表,创建一个plist指向pcur的next也就是第二个节点,此时释放第一个节点,再让pcur指向第二个节点,这样就完成了链表的彻底销毁;
完美啦,
下面附上gitee码云方便大家对照着学习