首先我们先了解 链表的概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
* 链表与顺序表的不同*:链表是存储的数据不是连续的,比起顺序表节省空间(顺序表realloc可能开辟太多空间,而导致浪费而链表则是需要则开辟一块空间避免了这个问题)。【现在我们进入单链表的实现与学习】
单向链表的物理结构:
*
链表的逻辑结构*:
typedef int SLdatatype;
typedef struct Slistnode
{
SLdatatype data;
struct Slistnode *next;//next来存放下一个结点的地址
}SL;
***重新命名有啥好处?***因为链表中会大量引用这个data变量,如果想要将其类型发生改变,那就需要一个个修改比较麻烦,如果使用 typedef 只需要修改int 便可以便于修改,同理重新定义结构体名称
初始化结构体
SL *BuySListNode(SLdatatype n)
{
SL *node=(SL*)malloc(sizeof(SL*));
if(node!=NULL)//防止开辟空间失败,成功则进行赋值,如果node==NULL则就返回NULL
{
node->data=n;
node->next=NULL;
return node;
}
else
{
return NULL;
}
}
然后我们开始实现它基本的操作,首先从头插入开始
首先看这张图,我们需要在1的前面插入0,则需要创建一个结点newnode,然后将newnode的next指向pphead的地址,再将newnode的值赋给pphead**
void SLPushFront(SL**pphead,SLdatatype n)//可能改变头指针,则需要传入二级指针
{
SL*newnode=BuySListNode(n);
if(newnode!=NULL)
{
newnode->next=*pphead;
*pphead=newnode;
}
}
*实现尾插:
*现在我们开始分析尾插:首先创建个newnode结点用函数BuySListNode进行初始化,再创建个指针cur指向链表的头部,使用迭代不断进行直到找到尾部为止(即那个结点的next指向空指针),然后将newnode->next指向cur,将cur->next指向空,这便是普通情况,如果头指针为空(即pr为NULL),则用就把新结点成为头结点。
oid SLpushback(SL** pphead, SLDatatype n)
{
if (*pphead == NULL)
{
*pphead = BuySListNode(n);
}
else
{
SL* cur = *pr;
SL* tail = SLcreate(n);
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = tail;
}
实现头删
:要实现头删除首先得保存头指针指向的下个结点的地址,然后在释放掉头指针但是如果你先释放掉头指针则就找不到下个结点,此外还得考虑如果这个链表是空则就不需要头删。
void SLpopfront(SL** pphead)
{
if(*pphead==NULL)
{
return ;
}
else
{
SL* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
}
*尾删的实现
照样先分析,想要将存储3这个结点删除,应该先能找到上个结点将其保存,使其上个结点的next指向NULL就可以了。然后再考虑特殊情况如果链表为空,则直接返回就行,如果只有一个结点,会发现上面那个逻辑prev-next为空又指向空,很明显的错误,所以则就free掉这个头节点就行。
void SLpopback(SL** pphead)
{
if (*pphead == NULL)
{
return;
}
else if((*pphead)->next==NULL)
{
free(*pphead);
*pphead=NULL;
}
else
{
SL* cur = *pr;
SL* prev = NULL;
while (cur->next != NULL)
{
prev = cur;
cur = cur->next;
}
free(cur);
prev->next = NULL;
}
}
为了使后续操作,在任意位置插入,和删除更加方便我们创建查找函数
``
SL*SLsearch(SL* pr, SLDatatype n)
{
assert(pr);
SL* search = pr;
while (search!= NULL)
{
if (search->data == n)
{
return search;
}
search = search->next;
}
return NULL;//没有找到怎么返回一个空指针
}
实现任意位置查找(在想要寻找的数字前)
如图所示,在pos前面插入一个新结点存储数据为2,应该找到pos上个结点的地址(比较麻烦后期我们会介绍带头结点的双向循环链表可以完美的解决这个问题),所以我们可以创建prev用不断的循环使prev->next等于pos时候停止,将prev->next指向创建的结点,然后将创建结点的next指向pos就基本完成插入操作了。但是我们得考虑是否有特殊情况会使代码崩溃假设当想插入pos恰好使头结点,会发现prev->next会一直查找不到pos,会陷入死循环程序崩溃,所以这种情况我们可以单独列出来进行头插引入函数就行。
void SLinsert(SL** pphead, SL*pos,SLDatatype n)
{
SL* newnode = SLcreate(n);
if (pos == *pphead)
{
SLpushfront(pphead, n);
}
else
{
SL* prev = *pphead;
while (prev->next!= pos)
{
prev = prev->next;
}
prev->next = newnode;
newnode->next = pos;
}
}
删除结点
同样开始分析先找到pos得先知道上个结点和下个结点的地址将保存,然后将prev->next指向cur->next将cur结点 free掉,然后把cur制为空,当然这个特殊情况跟insert操作相同,大家可以自行理解。
void SLerase(SL** pphead, SL* pos)
{
assert(*pphead);
if ((*pphead)->next == pos)
{
SLpopback(pphead);
}
SL* cur = *pphead;
while (cur ->next!= pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(cur);
cur = NULL;
}
结尾
看到这里,相信伙伴们已经对单链表已经有了基本了解,掌握了基本的操作接口实现方法。其实单链表在以后的学习工作中,并不是特别的实用,因为单链表也有很多缺陷。但是,掌握单链表是我们以后学习复杂数据结构的必须要的。
如果看到这里的你感觉这篇博客对你有帮助,不要忘了收藏,点赞,转发,关注哦。