【C语言实现简单数据结构】单链表
心有所向,日复一日,必有精进
文章目录
前言
上回书说道,顺序表极其实现,顺序表有其有点,也有其问题:
- 中间/头部的插入删除,时间复杂度为O(N)
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
思考:如何解决以上问题呢?下面给出了链表的结构来看看
链表
1.1链表的概念和结构
概念:==链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 ==
要想把链表链接起来,在逻辑上是连续的,所以是线性结构但是:
逻辑上:
物理上:(箭头不存在,方便我们理解)
我们看到了物理上可能不是连续的,是链接的,一个结构里存放两种信息构成一个结点,包括两个域,存储数据元素的域称为数据域,存储直接后继存储位置的域称为指针域,存储着指针或链,这就把每个结点链接起来所以叫链表。
1.2链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
- 单向或双向
- 带头或不带头
- 循环或非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2.带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
1.3单链表的实现
1.3.1接口实现
typedef int SLDataType;
typedef struct SLNODE{
SLDataType data;
struct SLNODE* next;
}SListNode;
void SListPopFront(SListNode* pphead);
//打印
void SListNodePrint(SListNode* phead);
//头插
void SListNodePushFront(SListNode** pphead, SLDataType x);
//动态申请一个结点
SListNode* NewSListNode();
//尾插
void SListNodePushBack(SListNode** pphead, SLDataType x);
//头删
void SListNodePopFront(SListNode** pphead);
//尾删
void SListNodePopBack(SListNode** pphead);
// 单链表查找
SListNode* SListNodeFind(SListNode* phead, SLDataType x);
// 单链表在pos位置之后插入x
// 分析思考为什么不在pos位置之前插入?
void SListInsertAfter(SListNode* pos, SLDataType x);
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos);
1.3.2动态申请一个结点
生成新的结点,把结点的数据存在数据域中,结点的指针域置为空。
//动态申请一个结点
SListNode* NewSListNode(SLDataType x){
SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
if (newNode == NULL)
{
perror("malloc fail");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
1.3.3单链表打印
//单链表打印
void SListNodePrint(SListNode* phead){
SListNode* cur = phead;
while (cur != NULL){
printf("%d->",cur->data);
//停止条件
cur = cur->next;
}
printf("NILL\n");
}
1.3.4单链表头插
//头插
void SListNodePushFront(SListNode** pphead ,SLDataType x){
assert(pphead);
SListNode* newNode = NewSListNode(x);
newNode->next = *pphead;
*pphead = newNode;
}
1.3.5单链表尾插
//尾插
void SListNodePushBack(SListNode** pphead, SLDataType x){
assert(pphead);
SListNode* newNode = NewSListNode(x);
if (*pphead == NULL){
*pphead = newNode;
}else{
SListNode* tail = *pphead;
while (tail->next != NULL){
tail = tail->next;
}
tail->next = newNode;
}
}
1.3.6单链表头删
void SListNodePopFront(SListNode** pphead){
assert(pphead);
/*if (*pphead == NULL){
return;
}*/
assert(*pphead);
SListNode* tmp = *pphead;
*pphead = (*pphead)->next;
free(tmp);
tmp = NULL;
}
1.3.7单链表尾删
//尾删
void SListNodePopBack(SListNode** pphead){
assert(pphead);
assert(*pphead);
SListNode* tail = *pphead;
if (tail->next == NULL){
free(*pphead);
*pphead = NULL;
}
else{
while (tail->next->next != NULL){
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
1.3.8单链表查找
// 单链表查找
SListNode* SListNodeFind(SListNode* phead, SLDataType x){
//找到返回地址,找不到返回-1;
while (phead){
if (phead->data == x){
return phead;
}
phead = phead->next;
}
return NULL;
}
1.3.9单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLDataType x){
assert(pos);
SListNode*newNode = NewSListNode(x);
newNode->next = pos->next;
pos->next = newNode;
}
1.3.10单链表在pos位置之前插入x
void SListNodeInsert(SListNode** pphead, SListNode* pos, SLDataType x)
{
assert(pphead);
assert(pos);
if (pos == *pphead)
{
SListNodePushFront(pphead, x);
}
else
{
SListNode* prev = *pphead;
while (prev->next != pos)
{
prev = prev->next;
// 暴力检查,pos不在链表中.prev为空,还没有找到pos,说明pos传错了
assert(prev);
}
SListNode* newnode = NewSListNode(x);
prev->next = newnode;
newnode->next = pos;
}
}
1.3.11单链表删除pos位置之后的值
// 单链表删除pos位置之后的值
// 分析思考为什么不删除pos位置?
void SListEraseAfter(SListNode* pos){
assert(pos);
if (pos->next == NULL){
return;
}
else{
SListNode* next = pos->next;
pos->next = next->next;
free(next);
}
}
总结
单链表在头插和头删中有一定的优势,而对于尾插来讲,以及需要遍历寻找到最后一个结点,这是缺点,他没有完全的解决顺序表中的缺点。
如果我们删除pos原位置的结点呢?(不去找该结点的上一个结点)
我们可以找到pos结点的下一个结点,数据域进行交换然后,删除pos位置的后一个结点相对容易
这样我们的目的也就实现了,如果在pos位置前面插入,可以直接插入该结点后面,数据域进行交换;
单链表还又很多缺陷,但为什么还要学习,因为在后面单链表可能作为某一结构的子结构,OJ一般也出这种类型题目,所以重视链表。
最后,我把单链表的简单实现的源码放到了我的Gitee上面了,作为参考。