初识链表(无头单向非循环链表的增删查改)
Posted *insist
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初识链表(无头单向非循环链表的增删查改)相关的知识,希望对你有一定的参考价值。
目录
链表相对于顺序表的优点是 操作数据时不需要大规模地移动原有的数据
链表相对于顺序表的优点是 操作数据时不需要大规模地移动原有的数据
1. 中间/头部的插入删除,时间复杂度为O(N)
2.
增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3.
增容一般是呈
2
倍的增长,势必会有一定的空间浪费。例如当前容量为
100
,满了以后增容到
200
,我们 再继续插入了5
个数据,后面没有数据插入了,那么就浪费了
95
个数据空间。
如何解决以上问题呢?下面给出了链表的结构来看看。
链表概念:链表是一种
物理存储结构上非连续
、非顺序的存储结构,数据元素的
逻辑顺序
是通过链表中的
指针链
接
次序实现的
本文介绍的就是链表中的最简单的无头单向非循环链表 后期在探讨其他类型的
无头单向非循环链表:
结构简单
,一般不会单独用来存数据。实际中更多是作为
其他数据结构的子结
构
,如哈希桶、图的邻接表等等。另外这种结构在
笔试面试
中出现很多。
先大致看一下整体的代码 好利于下面的阅读和理解
SListNode.h(头文件的引用,结构体的定义)
SListNode.c(各个函数的定义)
test.c(测试各个函数的功能)
打印函数
void SListPrint(SLTNode* plist)
while (plist != NULL)//只要结点不是空就打印其对应的值 链表尾是空
printf("%d->", plist->data);
plist = plist->next;
printf("NULL\\n");
插入数据时开辟空间的函数
SLTNode* BuySList(val)
SLTNode* node= (SLTNode*)malloc(sizeof(SLTNode));
node->data = val;
node->next = NULL;
return node;
一、增(头插 尾插 指定位置前或后插入)
插入数据时要考虑几种的情况:1.链表为空 2.链表中只有一个数据
1、头插
void SListPushFront(SLTNode** plist, SLTDataType val)
SLTNode* node = BuySList(val);//创建一块空间 把数据存入该空间 再把指向该空间的地址与链表相连
node->next = *plist;
*plist = node;//这里要改变*plist 的值 传参时就要传*plist 的地址 所以形参是二级指针用来接受一级指针的地址(*plist是结构体指针)
2.头删
void SListPopFront(SLTNode** plist)
if (*plist == NULL)
printf("SLTNode is NULL\\n");
return;//考虑链表是否为空 是空就无数据可删 返回
else
/*SLTNode* node= *plist;
node = node->next;*/
//(*plist) = (*plist)->next;
SLTNode* node = (*plist)->next;//保存下一个结点的地址
free(*plist);//既然是头删就要释放第一个结点的地址指向的那块空间
*plist = NULL;//释放后要置空 防止内存泄漏
*plist = node;//让保存的下一个结点的地址当新地址即新的头 把头删了要有新的头
3、尾插
void SListPushBack(SLTNode** plist,SLTDataType val)
SLTNode* newnode = BuySList(val);//既然是要插入数据 那开始就为其开辟一块空间 并且保存其地址 后面直接连在链表上即可
if (*plist == NULL)
*plist = newnode;//考虑链表头是空的情况 这是就直接把开辟的新空间的地址当作头即可
else
SLTNode* tail = *plist;//如果不是头为空的情况就要去找链表的尾了 找到尾巴后在其后面把开始开辟的空空间连在后边 就是尾插了
while (tail->next != NULL)
tail = tail ->next;//这里要注意加上括号 因为* 和-> 都是解引用 同级 要用括号括起来
//(*plist)->next=newnode;
tail->next = newnode;//连接
4、尾删
void SListPopBack(SLTNode** plist)
//考虑三种情况
//1.链表为空
if (*plist == NULL)
printf("SLTNode is NULL!\\n");
return;
//2.链表只有一个元素(结点)
else if ((*plist)->next == NULL)
free(*plist);
*plist = NULL;
//3.链表有多个元素
else
SLTNode* node = NULL;
SLTNode* tail = *plist;
while (tail->next != NULL)
node = tail;//尾删要找尾 同时还要让尾上一个结点的next为空 这就找到尾的上一个结点的地址了 这里是先保存一份尾 再让其往后走 形成一前一后的结构
tail = tail->next;
free(tail);//释放尾置空 达到尾删的效果
tail = NULL;
node->next = NULL;//让尾的上一个元素的next为空 因为链表最后得是空的
二、查找数据
SLTNode* SListFound(SLTNode* plist, SLTDataType val)
SLTNode* node = plist;//这里因为不要改变实参 所以传的实参的值 前面涉及到改变实参 所传的是实参的地址
while (node)//让头往后走 遍历链表的每个元素
if (node->data == val)//判断node指向的数据是不是我们要查找的值 如果是就返回其地主 不是的话就让node往后走 直到尾
return node;
else
node = node->next;
return NULL;//如果上面都没有返回地址就会往下执行 那么就是没有找到就返回空指针
根据返回值打印要查找的数据
三、删除指定数据与删除指定数据前或后端的数据
1.删除指定数据
void SListErasecur(SLTNode** plist, SLTDataType val)//删除指定的数据
SLTNode* node = *plist;//拷贝一份头地址 防止改变链表头
SLTNode* prev = NULL;//定义一个前驱指针 形成一前一后地址的结构 好让链表能删除数据后还是连在一起的
if (*plist == NULL)
printf("the SLTNode is already NULL!\\n");
return;
else
SLTNode* node = *plist;
while (node->data != val)
prev = node;
node = node->next;
//找到给定的数据的地址和其上一个元素的地址
if (prev == NULL)//如果prev是空 那么就是第一个就是我们要删的数据 就是相当于头删了
SLTNode* node1 = (*plist)->next;
free(*plist);
*plist = NULL;
*plist = node1;
else//否则就让指定数据的前一块空间的next指向指定数据后面的那一块空间 释放给定的数据的空间
prev->next = prev->next->next;
free(node);
node = NULL;
2.删除指定数据后边的数据
void SListEraseAfter(SLTNode** plist, SLTDataType val)
SLTNode* node = *plist;//拷贝头
if (*plist == NULL)//如果为空链表
printf("NLTNode is NULL\\n");
return;
else if ((*plist)->next == NULL)//如果只有一个数据 无法实现
printf("error!\\n");
return;
else
node = SListFound(*plist, val);//调用上面的查找函数 找我们给定的那个数据的位置 再去对其后面的数据进行删除
node->next = node->next->next;//跳过给定数据后面的元素 调整指向顺序
free(node->next);//释放要删数据的空间
node->next = NULL;
3.删除指定元素前面的那个元素
思路: 转变一下思想直接交换给定元素和其前一个元素的值 在释放给定元素的空间可以达到相同效果
void SListEraseBefore(SLTNode** plist, SLTDataType val)
if (*plist == NULL)
printf("SLTNode is already NULL!\\n");
return;
else if ((*plist)->next == NULL)
*plist = NULL;
else
SLTNode* prev = NULL;
SLTNode* node = *plist;
while (node->data != val)
prev = node;
node = node->next;
if (prev == NULL)
printf("error! \\n");
return;
else
/**plist = prev->next;
free(prev);
prev = NULL;*/
SLTNode* swapnode=(SLTNode*)malloc(sizeof(SLTNode));
swapnode->data = 0;
swapnode->data = prev->data;
prev->data = node->data;
node->data =swapnode->data;
prev->next = prev->next -> next;
free(node);
node = NULL;
四、指定数据前或后插入数据
1.指定数据后插入新数据
void SListInsertAfter(SLTNode** plist,SLTDataType des, SLTDataType val)
SLTNode* newnode = BuySList(val);
SLTNode* desnode=SListFound(*plist, des);
if (desnode == NULL)
printf("not find!\\n");
return;
else
newnode->next = desnode->next;
desnode->next = newnode;
2.指定数据前插入数据
思路:常规的思路是去找指定位置的元素地址 弄个前驱指针 找到指定元素上一个元素的地址将其删除 但是删了后要让被删元素的上一个元素的next指向我们给定的数据的地址 但是这个不好找 很麻烦 所以转变思路 直接在给定数据后插入新数据 再交换两者的位置即可实现
void SListInsertBefore(SLTNode** plist, SLTDataType des, SLTDataType val)
SLTNode* newnode=BuySList(val);
SLTNode* desnode = SListFound(*plist, des);
SLTNode* swapnode = (SLTNode*)malloc(sizeof(SLTNode));
swapnode->data = 0;
if (*plist == NULL)
*plist = newnode;
else
newnode->next = desnode->next;
desnode->next = newnode;
swapnode->data = desnode->data;
desnode->data = newnode->data;
newnode->data = swapnode->data;
/*swapnode = desnode;
desnode= newnode;
newnode= swapnode;*/
五、改
这个就比较简单了 找到给定元素地址 将其指向的数据改变即可
void SListModify(SLTNode** plist, SLTDataType des, SLTDataType val)
SLTNode* node = SListFound(*plist,des);
if (node == NULL)
printf("error!\\n");
return;
else
node->data = val;
最后给上代码运行图
创作不易 点赞支持一下吧! 若有不足 多加指点 互相交流呀~
以上是关于初识链表(无头单向非循环链表的增删查改)的主要内容,如果未能解决你的问题,请参考以下文章
数据结构入门带头双向循环链表(List)详解(初始化增删查改)