一.单链表的定义
1.组成
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
链表有很多结构,我们这里先讲述不带头不循环的简单单链表
单链表的节点一般由指向下一个节点的地址和数据成员组成,
通过当前节点中存放下一个节点的地址,找到下一个节点,最后一个节点的下一个节点为NULL,
单链表就是这些节点的组成,
二.单链表的操作
1.创建节点
SListNode* BuySListNode(SLTDateType x)
{
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (newNode == NULL)
{
perror("malloc:");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
使用malloc申请一块空间,存放该节点,对节点初始化,返回这个空间的地址
2.头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist != NULL);
if (*pplist == NULL)
{
SListNode* newNode = BuySListNode(x);
*pplist = newNode;
}
else
{
SListNode* newNode = BuySListNode(x);
SListNode* p = *pplist;
*pplist = newNode;
newNode->next = p;
}
}
当前链表的头指针就是第一个节点,所以要改变这个指针,就需要这个指针变量的地址,我们通过二级指针来改变它,如果链表为空,就申请一个新节点当头节点,如果不为空,就抽入进去,同时改变头指针的指向
3.尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist != NULL);
SListNode* p = *pplist;
if (p == NULL)
{
SListNode* newNode = BuySListNode(x);
*pplist = newNode;
return;
}
while (p ->next!= NULL)
{
p = p->next;
}
SListNode* newNode = BuySListNode(x);
p->next = newNode;
}
尾插的原理很简单,找到尾节点的上一个节点,改变指针即可,但在尾插中,我们也应该考虑改变头指针的问题,所以仍然需要传递二级指针
4.头删
void SListPopFront(SListNode** pplist)
{
assert(pplist != NULL&&*pplist!=NULL);
SListNode* p = *pplist;
if (p->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
*pplist = p->next;
free(p);
}
}
记录当下节点,让头指针走向下一个节点即可,然后释放记录的节点,改变头指针仍然需要二级指针,注意只有一个节点的时候情况
5.尾删
void SListPopBack(SListNode** pplist)
{
assert(pplist != NULL&&*pplist!=NULL);
SListNode* p = *pplist;
if (p->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
while (p->next->next != NULL)
{
p = p->next;
}
free(p->next);
p->next = NULL;
}
}
找到尾节点的上一个节点,释放尾节点,置为空指针即可,但也要注意只有一个节点的情况,
6.查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
while (plist != NULL)
{
if (plist->data == x)
return plist;
plist = plist->next;
}
return NULL;
}
遍历链表每个节点,返回该节点指针,如果没找到,返回NULL
7.在给定节点之后插入
void SListInsertAfter(SListNode* pow, SLTDateType x)
{
assert(pow != NULL);
SListNode* newNode = BuySListNode(x);
SListNode*p=pow->next;
pow->next = newNode;
newNode->next = p;
}
8.删除给定节点后面的节点
void SListEraseAfter(SListNode* pow)
{
assert(pow != NULL);
SListNode* p = NULL;
if (pow->next != NULL)
{
p = pow->next->next;
}
free(pow->next);
pow->next = p;
}
9.在给定节点前面插入
void SLTInsert(SListNode** pphead, SListNode* pow, SLTDateType x)
{
assert(pphead != NULL);
assert(pow != NULL);
assert(*pphead != NULL);
SListNode* p = *pphead;
if (p == pow)
{
SListPushFront(pphead, x);
}
else
{
while (p->next != pow)
{
p = p->next;
}
SListNode* newNode = BuySListNode(x);
newNode->next = p->next;
p->next = newNode;
}
}
找到该节点的上个节点,插入即可,如果给定节点是头节点,就相当于头插,
10.删除当前位置节点
void SLTErase(SListNode** pphead, SListNode*pow)
{
assert(pphead != NULL && *pphead != NULL);
assert(pow != NULL);
SListNode* p = *pphead;
if (p == pow)
{
SListPopFront(pphead);
}
else
{
while (p->next != pow)
{
p = p->next;
}
SListNode* pc = p->next;
p->next = p->next->next;
free(pc);
pc = NULL;
}
}
与给定插入类似,只不过是只有一个节点的情况下相当于头删,
11.链表的销毁
void SLTDestroy(SListNode** pplist)
{
assert(pplist!=NULL);
SListNode* p = *pplist;
while (p != NULL)
{
SListNode* pc = p;
p = p->next;
free(pc);
pc = NULL;
}
*pplist = NULL;
}
释放每一个节点,最后将头节点指向空即可
三,单链表的缺陷
-
单向遍历: 单链表只能单向遍历,从头节点开始,依次访问下一个节点。如果需要反向遍历,就需要从头开始遍历,直到达到目标节点。这在某些情况下可能会导致性能问题,尤其是对于需要频繁反向遍历的操作。
-
随机访问困难: 在单链表中,要想访问或者操作某个特定位置的节点,通常需要从头节点开始遍历,直到达到目标位置。这与数组不同,数组可以通过索引直接访问任意位置的元素。对于需要频繁随机访问的应用,单链表可能效率较低。
-
无法快速获取前驱节点: 在单链表中,如果想要删除一个节点,通常需要知道其前驱节点。但是由于单链表只保存了后继节点的指针,要获取前驱节点通常需要从头开始遍历,直到找到目标节点的前一个节点。
-
不适合逆向操作: 有些算法或数据操作需要逆向访问数据,例如逆向遍历、逆向查找等。由于单链表的结构,这样的逆向操作可能需要更多的时间和空间。
-
情况较多:有些时候传二级指针,有些时候还要思考只有一个节点的情况,