☃️个人主页:fighting小泽
🌸作者简介:目前正在学习C语言和数据结构
🌼博客专栏:数据结构
🏵️欢迎关注:评论👊🏻点赞👍🏻留言💪🏻
文章目录
- 前言
- 一.链表
- 1.1 链表的概念及结构
- 1.2链表的分类
- 二.单链表的特征
- 2.1单链表的优缺点
- 三.SList.h
- 四.SList.c
- 4.1单链表的打印
- 4.2malloc一个新节点
- 4.3单链表的销毁
- 4.4单链表的头插头删
- 4.5单链表的尾插尾删
- 4.6单链表的查找(修改)
- 4.7在pos前插入、删除节点
- 4.8在pos后插入、删除节点
- 5.结尾
前言
在前面我们已经学习过了有关顺序表的知识,但是我们知道顺序表是存在着一些问题的
问题:
- 中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
所以我们能不能寻求其他的解决方案?
- 不扩容
- 按需申请释放
- 解决头部或者中间插入删除需要挪动数据的问题
顺序表的一切根源就是一块连续的空间,那我们能不能用不连续的空间存储数据呢?
这个时候就要用到我们的链表了。
一.链表
1.1 链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
1.2链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
- 单向的或双向的
- 带头或者不带头
- 循环或者非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
- 无头单向非循环链表
- 带头双向循环链表
- 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
二.单链表的特征
单链表中,每一个节点存储一个数据和指向下一个节点的指针,最后一个节点指向NULL。
- 当我们需要进行头插头删等操作时,需要改变头指针的地址,就需要传入二级指针。
- 当我们想要修改某个结构体变量,(结构体的 next 或者结构体的 data) 时,我们需要传结构体的地址,就需要一级指针。
2.1单链表的优缺点
优点:
- 单链表增加删除节点简单,遍历的时候不会死循环。
- 逐个申请、释放空间,没有空间浪费。
缺点:
- 只能从头到尾遍历。只能找到后继,不能找到前驱,也就是只能前进。
三.SList.h
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SLTNode;
void SLTprint(SLTNode* phead);//打印
void SLTPushFront(SLTNode** pphead, SLTDataType x);//前插
SLTNode* BuyTNode(SLTDataType x);//malloc一个新节点
void SLTPushBack(SLTNode** pphead, SLTDataType x);//尾插
void SLTPopBack(SLTNode** pphead);//尾删
void SLTPopFront(SLTNode** pphead);//头删
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);//查找某个节点
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//在pos指针处插入元素
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//在pos指针后插入元素
void SLTErase(SLTNode** pphead, SLTNode* pos);//删除pos指针指向的节点
void SLTEraseAfter(SLTNode* pos);//删除pos指针后一个节点
void SLTDestroy(SLTNode* phead);//删除列表,释放maollc的节点
四.SList.c
4.1单链表的打印
遍历一遍链表打印即可
void SLTprint(SLTNode* phead)
{
SLTNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
4.2malloc一个新节点
SLTNode* BuyTNode(SLTDataType x)
{
SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
if (node == NULL)
{
perror("malloc");
return 0;
}
node->data = x;
node->next = NULL;
return node;
}
4.3单链表的销毁
迭代释放链表节点
void SLDestroy(SLTNode** pphead)
{
assert(pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
4.4单链表的头插头删
单链表的优势就是头插头删
对于头删,我们要断言二级指针指向的一级指针是否为空。若为空说明该链表没有元素,assert会报错。
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyTNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
void SLTPopFront(SLTNode** pphead)
{
assert(*pphead);
SLTNode* tail = *pphead;
*pphead = tail->next;
free(tail);
}
4.5单链表的尾插尾删
尾插时,需要分类讨论链表是否为空的情况
尾删时,同样需要链表是否为空。同样需要分开讨论仅剩一个头节点或剩余多个节点的情况。若只剩一个节点,删除后将头指针置 NULL 。若剩余多个节点,则删除最后一个节点后将尾节点的 next 置 NULL 。
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
SLTNode* newnode = BuyTNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
return;
}
else
{
SLTNode* tail = *pphead;
while ((tail->next) != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
void SLTPopBack(SLTNode** pphead)
{
assert(*pphead);
SLTNode* tail = *pphead;
if (tail->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
while (tail->next->next != NULL)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
4.6单链表的查找(修改)
遍历一遍链表,查找到返回结构体的指针,反之返回NULL
因为已经找到了结构体的指针,所以可以直接修改结构体的变量
查找函数是要配合下面的pos节点插入、删除函数使用的
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
SLTNode* cur = phead;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
4.7在pos前插入、删除节点
注意要分类讨论 pos 是否为头节点
此接口不常用,因为单链表的前一个节点不好找,算法效率低
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
assert(pos);
if (*pphead == pos)
{
SLTPushFront(pphead, x);
return;
}
SLTNode* cur = *pphead;
while (cur)
{
if (cur->next == pos)
{
SLTNode* newnode = BuyTNode(x);
newnode->next = cur->next;
cur->next = newnode;
return;
}
else
cur = cur->next;
}
}
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
assert(pos);
SLTNode* cur = *pphead;
if (*pphead == pos)
{
SLTPopFront(pphead);
//*pphead = cur->next;
//free(cur);
return;
}
while (cur)
{
if (cur->next == pos)
{
cur->next = pos->next;
free(pos);
return;
}
else
cur = cur->next;
}
}
4.8在pos后插入、删除节点
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
assert(pos);
SLTNode* newnode = BuyTNode( x);
newnode->next = pos->next;
pos->next = newnode;
}
void SLTEraseAfter(SLTNode* pos)
{
assert(pos);
SLTNode* cur = pos->next;
pos->next = cur->next;
free(cur);
}
5.结尾
这些就是我给大家分享的关于链表的知识啦,希望我们都能有所收获!
先赞后看,养成习惯!!^ _ ^
码字不易,大家的支持就是我坚持下去的动力,点赞后不要忘了关注我哦!
如有错误,还请您批评改正(。ì _ í。)