数据结构单链表的增删查改,附代码+笔记gitee自取
Posted 凛音Rinne
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构单链表的增删查改,附代码+笔记gitee自取相关的知识,希望对你有一定的参考价值。
单链表
文章目录
导言
-
虽然顺序表一定程度上解决了定长数组带来的
空间浪费
-
但是在我们约定顺序表以
2倍的形式扩容
的时候,还是会有些空间上的浪
费 -
每次头部插入和尾部插入都需要
移动数据,效率低下
那么数据结构中还有一种存储结构,叫做链表
,可以让我们充分利用空间
本文重点介绍常见链表的一种——单链表
一、链表的概念及其结构
1. 概念
链表是一种物理存储结构上非连续、非顺序
的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
2. 结构特点
上一个结构体有下一个结构体的地址,最后指向null
这样一个结构,数据不用连续存储,只要有头部位置的地址
就可以找到所有的数据
二、单链表的增删查改
1. 单链表主体
每个主体都是一个结构体,上面放数据,下面放下一个指向下一个结构体的地址
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SLTNode;
2. 单链表申请节点
无论实现增删的时候都需要有需要一个链表结构,首先我们得拥有一个节点
// 动态申请一个节点,此节点为新节点
SLTNode* BuySListNode(SLTDateType x)
{
SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
if (newnode == NULL)
{
printf("单链表开辟空间失败\\n");
exit(-1);
}
newnode->data = x;//存的数据
newnode->next = NULL;//最后一个节点的标志
return newnode;
3. 单链表打印
直到该节点指针指向下一节点的地址为NULL
停止
// 单链表打印
void SListPrint(SLTNode* plist)
{
while(plist != NULL)
{
printf("%d->", plist->data);
plist = plist->next;
}
printf("NULL\\n");
}
4. 单链表尾插
新建一个节点,分为两种情况
考虑:
链表不存在
,将新建的节点地址为链表地址链表存在
,找到最后一个节点,将节点指向的NULL,改为指向新节点地址
// 单链表尾插
void SListPushBack(SLTNode** pplist, SLTDateType x)
{
SLTNode* newnode = BuySListNode(x);//先新定义一个尾节点,将数字也插进去
//此数据待链接
//尾部节点
SLTNode* tail = *pplist;
if (tail == NULL)//需要链接的节点为空
{
*pplist = newnode;//新的空间就是第一个节点地址就是newnode无需链接
//这里不能写tail因为是局部变量,改变其数据不能改变本身
}
else
{
while (tail->next != NULL)//找到最后一个尾节点
{
tail = tail->next;
}
tail->next = newnode;
}
}
测试一下我们的打印和申请节点和尾插
void test1()//测试尾插+打印
{
SLTNode* s = NULL;
//不申请直接插入
SListPushBack(&s, 1);
//尾插
SListPushBack(&s, 2);
SListPushBack(&s, 3);
SListPushBack(&s, 4);
//打印
SListPrint(s);
}
int main()
{
test1();//尾插测试
return 0;
}
测试结果:符合预期
5. 单链表头插
头插也是先考虑两种情况
链表不存在
,将新建的节点地址为链表地址链表存在
,新节点指向的NULL,改为头节点地址,再将头节点地址改为新节点地址
// 单链表的头插
void SListPushFront(SLTNode** pplist, SLTDateType x)
{
SLTNode* newnode = BuySListNode(x);//新的节点
//当链表为空的时候
if (*pplist == NULL)
{
*pplist = newnode;
}
else
{
newnode->next = *pplist;
*pplist = newnode;
}
}
代码改进:
其实后来发现,两个代码其实可以合并为一个
,就算是空也没有关系
简化代码:
// 单链表的头插
void SListPushFront(SLTNode** pplist, SLTDateType x)
{
SLTNode* newnode = BuySListNode(x);//新的节点
newnode->next = *pplist;
*pplist = newnode;
}
测试代码:
void test2()//测试头插+打印
{
SLTNode* s = NULL;
//头插
SListPushFront(&s, 1);
SListPushFront(&s, 2);
SListPushFront(&s, 3);
SListPushFront(&s, 4);
//打印
SListPrint(s);
}
int main()
{
test2();//头插测试
return 0;
}
调试效果:
6. 单链表尾删
删除单链表最后一个元素需要考虑3种情况
:
大概分为可以删和不能删2种情况,可以删还分2种
- 单链表为空
- 单链表还剩最后一个数
- 常规单链表
图解
代码实现:
// 单链表的尾删
void SListPopBack(SLTNode** pplist)
{
assert(*pplist);
SLTNode* tail = *pplist;
if (tail->next == NULL)
{
free(*pplist);
*pplist = NULL;
}
else
{
while (tail->next->next)
{
tail = tail->next;
}
free(tail->next);
tail->next = NULL;
}
}
测试代码:
void test3()//测试尾删+打印
{
SLTNode* s = NULL;
//头插
SListPushFront(&s, 1);
SListPushFront(&s, 2);
//尾删
SListPopBack(&s);
SListPopBack(&s);
//打印
SListPrint(s);
}
int main()
{
test3();//尾删测试
return 0;
}
测试结果
7. 单链表头删
头删其实和头插类似,也是需要分3种情况,但其中两种情况可以合并,所以最终是2种情况:
- 删不了的情况
- 删的了的情况
图解:
合并出代码:
// 单链表头删
void SListPopFront(SLTNode** pplist)
{
assert(*pplist);
SLTNode* head = *pplist;
head = head->next;
free(*pplist);
*pplist = head;
}
在尾删测试代码上做一下修改
void test4()//测试头删+打印
{
SLTNode* s = NULL;
//头插
SListPushFront(&s, 1);
SListPushFront(&s, 2);
//尾删
SListPopFront(&s);
SListPopFront(&s);
//打印
SListPrint(s);
}
int main()
{
test4();//头删测试
return 0;
}
测试结果:
8. 单链表查找
单链表查找和尾删有点像,思路上是分两种情况:
- 链表为空
- 常规链表查找
注意不是找到就可以了,要返回该数字所在节点的地址
// 单链表查找
SLTNode* SListFind(SLTNode* plist, SLTDateType x)
{
while (plist)
{
if (plist->data == x)
{
printf("找到了!\\n");
return plist;
}
plist = plist->next;
}
return NULL;//没有找到
}
测试代码
void test5()//测试查找
{
SLTNode* s = NULL;
//头插
SListPushFront(&s, 1);
SListPushFront(&s, 2);
//查找
s = SListFind(s, 1);
s = SListFind(s, 3);
//打印
SListPrint(s);
}
int main()
{
test5();//查找测试
return 0;
}
测试结果
一个找到了,一个没找到
9. 单链表在pos之后插入
也是两种情况:
- 链表为空
- 常规链表插入
//在pos之后插入
void SListInsertAfter(SLTNode* pos, SLTDateType x)
{
assert(pos);
SLTNode* newnode = BuySListNode(x);
newnode->next = pos->next;
pos->next = newnode;
}
测试代码:
void test6()//测试查找pos后插入
{
SLTNode* s = NULL;
//头插
SListPushFront(&s, 1);
SListPushFront(&s, 2);
//查找
SLTNode* pos = SListFind(s, 2);
//插入
SListInsertAfter(pos, 11);
//打印
SListPrint(s);
}
int main()
{
test6();//pos后插入测试
return 0;
}
测试结果:
10. 单链表在pos之前插入
考虑情况:
- 头插
- 中间插
- 尾插
//在pos之前插入
void SListInsert(SLTNode** pplist, SLTNode* pos, SLTDateType x)
{
assert(pos && (*pplist));
SLTNode* newnode = BuySListNode(x);
SLTNode* tail = *pplist;
if (*pplist == pos)
{
newnode->next = tail;
*pplist = newnode;
}
else
{
while (tail->next != pos)
{
tail = tail->next;
}
newnode->next = tail->next;
tail->next = newnode;
}
}
测试代码:
void test7()//pos前插入测试
{
SLTNode* s = NULL;
//头插
SListPushFront(&s, 1);
SListPushFront(&s, 2);
//查找
SLTNode* pos = SListFind(s, 1);
//插入
SListInsert(&s, pos, 11);
//打印
SListPrint(s);
}
int main()
{
test7();//pos前插入测试
return 0;
}
测试结果:
11. 单链表在pos之后删除
需要考虑情况:
- 链表为空
- 链表的pos之后为空
- 常规
// 单链表删除pos位置之后的值
void SListEraseAfter(SLTNode* pos)
{
assert(pos);
assert(pos->next);
SLTNode* tag = pos->next;
pos->next = pos->next->next;
free(tag);
//因为tag是局部变量,函数结束后会销毁,所以就不需要令它为NULL
}
测试代码:
test7()
{
SLTNode* s = NULL;
//头插
SListPushFront(&s, 1);
SListPushFront(&s, 2);
//查找
SLTNode* pos = SListFind(s, 2);
//删除
SListEraseAfter(pos);
//打印
SListPrint(s);
}
int main()
{
test7();//pos后删
return 0;
}
测试结果
12. 单链表在pos位子值删除
考虑因素:
- 链表为空
- pos在首位
- pos在中间
// 单链表删除pos位置的值
void SListErase(SLTNode** pplist, SLTNode* pos)
{
assert(pos && *pplist);
if (pos == *pplist)
{
SListPopFront(pplist);//头删
}
else
{
SLTNode* tail = *pplist;
while (tail->next != pos)
{
tail = tail->next;
}
tail->next = pos->next;
free(pos);
}
}
测试
test8()
{
SLTNode* s = NULL;
//头插
SListPushFront(&s, 1);
SListPushFront(&s, 2);
//查找
SLTNode* pos = SListFind(s, 2);
//删除
SListErase(&s, pos);
//打印
SListPrint(s);
}
int main()
{
test8();//pos位置删除
return 0;
}
结果
13. 销毁单链表
// 单链表的销毁
void SListDestory(SLTNode** plist)
{
assert(plist);
SLTNode* tag = *plist;
while (*plist != NULL)
{
*plist = tag->next;
free(tag);
tag = *plist;
}
}
测试
test9()
{
SLTNode* s = NULL;
//头插
SListPushFront(&s, 1);
SListPushFront(&s, 2);
//销毁单链表
SListDestory(&s);
//打印
SListPrint(s);
}
结果
以上是关于数据结构单链表的增删查改,附代码+笔记gitee自取的主要内容,如果未能解决你的问题,请参考以下文章
数据结构学习笔记(单链表单循环链表带头双向循环链表)的增删查改排序等)