数据结构 单链表的简单理解和基本操作
Posted 林慢慢i
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构 单链表的简单理解和基本操作相关的知识,希望对你有一定的参考价值。
前言:本章主要内容是数据结构中的单链表。
文章目录
1.为什么需要链表?
1.1顺序表的缺陷
1.动态增容有性能消耗
2.需要头部插入数据,需要挪动数据
鉴于以上两点缺陷,我们引入链表。
链表的概念及结构概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
1.2链表逻辑结构如下:
注释:链表结点进行了分层,上层用于存储数据 ; 下层用指针指向下一个结点,以达到连接目的
1.3物理结构如下:
(下图仅为了举个例子,在堆中物理结构未必线性连续)
1.4顺序表和单链表物理结构的对比:
1.顺序表是利用
realloc
一次性动态调整出一大块空间,所以这一大块空间中的每个单元,地址都是连续的。2.单链表每一次都是用的
malloc
开辟的一个空间,那么每个空间的地址一定是不一样且不连续的。
2.单链表的代码实现
程序名 | 功能 |
---|---|
SList.h | 创建单链表,完成一些函数的声明、结构的定义、头文件引用等 |
SList.c | 实现单链表各个函数的定义 |
test.c | 测试单链表所需函数是否正确 |
2.1 定义单链表
typedef int SLTDataType; //方便以后修改数据类型
struct SListNode
{
SLTDatType data;
struct SListNode* next;
}SLTNode; //把结构体名改短一点
思考下为何使用typedef?
如果一开始我们就确定了结构体中的变量类型,后续在项目过程中如果需要对这个变量类型进行调整,那么所需的操作是很繁琐的。故使用typedef,后续若是需要修改,改动typedef就足够了。
2.2 单链表的空间开辟
SLTNode* BuySLTNode(SLTDatType elem)
{
SLTNode* newnode = (SLTNode* )malloc(sizeof(SLTNode));//记得引头文件
if(newnode == NULL)
{
perror("错误原因:");
exit(-1);
}
newnode->data = elem;
newnode->next = NULL;
return newnode;
}
2.3 单链表的尾插
完成尾插前先回忆下单链表的结构:
phead
(头指针)指向头结点(第一个结点),之后的每个结点的下层指针next
指向下一个结点,其中尾结点的next
为空.
实现思路:
1.遍历找到最后一个结点(即其next为空)
2.malloc动态开辟一个空间存储数据,然后把新开辟的空间的
next
置为空.3.使用尾结点的
next
连接新开辟的空间
错误程序:
void SListPushBack(SLTNode* phead,SLTDataType elem)
{
//第一步:找尾结点, 即cur->next 等于 NULL
SLTNode* cur = phead;
while(cur->next != NULL) //cur用于迭代
{
cur = cur->next;
}
//第二步:开辟新空间
SLTNode* newnode = BuySLTNode(elem);
//第三步:连接
cur->next = newnode;
}
上述尾插代码有错误,思考下哪里出现bug?
1.phead未进行判空,当链表为空时,phead就为空,会引发异常。
2.函数传参错误,
plist
的类型为SLTNode *
,而我们形参类型也是SLTNode *
,这属于值传递,值传递相当于 形参是实参的一份临时拷贝,形参的改变并不会影响实参的值。想要修改实参的值就需要进行传址操作,在这里传plist
的地址.形参用二级指针。
修改后程序
void SListPushBack(SLTNode** pphead, SLTDataType elem)
{
assert(pphead); //pphead不可以为空指针.
if (*pphead == NULL)
{
*pphead = BuySLTNode(elem);
}
else
{
//第一步:找尾结点, 即cur->next 等于 NULL
SLTNode* cur = *pphead;
while (cur->next != NULL) //cur用于迭代
{
cur = cur->next;
}
//第二步:开辟新空间
SLTNode* newnode = BuySLTNode(elem);
//第三步:连接
cur->next = newnode;
}
}
2.4单链表的头插
函数需要改变phead的值,所以我们的形参需要二级指针。
1.创建新节点
2.新节点链接原来的头结点
3.让phead指针指向新节点
void SListPushFront(SLTNode** pphead,SLTDataType elem)
{
assert(pphead);
//第一步,创建新节点
SLTNode* newnode = BuySLTNode(elem);
//第二步,新结点链接原来的头结点
newnode->next = *pphead;
//第三步,phead指针指向新节点
*pphead = newnode;
}
2.5单链表的数据打印
直接用for遍历
void SListPrint(SLTNode* phead)
{
SLTNode* cur = phead;
while(cur->next != NULL)
{
printf("%d->",cur->data);
}
printf("NULL\\n");
}
2.6 单链表的尾删
1.考虑正常情况,如下图。
2.考虑结点数量只有0或1时。
1.从头开始遍历,找到倒数第二个结点。
2.free掉最后一个结点
3.将原本的倒数第二个结点的next释放掉
void SListPopBack(SLTNode** pphead)
{
assert(pphead); //结点数0
assert(*pphead); //结点数1
if((*pphead)->next == NULL)//只有一个结点时
{
free(*pphead);
*pphead = NULL;
return;
}
//多节点,第一步,找倒数第二个结点
SLTNode* cur = *pphead;
while (cur->next->next != NULL)
{
cur = cur->next;
}
//第二步,free掉最后一个结点
free(cur->next);
//第三步,将现结点释放掉,置NULL
cur->next = NULL;
}
2.7 单链表的头删
1.先把第二个结点的地址记下来。
2.释放第一个结点。
3.phead链接到原来的第二个结点
void SListPopFront(SLTNode** pphead)
{
assert(pphead);
//0结点
assert(*pphead);
//1结点
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
//多结点,第一步,保留第二个结点地址
SLTNode* next = (*pphead)->next;
//第二步,释放第一个结点
free(*pphead);
//第三步,连接第二个
*pphead = next;
}
2.8 单链表的长度查找
这个函数没有修改phead,所以选择值传递。
int SListSize(SLTNode* phead)
{
SLTNode* cur = phead;
int size = 0;
while(cur->next != NULL)
{
size++;
cur = cur->next;
};
return size;
}
2.9 单链表的判空操作
bool SListEmpty(SLTNode* phead)
{
return phead == NULL;
}
2.10 单链表的值查找操作
如果可以找到,就返回那个结点,如果找不到,返回空指针。
SLTNode* SListFind(SLTNode* phead, SLTDataType elem)
{
SLTNode* cur = phead;
while(cur->data != elem)
{
cur = cur->next;
}
if(cur->data==elem)
{
return cur;
}
return NULL;
}
2.11 单链表的删除操作
1.找到目标结点之前位置
2.提前保存目标结点后位置
3.销毁目标结点
4.链接原目标结点之前的位置与原目标结点之后的位置
void SListErase(SLTNode** pphead,SLTNode* pos)
{
assert(pphead);
//0结点情况
assert(*pphead);
//1结点时.相当于头删,直接调用头删.
if((*pphead)->next == NULL)
{
SListPopFront(pphead);
}
else
{
SLTNode* cur = *pphead;
while(cur->next != pos)
{
cur = cur->next;
}
SLTNode* two_next = pos->next;
free(pos);
cur->next = two_next;
}
}
2.12 单链表的插入操作
1.找到目标结点之前结点
2.创建新结点
3.新结点链接目标结点
4.原目标结点之前的结点链接新结点
void SListInsert(SLTNode** pphead,SLTNode* pos,SLTDataType elem)
{
assert(pphead);
assert(pos);
//当第一个结点便是目标结点,其实就是头查
if (*pphead== pos)
{
SListPushFront(pphead,elem);
}
//当多个结点时
else
{
SLTNode* pre = *pphead;
while (pre->next != pos)
{
pre = pre->next;
}
SLTNode* next = BuySLTNode(elem);//创建准备插入的结点
next->next = pos;
pre->next = next;
}
}
2.13 单链表的销毁操作
遍历free销毁
void SListDestory(SLTNode** pphead)
{
assert(pphead);
SLTNode* cur = *pphead;
while (cur)
{
SLTNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
3.源码链接
https://gitee.com/linkylo/c_code_2021/tree/master/c_code_2021_8_12
数据结构的单链表内容到此设计结束了,感谢您的阅读!!!如果内容对你有帮助的话,记得给我三连(点赞、收藏、关注)——做个手有余香的人。
以上是关于数据结构 单链表的简单理解和基本操作的主要内容,如果未能解决你的问题,请参考以下文章