链表是数据结构中比较重要的内容,由于其代码中繁多的指针使用。使很多同学患上了链表恐惧症。正所谓会者不难,难者不会。多打代码多看代码就能让我们真正理解链表,并会使用链表。
这篇博客我们着重讨论无头单向不循环链表。
我们现在介绍一下链表的节点的结构体
typedef int SLDataType;
typedef struct SListNode{
SLDataTyope _data;//节点数据域
struct SListNode _next;//节点连接指针域
}SListNode;
可以把头结点封装起来,虽然在c语言中看来用处不大。外部依然可以修改访问它。但用c++来写的话,可以大大增加程序的安全性。
typedef struct SList{
SListNode=_head;
}SList;
首先来看链表的节点是如何构建出来的,构建节点的函数
思路非常简单:用一个新的指针申请相应的一个节点的空间,而后把相应的数组赋值给节点的数据域。
SListNode* CreatSListNode(SLDataType x){
SListNode *NewNode=(SListNode *)malloc(sizeof(SListNode));
NewNode->_data=x;
NewNode->_next=NULL;
return NewNode;
}
链表的初始化,可以说是最简单的了
void InitSList(SList *ps)
{
s->_head=NULL;
}
我们再来看看 链表的尾插代码
尾插思路:运用循环找到链表的尾部位置,而后把尾部与需要插入的节点连接起来。注意判断链表为空的情况
void SListPushBack(SList *ps,SLDataType x)
{
SListNode* cur=ps->_head;
if(ps->_head==NULL)
{
s->head=CreatSListNode(x);
}
else
{
while(cur->next);
{
cur=cur->_next;
}
cur->_next=CreatSListNode(x);
}
}
在尾插代码中我犯过一个低级错误,导致代码老是出错,找了好多次都没找出来。在这里特别指出
尾插代码倒数第七行,当时我是这么写的
while(cur)
{
cur=cur->_next;
}
cur=CreatSlistNode(x);
}}
循环的逻辑没有什么问题,但是我把新建的节点赋值给了cur指针,并不管原链表的事情。意思就是这个新节点并没有连到相应的链表上。而是把新节点赋值给了cur指针,导致严重错误!!
接下来是头插代码
void SListHeadInsert(SList *ps,SLDataType x)
{
SListNode* cur=ps->_head;
SListNode *NewNode=CreatSListNode(x);
NewNode->=cur;
ps->head=NewNode;
}
头插没有什么好说的,记得把链表的头部重新赋值给新节点就好
接下来是头删和尾删接口,为什么把这两者放到一起来说呢!应为在链表的接口中。这两个接口一定要使用两个指针去记录被删节点的下一个节点/被删节点的前一个节点,这样才能保证把链表重新连接起来。
我们来说头删,代码如下
void SListPopFront(SList *ps)
{
/*SListNode *prev=ps->_head->_next;
SListNode *cur=ps->_head;
if(ps->_head==NULL)
{如果前部代码这么写的话,一旦链表头结点为空,prev就会出现问题。那么整个程序就会崩溃。所以要对位置进行斟酌
return;
}*/
if(p->_head==NULL)
{
return;
}
SListNode *prev=ps->_head->_next;
SListNode *cur=ps->_head;
free(cur);
cur=NULL;
ps->_head=prev;
return;
}
接下来来看尾删代码
尾删思路:找到最后一个节点,释放尾部的节点,将尾部的前一个一个节点的_next指向NULL,注意处理空链表和只有一个节点的情况
void SListPopBack(SList *ps)
{
if(ps->_head==NULL)
{
return;
}
SListNode *prev=NULL;
SListNode *cur=ps->_head;
while(cur->_next)
{
prev=cur;
cur=cur->_next;
}
if(prev==s->_head)
s->_head=NULL;
else{
free(cur);
prev->_next=NULL;
}
}
最主要的就是以上介绍的创建节点,头插,尾插,头删,尾删。。最主要的就是想清楚过程,代码实现的过程十分简单
来看看链表的打印函数
void PrintSList(SList*ps)
{
SListNode*cur=ps->_head;
if(cur==NULL)
return;
while(cur)
{
printf("%d->",cur->_data);
cur=cur->_next;
}
printf("\n");
}
链表的销毁接口代码。
void DestroySList(SList *ps)
{
SListNode *prev=NULL;
SListNode *cur=ps->_head;
while(cur)
{
prev=cur;
cur=cur->_next;
free(prev);
}
}
不用说,这个各位肯定看得懂
下来看链表的在pos位置处插入数据
思路:位置pos处已经给出,不用再从头结点向后一个个的找位置。
void SListInsert(SListNode *pos, SLDataType x)
{
SListNode* cur=pos->_next;
pos->_next=CreatSListNNode(x);
pos->_next->_next=cur;
}
在pos处后删除一个节点的函数
void PopSListNode(SListNode *pos)
{
SListNode *cur=pos->_next;
if(cur==NULL)
return;
stNode *prev=pos->_next->_next;
free(cur);
pos->_next=prev;
}
以上就是单链表所有的接口,在这里要注意几点
1.要修改指针的位置时,要传入二级指针的参数,这样才能够去改变指针的位置。这个道理就和传值调用与传址调用是一样的。
2.着重注意判断循环条件,看出循环时是尾指针还是尾指针的下一个指针。
3.当不能改表原链表节点时,要重新申请一个指针,对这个申请的指针进行操纵!!
再一个,要对链表的代码多写多练。这是做OJ题的基础!!