单链表的基础结构
单链表在创建前,我们需要知道,在单链表中我们需要插入数据,在插入数据之后还需要一个链表指针(用于指向下一数据的存储位置),因此我们便能得到单链表的结构如下
typedef int SLTDateType;//便于需要改变数据类型时作统一修改
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
单链表的数据操作
①动态申请一个节点
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x)
{
//开辟一块空间用来存放储存数据和下一数据的地址
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)//开辟失败进行反馈
{
printf("malloc fail");
return NULL;
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
②单链表的打印
// 单链表打印
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
while (cur != NULL)
{
printf("%d->",cur->data);
cur = cur->next;
}
printf("NULL");
printf("\n");
}
用图示来解释结束打印的条件
如果判断条件为cur->next的话那么最后一个数据将不会被打印出来。
③单链表的尾插
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newnode = BuySListNode(x);
SListNode* cur = *pplist;
if (cur == NULL)
{
*pplist = newnode;
}
else
{
while (cur->next != NULL)
{
cur = cur->next;
}
cur->next = newnode;
}
}
根据图像分析可知我们需要以cur->next作为条件来寻找NULL指针
但没有节点时,会出现明显的问题,这时候我们只需要直接使plist=newnode即可
④单链表的头插
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x)
{
assert(pplist);
SListNode* newnode = BuySListNode(x);
/*SListNode* tmp = *pplist;
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
*pplist = newnode;
(*pplist)->next = tmp;
}*/
newnode->next = *pplist;
*pplist = newnode;
}
简要分析可知,这里并不需要作条件判断,因为只有plist的指向需要做改变
⑤单链表的尾删
// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
assert(pplist);
assert(*pplist);
SListNode* tail = *pplist;
if (tail->next == NULL)
{
free(tail);
*pplist = NULL;
}
else
{
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
简要分析,如图,要先通过tail->next->next找到最后一个节点的上一个节点,这之后先将tail->next指向的最后一个节点释放,再将其指向NULL作为新的尾节点
同样的,因为是通过tail->next->next来寻找尾节点,所以当原本仅有一个节点时(如图),我们只需要先直接释放tail再将plist指向NULL即可
⑥单链表的头删
// 单链表头删
void SListPopFront(SListNode** pplist)
{
assert(pplist);
assert(*pplist);
SListNode* tmp = *pplist;
*pplist = (*pplist)->next;
free(tmp);
tmp = NULL;
}
如图所示,要先存储首节点,再将首节点的下一位置存储进plist中作为新的首节点,之后用先前临时存储的首节点使其释放
⑦单链表的数据查找
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x)
{
while (plist != NULL)
{
if (plist->data == x)
{
return plist;
}
plist = plist->next;
}
return NULL;
}
⑧单链表在pos位置之后插入x
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x)
{
assert(pos);
SListNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
如图我们可以分析,将pos->next的值存入newnode中,再使pos->next指向newnode
⑨单链表删除pos位置之后的值
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos)
{
assert(pos);
assert(pos->next);
SListNode* tmp = pos->next->next;
free(pos->next);
pos->next = NULL;
pos->next = tmp;
}
如图,其思路与尾删思路大同小异,就不做赘述
⑩单链表的销毁
// 单链表的销毁
void SListDestroy(SListNode* plist)
{
assert(plist);
SListNode* cur = plist;
while (cur != NULL)//依次销毁所有节点直至cur指向NULL
{
cur = cur->next;
free(plist);
plist = NULL;
plist = cur;
}
}
⑪单链表在pos位置之前插入x
// pos之前插入
void SLTInsert(SListNode** pplist, SListNode* pos, SLTDateType x)
{
assert(pplist);
assert(pos);
if (*pplist == pos)
{
SListPushFront(pplist,x);
}
else
{
//找到pos之前的位置
SListNode* pre = *pplist;
while (pre->next != pos)
{
pre = pre->next;
}
//插入值
SListNode* newnode = BuySListNode(x);
newnode->next = pre->next;
pre->next = newnode;
}
}
如图需要从首节点开始通过pre->next是否为pos为标志寻找pos的上一节点,从而在上一节点实现在其之后插入节点的操作
同样的,因为要使用pre->next所以在pos为首节点位置时,这里的实际意义就是头插一个数据,因此直接进行头插即可
⑫单链表删除pos位置的值
// pos位置删除
void SLTErase(SListNode** pplist, SListNode* pos)
{
assert(pplist);
assert(pos);
if (*pplist == pos)
{
SListPopFront(pplist);
}
else
{
SListNode* del = *pplist;
while (del->next != pos)
{
del = del->next;
}
SListNode* tmp = pos->next;
free(pos);
pos = NULL;
del->next = tmp;
}
}
与上一个操作类似,这里也需要从头开始寻找,对pos上一个的节点实现删除其后节点的操作
且如果删除的pos为首节点的话,直接进行头删即可
单链表的优势与劣势
①优势
插入和删除操作方便快捷:由于每个节点只有一个指针,插入或删除节点时只需改变节点的指针即可,而不需要移动其他节点,因此插入和删除操作的时间复杂度为O(1)。
不需要预先分配内存:单链表的节点可以在运行时动态分配内存,因此可以根据需要灵活地创建节点,这使得单链表的使用更加灵活和高效。
支持动态扩容:由于单链表的节点可以在运行时动态分配内存,因此可以根据需要动态地增加节点的数量,这使得单链表的大小可以根据需要动态扩展。
②劣势
访问元素时效率低:由于单链表中的节点不是连续存储的,因此访问元素时需要从头开始遍历,时间复杂度为O(n)。这使得单链表在访问元素方面效率较低,尤其是在需要随机访问元素时。
空间利用率低:由于每个节点都需要存储一个指针,因此单链表的空间利用率相对较低,尤其是在需要存储大量数据时。
不支持随机访问:由于单链表中的节点不是连续存储的,因此无法通过下标随机访问节点。这使得单链表在需要随机访问节点时效率低下。