前言
在数据结构中,单链表是十分重要的一种结构,其最大的存储特点是数据存储不连续,通过指针相连。相比于同为线性表的顺序表,其优势是插入和删除十分简单,其缺点是查找某个节点比较复杂,由于其不是连续存储,所以不能直接用下标。在此文中,讲解了用C语言实现带头节点的单链表。
一、链表的组成
每个链表节点由两部分组成,一个是节点本身所带的值,还有一部分是下一个节点的地址。
所以我们的节点结构体可以以下定义:
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
一个节点由包含SLDateType类型的data和指针next组成,再将struct SListNode重命名为SListNode。
二、包含
在单链表中,由于需要手动申请和释放空间,还需要打印和断言等基本操作,所以需要包含以下头文件:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
三、函数实现
1)初始化
初始化链表就是让指针指向头节点,如果是不带头节点的单链表,就是指向第一个节点。
思路:为头节点申请一块空间,将头节点的next指向NULL;
void Init(SListNode** pphead)
{
SListNode* p = (SListNode*)malloc(sizeof(SListNode));
if (p)
{
p->next = NULL;
(*pphead) = p;
}
else
perror("Init error!");
}
注意此处传参类型为SListNode**pphead,因为,我们在此处,需要对一个指针的指向做修改,如果我们使用一重指针SListNode*phead,当我们对phead只存在于该函数栈帧中,当函数结束,函数栈帧销毁,该参数会销毁,所以无法对原来指针的指向进行修改。在这里,我们最好对开辟空间p进行检查,看是否成功开辟空间。
2)头插
顾名思义,头插法就是在头节点和头节点后面一个节点之间插入一个节点的方法。
思路:将新节点指向phead的下一个节点,再将pheqd指向该节点。注意二者顺序不可以颠倒。
void Insert_head(SListNode** pphead, SLTDateType x)
{
//首先检查传入头节点合法性,即确定其是否为空
assert(pphead && (*pphead));
//为节点申请一块空间
SListNode* s = (SListNode*)malloc(sizeof(SListNode));
if (s)
{
s->data = x;
//将s的next指向头节点的下一个
s->next = (*pphead)->next;
//头节点的下一个指向s
(*pphead)->next = s;
}
else
perror("Insert_haed! error");
}
传入参数为头节点地址,和需要头插节点的值。
3)尾插
尾插法就是在链表末尾插入节点的方法。
思路:①找到尾节点,②将s节点的next指向NULL,因为s节点要作为新的尾节点。③将原来尾节点的next指向s。步骤同样不能顺序颠倒。
void Insert_tail(SListNode**pphead,SLTDateType x)
{
//首先检查传入头节点合法性,即确定其是否为空
assert(pphead && (*pphead));
//找到尾节点
SListNode* tail = *pphead;
//为节点申请一块空间
SListNode* s = (SListNode*)malloc(sizeof(SListNode));
while (tail->next)
{
tail = tail->next;
}
if (s)
{
s->data = x;
//将s的next指向NULL
s->next = NULL;
//尾节点的下一个指向s,s成为新的尾节点
tail->next = s;
}
else
perror("Insert_tail error!");
}
传入参数为头节点地址,和需要尾插节点的值。
4)打印链表
用法说明:将单链表按照顺序打印出来。
思路:依次遍历链表并将其打印出来。
void print(SListNode* phead)
{
assert(phead);
SListNode* p = phead->next;
//遍历并打印链表
while (p)
{
printf("%d=>", p->data);
p = p->next;
}
}
因为此处不需要对指针进行修改,所以用一级指针即可。
5)头删
将头节点后面的第一个节点删除,此处应区分一下空链表和非空链表。
思路:对于空链表,直接返回即可。对于非空链表,则进行以下步骤:①找到需要删除的节点s,让phead的next指向s的next即可。②释放s的空间。
空链表
非空链表:
void del_head(SListNode** pphead)
{
assert(pphead && (*pphead));
//如果链表为空,直接返回
if ((*pphead)->next == NULL)
return;
//不为空,删除节点
SListNode* s = (*pphead)->next;
(*pphead)->next = s->next;
free(s);
}
6)尾删
删除链表最后面一个节点。
思路:①找到尾节点的前一个节点p,释放尾节点的空间,将p的next指向NULL。
void del_tail(SListNode**pphead)
{
assert(pphead && (*pphead));
SListNode* p = *pphead;
//如果是空链表,直接返回
if (p->next == NULL)
return;
//找到尾节点p
while (p->next->next)
{
p = p->next;
}
free(p->next);
p->next = NULL;
}
7)查找
在链表中查找第一个值为x的节点,并且返回该节点的地址。
思路:遍历链表,将链表的值与x相比,知道找到值为x的节点并将其返回
SListNode* find(SListNode* phead,SLTDateType x)
{
SListNode* p = phead->next;
while (p)
{
if (p->data == x)
return p;
p = p->next;
}
return NULL;
}
8)销毁链表
将该链表销毁。
思路:如果链表不为空,就循环调用头删法,由于尾删时查找尾节点会有时间消耗,当数据量较大的时候,时间开销会较大,所以不用尾删法.最后将头节点销毁。
void destroy(SListNode**pphead)
{
assert(pphead && (*pphead));
while ((*pphead)->next)
{
del_head(pphead);
}
free(*pphead);
}
四、源码分享
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
void Init(SListNode** pphead)
{
SListNode* p = (SListNode*)malloc(sizeof(SListNode));
if (p)
{
p->next = NULL;
(*pphead) = p;
}
else
perror("Init error!");
}
void Insert_head(SListNode** pphead, SLTDateType x)
{
//首先检查传入头节点合法性,即确定其是否为空
assert(pphead && (*pphead));
//为节点申请一块空间
SListNode* s = (SListNode*)malloc(sizeof(SListNode));
if (s)
{
s->data = x;
//将s的next指向头节点的下一个
s->next = (*pphead)->next;
//头节点的下一个指向s
(*pphead)->next = s;
}
else
perror("Insert_haed! error");
}
void Insert_tail(SListNode**pphead,SLTDateType x)
{
//首先检查传入头节点合法性,即确定其是否为空
assert(pphead && (*pphead));
//找到尾节点
SListNode* tail = *pphead;
//为节点申请一块空间
SListNode* s = (SListNode*)malloc(sizeof(SListNode));
while (tail->next)
{
tail = tail->next;
}
if (s)
{
s->data = x;
//将s的next指向NULL
s->next = NULL;
//尾节点的下一个指向s,s成为新的尾节点
tail->next = s;
}
else
perror("Insert_tail error!");
}
void print(SListNode* phead)
{
assert(phead);
SListNode* p = phead->next;
//遍历并打印链表
while (p)
{
printf("%d=>", p->data);
p = p->next;
}
printf("\n");
}
void del_head(SListNode** pphead)
{
assert(pphead && (*pphead));
//如果链表为空,直接返回
if ((*pphead)->next == NULL)
return;
//不为空,删除节点
SListNode* s = (*pphead)->next;
(*pphead)->next = s->next;
free(s);
}
void del_tail(SListNode**pphead)
{
assert(pphead && (*pphead));
SListNode* p = *pphead;
//如果是空链表,直接返回
if (p->next == NULL)
return;
//找到尾节点p
while (p->next->next)
{
p = p->next;
}
free(p->next);
p->next = NULL;
}
SListNode* find(SListNode* phead,SLTDateType x)
{
SListNode* p = phead->next;
while (p)
{
if (p->data == x)
return p;
p = p->next;
}
return NULL;
}
void destroy(SListNode**pphead)
{
assert(pphead && (*pphead));
while ((*pphead)->next)
{
del_head(pphead);
}
free(*pphead);
}