tp钱包官网版app正版|链表
【数据结构】链表(单链表实现+详解+原码)-CSDN博客
>【数据结构】链表(单链表实现+详解+原码)-CSDN博客
【数据结构】链表(单链表实现+详解+原码)
最新推荐文章于 2024-03-04 22:33:40 发布
风继续吹TT
最新推荐文章于 2024-03-04 22:33:40 发布
阅读量2.8w
收藏
186
点赞数
84
分类专栏:
数据结构与算法(C语言)
文章标签:
数据结构
链表
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Edward_Asia/article/details/120876314
版权
数据结构与算法(C语言)
专栏收录该内容
10 篇文章
98 订阅
订阅专栏
目录
前言
链表
链表的概念及结构
链表的分类
1. 单向或者双向
2.带头或不带头
3.循环或非循环
单链表的实现
打印链表
申请结点
尾插
头插
尾删
头删
指定位置后面插入
指定位置删除
查找
销毁链表
完整代码
力扣链表OJ
顺序表和链表的区别
前言
上一章我们学到了顺序表,实现了顺序表的增删查改。
当然也发现了顺序表存在的一些优点与缺陷,我们再来回顾一下:
优点:
支持随机访问,可以通过下标来直接访问。可以排序。
缺点:
中间/头部的插入删除,时间复杂度为O(N)增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。增容一般是呈2倍的增长,势必会有一定的空间浪费。
所以为了弥补这些缺点就有了链表,那么什么是链表呢?
链表
链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。 只根据文字描述还是比较抽象的,直接上图来观察:
图中:2.3.4.5都是结构体,称之为结点,与顺序表不同的是,链表中的每个结点不是只单纯的存一个数据。而是一个结构体,结构体成员包括一个所存的数据,和下一个结点的地址。另外,顺序表中的地址是连续的,而链表中结点的地址是随机分配的。
那链表是怎样运行的呢?
图中的phead指针中存放的是第一个结点的地址,那么根据指着地址我们就可以找到这个结构体,又因为这个结构体中存放了下一个结构体的地址,所以又可以找到第二个结构体,循环往复就可以找到所有的结点,直到存放空地址的结构体。
注:图中的箭头实际上是不存在的,这里只是为了能够方便理解。
注意:
从图中可以看出,链式结构在逻辑上是连续的,但在物理上不一定连续。现实中的结点一般都是从堆上申请出来的。从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。
链表的分类
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
1. 单向或者双向
2.带头或不带头
3.循环或非循环
虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。 2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。
单链表的实现
#pragma once
#include
#include
#include
typedef int SLTDataType;
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;//存放下一个结点的地址
}SListNode;
//打印链表
void SListPrint(SListNode* phead);
//申请结点
SListNode* SListBuyNode(SLTDataType x);
//因为我们是通过一个指针指向该链表的头结点,同时因为在进行插入删除操作时可能改变链表的头结点,所下面的参数需传二级指针
//尾插
void SListPushBack(SListNode** pphead, SLTDataType x);
//头插
void SListPushFront(SListNode** pphead, SLTDataType x);
//尾删
void SListPopBack(SListNode** pphead);
//头删
void SListPopFront(SListNode** pphead);
//查找
SListNode* SListFind(SListNode* phead, SLTDataType x);
//指定位置后面插入
void SListInsert(SListNode** pphead, SListNode* pos, SLTDataType x);
//指定位置后面删除
void SListErase(SListNode** pphead, SListNode* pos);
//销毁链表
void SListDestory(SListNode** pphead);
打印链表
在顺序表中是通过下标来访问每个元素,链表与顺序表不同,在访问到本结点的数据之后,需通过该结点存放的地址找到下一个结点。
//打印链表
void SListPrint(SListNode* phead)
{
SListNode* cur = phead;//一般不直接移动头指针,而是创建一个指针变量来移动
while (cur)//当指针为空时结束循环
{
printf("%d->", cur->data);//打印该结点的数据
cur = cur->next;//将指针指向下一个结点
}
printf("NULL\n");
}
申请结点
链表的每一个结点都是动态开辟(malloc)出来的,每一个结点的大小为该结构体的大小。
开辟成功后,要将结点存放的数据置为需要存放的值,结点存放的地址置为NULL。
//申请结点
SListNode* SListBuyNode(SLTDataType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)//判断结点是否开辟成功
{
perror("malloc:");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;//返回结点地址
}
尾插
因为不能根据下标访问元素(即不能随机访问),当然我们也不知到最后一个结点的位置,所在尾插时,需要遍历找到最后一个结点的位置。
同时这里分为两种情况:
如果链表为空,则直接插入即可。如果链表不为空,则需要找到尾结点再插入。
//尾插
void SListPushBack(SListNode** pphead, SLTDataType x)
{
assert(pphead);
SListNode* newnode = SListBuyNode(x);//申请结点
if (*pphead == NULL)//1.链表为空
{
*pphead = newnode;//直接将头结点置为需要插入的结点
}
else
{
SListNode* cur = *pphead;
while (cur->next)//找尾结点
{
cur = cur->next;
}
cur->next = newnode;//将尾结点中存放的地址置为插入结点的地址即可
}
}
头插
头插相对来说比较简单,直接将申请结点的next置为头结点,然后将头结点改成申请结点即可
注:这里不需要考虑链表是否为空。
//头插
void SListPushFront(SListNode** pphead, SLTDataType x)
{
assert(pphead);
SListNode* newnode = SListBuyNode(x);
newnode->next = *pphead;//将申请结点中保存的地址置为头结点的地址
*pphead = newnode;//再将头结点向右移动
}
尾删
同尾插一样,我们也不知道尾结点的地址,所以需要先找到尾结点。
同时这里需要考虑三种情况:
链表为空。链表中只有一个结点。链表中有一个以上结点。
//尾删
void SListPopBack(SListNode** pphead)
{
//1.链表为空不能删除结点,且该指针不能为空
assert(*pphead && phead);
//2.链表中只有一个结点,直接释放该结点,然后将结点置为NULL
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
//3.链表中有一个以上结点,先找尾结点,释放掉尾结点,置为NULL
// 但这样还不够,因为倒数第二个结点还存有尾结点的地址,所以需要将他置为NULL
SListNode* cur = *pphead;//用来标记倒数第二个结点
SListNode* next = (*pphead)->next;//标记尾结点
while (next->next)
{
next = next->next;
cur = cur->next;
}
cur->next = NULL;//将倒数第二个结点中存的地址置为NULL
free(next);//释放尾结点
next = NULL;
}
头删
头删也比较简单,相当于将头指针移动到第二个结点上。
这里分为两种情况:
链表为空。链表不为空。
//头删
void SListPopFront(SListNode** pphead)
{
assert(*pphead && phead);//链表为空不能删除
SListNode* next = (*pphead)->next;//记录第二个结点的地址
free(*pphead);//释放头结点
*pphead = next;//将指针指向第二个结点
}
指定位置后面插入
这里在指定位置后面插入而不在前面插入是因为,在前面插入时需要找到插入位置前面的地址,而这样又会遍历一次链表,时间复杂度为O(N),而在后面插入则直接插入即可,时间复杂度为O(1)。
//指定位置后面插入
void SListInsert(SListNode** pphead, SListNode* pos, SLTDataType x)
{
assert(pphead && pos);
SListNode* newnode = SListBuyNode(x);//申请结点
SListNode* next = pos->next;//找到插入位置的下一个结点的地址
pos->next = newnode;//插入结点
newnode->next = next;//连接到后面的链表
}
// 在pos位置之前去插入一个节点
void SListInsert(SLTNode** pphead, ListNode* pos, SLTDateType x)
{
assert(pphead);
assert(pos);
ListNode* newnode = BuyListNode(x);
if (*pphead == pos)
{
newnode->next = *pphead;
*pphead = newnode;
}
else
{
// 找到pos的前一个位置
ListNode* posPrev = *pphead;
while (posPrev->next != pos)
{
posPrev = posPrev->next;
}
posPrev->next = newnode;
newnode->next = pos;
}
}
指定位置删除
这里在指定位置删除,而不在前面删除或者后面删除,是因为在头删和尾删时会遇到一些麻烦。
//指定位置删除
void SListErase(SListNode** pphead, SListNode* pos)
{
assert(pphead && pos);
if (*pphead == pos)//如果头结点是要删除的结点
{
*pphead = (*pphead)->next;
free(pos);
pos = NULL;
}
else
{
SListNode* cur = *pphead;
while (cur->next != pos)//找到要删除的结点
{
cur = cur->next;
}
cur->next = pos->next;//将需要删除的结点的上一个结点的next指向需要删除的下一个结点
free(pos);
pos = NULL;
}
}
查找
根据提供的数据,在链表中遍历每个结点,若某个结点中的数据与之相同则返回该结点的地址;若没有找到则返回NULL。
//查找
SListNode* SListFind(SListNode* phead, SLTDataType x)
{
while (phead)
{
if (phead->data == x)
{
return phead;
}
phead = phead->next;
}
return NULL;
}
销毁链表
保存下一个结点的地址,释放当前结点,再将指针指向下一个结点,释放下一个结点,直到链表为空。
//销毁链表
void SListDestory(SListNode** pphead)
{
assert(pphead);
while (*pphead)
{
SListNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
}
完整代码
//打印链表
void SListPrint(SListNode* phead)
{
SListNode* cur = phead;
while (cur)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
//申请结点
SListNode* SListBuyNode(SLTDataType x)
{
SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
if (newnode == NULL)
{
perror("malloc:");
exit(-1);
}
newnode->data = x;
newnode->next = NULL;
return newnode;
}
//销毁链表
void SListDestory(SListNode** pphead)
{
assert(pphead);
SListNode* cur = *pphead;
while (cur)
{
SListNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
//尾插
void SListPushBack(SListNode** pphead, SLTDataType x)
{
assert(pphead);
SListNode* newnode = SListBuyNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SListNode* cur = *pphead;
while (cur->next)
{
cur = cur->next;
}
cur->next = newnode;
}
}
//头插
void SListPushFront(SListNode** pphead, SLTDataType x)
{
assert(pphead);
SListNode* newnode = SListBuyNode(x);
newnode->next = *pphead;
*pphead = newnode;
}
//尾删
void SListPopBack(SListNode** pphead)
{
assert(*pphead && pphead);
if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
return;
}
SListNode* cur = *pphead;
SListNode* next = (*pphead)->next;
while (next->next)
{
next = next->next;
cur = cur->next;
}
cur->next = NULL;
free(next);
next = NULL;
}
//头删
void SListPopFront(SListNode** pphead)
{
assert(*pphead && pphead);
SListNode* next = (*pphead)->next;
free(*pphead);
*pphead = next;
}
//查找
SListNode* SListFind(SListNode* phead, SLTDataType x)
{
while (phead)
{
if (phead->data == x)
{
return phead;
}
phead = phead->next;
}
return NULL;
}
//指定位置后面插入
void SListInsert(SListNode** pphead, SListNode* pos, SLTDataType x)
{
assert(pphead && pos);
SListNode* newnode = SListBuyNode(x);
SListNode* next = pos->next;
pos->next = newnode;
newnode->next = next;
}
//指定位置删除
void SListErase(SListNode** pphead, SListNode* pos)
{
assert(pphead && pos);
if (*pphead == pos)
{
*pphead = (*pphead)->next;
free(pos);
pos = NULL;
}
else
{
SListNode* cur = *pphead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
free(pos);
pos = NULL;
}
}
注:代码中的assert(*pphead)和assert(pphead)含义不相同!!!
assert(*pphead)表示的是链表不为空。
assert(pphead)表示的是参数二级指针不能为空,因为空指针不能解引用!
力扣链表OJ
力扣---移除链表元素
思路:
与单链表的尾删类似,先找到需要删除的元素,将上一个结点的next指向该删除结点的next,
然后free掉当前结点。
当然这里也分为三种情况:
链表为空,返回NULL。需要删除的结点为头结点,直接将头结点释放,将下一个结点作为头结点。需要删除的结点在中,则使用常规方法。
代码如下:
typedef struct ListNode ListNode;//重命名,方便处理
struct ListNode* removeElements(struct ListNode* head, int val){
//1.链表为空,直接返回NULL
if(head == NULL)
{
return NULL;
}
//链表不为空
ListNode*cur =head;//标记需要删除结点
ListNode*prev = NULL;//标记需要删除结点的前一个结点
while(cur)
{
//找到需要删除的结点
if(cur->val==val)
{
//2.如果需要删除的结点为头结点
if(cur == head)
{
//将头指针指向第二个结点
head = head->next;
//释放头结点
free(cur);
cur = head;
}
//3.需要删除的结点在中间
else
{
//将前一个结点的next指向删除结点的next
prev->next = cur->next;
free(cur);
cur = prev->next;
}
}
//不是需要删除结点就向后移动
else
{
prev = cur;
cur = cur->next;
}
}
return head;
}
顺序表和链表的区别
不同点顺序表链表存储空间上物理上一定连续逻辑上连续,但物理上不一定连 续随机访问支持O(1)不支持:O(N)任意位置插入或者删除元 素可能需要搬移元素,效率低O(N)只需修改指针指向插入动态顺序表,空间不够时需要扩 容没有容量的概念应用场景元素高效存储+频繁访问任意位置插入和删除频繁缓存利用率高低
优惠劵
风继续吹TT
关注
关注
84
点赞
踩
186
收藏
觉得还不错?
一键收藏
打赏
知道了
16
评论
【数据结构】链表(单链表实现+详解+原码)
前言上一章我们学到了顺序表,实现了顺序表的增删查改。当然也发现了顺序表存在的一些优点与缺陷,我们再来回顾一下:优点:支持随机访问,可以通过下标来直接访问。 可以排序。缺点:中间/头部的插入删除,时间复杂度为O(N) 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 增容一般是呈2倍的增长,势必会有一定的空间浪费。所以为了弥补这些缺点就有了链表,那么什么是链表呢?链表链表的概念及结构概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是
复制链接
扫一扫
专栏目录
数据结构-单链表的学习及代码实现
09-03
包含单链表的增加、删除、修改,增加分为两种,直接增加到链表的最后和按照编号添加到固定的位置。以及对这些代码的全部实现。
为了自己学习记录,如果里面有不正确的,还请指正
数据结构 合并有序链表 单链表 初学
02-17
合并有序链表
嵌入式技术公开课数据结构笔记
16 条评论
您还未登录,请先
登录
后发表或查看评论
数据结构---单向链表,双向链表,单向环形链表
root1994的博客
07-09
4504
链表介绍
链表是以节点的方式来存储,是链式存储
每个节点包含 data 域, next 域:指向下一个节点.
如图:发现链表的各个节点不一定是连续存储.
链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
修改节点功能
思路(1) 先找到该节点,通过遍历,(2) temp.name = newHeroNode.name ; temp.nickname= newHeroNode.n...
[数据结构]——链表详解
m0_62023005的博客
09-19
3534
数据结构---链表详解
链表详解(易懂)
SlimShadyKe的博客
04-24
6万+
链表是一系列的存储数据元素的单元通过指针串接起来形成的,因此每个单元至少有两个域,一个域用于数据元素的存储,另一个或两个域是指向其他单元的指针。这里具有一个数据域和多个指针域的存储单元通常称为节点(node)。
链表的第一个节点和最后一个节点,分别称为链表的头节点和尾节点。尾节点的特征是其 next 引用为空(null)。链表中每个节点的 next 引用都相当于一个指针,指向另一个节点,借助这些...
数据结构(一)--- 链表
weixin_53072248的博客
08-05
2125
数据结构概述:在计算机中存储和组织数据的方式。算法概述:解决方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。算法复杂度O(1) 常数阶O(log(n)) 对数阶O(n) 线性阶O(nlog(n)) 线性和对数乘积O(n2) 平方阶O(2n) 指数阶既可以从头遍历到尾,又可以从尾遍历到头。也就是说链表连接的过程是双向的,它的实现原理是:一个节点既有向前连接的引用,也有一个向后连接的引用。
【数据结构】链表
knous
03-13
6万+
链表
链表基础知识详解(非常详细简单易懂)
热门推荐
qq_61672347的博客
07-10
14万+
数据结构链表详解,创建,遍历,释放,节点的查找,节点的删除,插入节点,链表排序等操作,非常详细
链表(超详细--包教包会)
Lushengshi的博客
02-20
3019
C\C++ 链表 包教包会
C语言数据结构——链表
zcxmjw的博客
12-22
1万+
大家好,本篇文章主要带领大家了解一下什么是链表,链表的两个主要结构,以及链表和顺序表的差别。
链表(C语言版)超详细讲解
h220321204的博客
08-15
6703
而如果使用头插法,则需要遍历到链表的第一个节点,时间复杂度为O(n),而且尾插法是由数据插入的顺序依次存储的,查找起来会更加方便。1、首先我们定义两个指针(x1,p),x1作为头指针指向头结点(尾插法时不要移动头指针,我们只需移动p即可),p始终指向尾结点(这里我们初始化指向头结点),再申请一块结点并用x指向它。这样我们基础工作完成。
Python实现数据结构线性链表(单链表)算法示例
09-19
主要介绍了Python实现数据结构线性链表(单链表)算法,结合实例形式分析了Python单链表的定义、节点插入、删除、打印等相关操作技巧,需要的朋友可以参考下
浅谈PHP链表数据结构(单链表)
12-18
链表:是一个有序的列表,但是它在内存中是分散存储的,使用链表可以解决类似约瑟夫问题,排序问题,搜索问题,广义表
单向链表,双向链表,环形链表
PHP的底层是C,当一个程序运行时,内存分成五个区(堆区,栈区,全局区,常量区,代码区)
规定:基本数据类型,一般放在栈区
复合数据类型,比如对象,放在堆区
定义一个类Hero
定义成员属性排名 $no
定义成员属性姓名 $name
定义成员属性昵称 $nickname
定义成员属性 $next,是一个引用,指向下一个Hero对象
定义构造函数,传递参数:$no,$name,$nickname
创建一个头head,该head只是一个头,不放入数据
获
链表逆置+算法+详解介绍
09-17
链表逆置是计算机科学中一个重要的算法问题,它涉及到链表数据结构的操作和算法设计。在本文中,我们将详细讨论链表逆置的背景、原理、不同方法以及实际应用。
链表是一种常见的数据结构,用于存储一系列元素,其中每个元素都包含一个值和一个指向下一个元素的指针。链表可以分为单向链表、双向链表和循环链表等不同类型,但在链表逆置问题中,我们通常讨论单向链表。
【数据结构】二、线性表:3.双链表的定义及其基本操作(初始化、头插法尾插法建表、插入、遍历查找、删除、判空等)
weixin_51350847的博客
03-04
804
对于只需要顺序遍历或仅从头部开始操作的情况,单链表可能是更简洁和高效的选择。但对于需要在两个方向上遍历或在任意位置插入或删除节点的情况,双链表就更有优势了。单链表(Singly Linked List)和双链表(Doubly Linked List)是两种常见的链表数据结构,它们在节点之间的连接方式上有所区别。双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式实现,时间复杂度为。注意:要注意代码中结点前驱后继的调整顺序,防止出现自己指向自己的情况。双链表与单链表一样,为了操作方便也可以加入。
【数据结构】二、线性表:2.单链表的建立(尾插法实例、头插法)
weixin_51350847的博客
03-04
367
当插入1个元素时,while需要循环一次,插入2个元素,while循环1此…插入n个元素,while循环n-1次。通过将用户输入的数据逐个添加到链表的尾部,可以方便地保存输入的数据,并在后续处理中使用。头插法建立单链表的特点是:新节点插入到链表的头部位置,因此建立完成的链表元素顺序是和输入数据集合的顺序相反的,即。),指向表尾,当要在尾部插入一个新的数据元素时,就只需要对r结点做一个后插操作就行了。如果要把很多个数据元素存到一个单链表中,如何操作?这种操作,时间复杂度太大,并不是最佳方案。
Google Dremel和parquet的复杂嵌套数据结构表征方法解析
archimekai的博客
03-04
877
转载请注明出处。作者:archimekai核心参考文献: Dremel: Interactive Analysis of Web-Scale Datasets。
python 数据结构
最新发布
JiuMeilove的博客
03-04
544
在Python中,数据结构是一种组织和存储数据的方式,以便更有效地进行数据的访问和修改。Python提供了多种内置的数据结构,如列表(List)、元组(Tuple)、字典(Dictionary)、集合(Set)等。除了这些内置的数据结构,Python还支持更复杂的自定义数据结构,如树(Tree)、图(Graph)、堆(Heap)、队列(Queue)、栈(Stack)等。这些数据结构通常需要使用类(Class)来实现。
【数据结构】实现堆
qq_75000174的博客
03-04
977
数据插入完成后,我们还要保证这是一个小堆,所以要对比这个数据和它的双亲结点的值:如果它的值要大于双亲结点的值,那位置就不要动;如果它的值要小于双亲结点的值,为了让这个堆还是小堆,我们要将它和双亲结点互换位置,直到它的值要大于双亲结点的值或者它已经是根节点。的顺序存储方式存储在一个一维数组中,并满足:任意一个双亲结点的值=孩子节点的值),则称为。因为函数的实参是HP类型变量的地址,不可能为空,所以对它断言,下面的接口同理。如果有一个关键码的集合K,把它的所有元素按。
广工数据结构第二章+双向链表
10-20
广工数据结构第二章主要介绍了线性表的顺序存储和链式存储两种方式,以及它们的实现和应用。其中,链式存储包括单向链表、双向链表和循环链表三种形式。
双向链表是一种常见的链式存储结构,它与单向链表相比,每个节点除了指向后继节点的指针外,还有指向前驱节点的指针。这样可以方便地实现双向遍历和在任意位置插入或删除节点等操作。
在双向链表中,每个节点包含三个部分:数据域、指向前驱节点的指针和指向后继节点的指针。头节点不存储数据,只是为了方便操作而存在。双向链表的插入和删除操作需要修改前驱节点和后继节点的指针,因此需要注意指针的顺序和细节。
双向链表的优点是可以方便地实现双向遍历和在任意位置插入或删除节点等操作,缺点是需要额外的空间存储前驱节点的指针,同时插入和删除操作需要修改两个指针,比较繁琐。
“相关推荐”对你有帮助么?
非常没帮助
没帮助
一般
有帮助
非常有帮助
提交
风继续吹TT
CSDN认证博客专家
CSDN认证企业博客
码龄3年
C/C++领域新星创作者
68
原创
1万+
周排名
183万+
总排名
22万+
访问
等级
4790
积分
7542
粉丝
2432
获赞
1354
评论
6009
收藏
私信
关注
热门文章
【数据结构】八大排序(超详解+附动图+源码)
75130
【数据结构】链表(单链表实现+详解+原码)
28259
【数据结构】顺序表(实现+详解+源码+通讯录项目(静态+动态+文件保存))
16410
【Linux】进程间通信
15379
【C++】继承
8366
分类专栏
C语言系列
20篇
C++
19篇
Linux
18篇
数据结构与算法(C语言)
10篇
剑指offer系列
1篇
最新评论
【Linux】进程间通信
m0_62036180:
是这样的
【Linux】进程间通信
YZ598789:
“管道是一个只能单向通信的通信信道”这个说法不正确吧,应该是半双工的通信信道
【数据结构】八大排序(超详解+附动图+源码)
杨思默:
请问博主这种排序的空间负责度在你代码里是怎么理解的呢
【C++】继承
伱恏呀呀呀呀:
salute
【数据结构】八大排序(超详解+附动图+源码)
小李快跑%s:
这是比特吗?感觉好熟悉啊
最新文章
【C++】C++11 新特性
【C++】C++11 异常
【C++】哈希表
2023年3篇
2022年30篇
2021年35篇
目录
目录
分类专栏
C语言系列
20篇
C++
19篇
Linux
18篇
数据结构与算法(C语言)
10篇
剑指offer系列
1篇
目录
评论 16
被折叠的 条评论
为什么被折叠?
到【灌水乐园】发言
查看更多评论
添加红包
祝福语
请填写红包祝福语或标题
红包数量
个
红包个数最小为10个
红包总金额
元
红包金额最低5元
余额支付
当前余额3.43元
前往充值 >
需支付:10.00元
取消
确定
下一步
知道了
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝
规则
hope_wisdom 发出的红包
打赏作者
风继续吹TT
你的鼓励将是我创作的最大动力
¥1
¥2
¥4
¥6
¥10
¥20
扫码支付:¥1
获取中
扫码支付
您的余额不足,请更换扫码支付或充值
打赏作者
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额
0
抵扣说明:
1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。 2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。
余额充值
一口气搞懂「链表」,就靠这20+张图了 - 知乎
一口气搞懂「链表」,就靠这20+张图了 - 知乎首发于技术让梦想更伟大切换模式写文章登录/注册一口气搞懂「链表」,就靠这20+张图了李肖遥 说真的,任何说起嵌入式软件怎么入门啊?需要学些什么东西啊,我差不多一致的回答都是:C语言和数据结构加上一些简单常用的算法,这些需要学好。 借着自己的回顾学习,我也写一些基础的数据结构知识,多画图,少BB,与大家一起学习数据结构 顺序存储和链式存储数组—顺序存储数组作为一个顺序储存方式的数据结构,可是有大作为的,它的灵活使用为我们的程序设计带来了大量的便利;但是,但是,数组最大的缺点就是我们的插入和删除时需要移动大量的元素,所以呢,大量的消耗时间,以及冗余度难以接受了。以C语言数组插入一个元素为例,当我们需要在一个数组{1,2,3,4}的第1个元素后的位置插入一个’A’时,我们需要做的有:将第1个元素后的整体元素后移,形成新的数组{1,2,2,3,4} 再将第2个元素位置的元素替换为我们所需要的元素’A’ 最终形成我们的预期,这需要很多的操作喔。 上图可以看出,使用数组都有这两大缺点:插入删除操作所需要移动的元素很多,浪费算力。 必须为数组开足够的空间,否则有溢出风险。 链表—链式存储由于数组的这些缺点,自然而然的就产生链表的思想了。链表通过不连续的储存方式,自适应内存大小,以及指针的灵活使用,巧妙的简化了上述的内容。链表的基本思维是,利用结构体的设置,额外开辟出一份内存空间去作指针,它总是指向下一个结点,一个个结点通过NEXT指针相互串联,就形成了链表。 其中DATA为自定义的数据类型,NEXT为指向下一个链表结点的指针,通过访问NEXT,可以引导我们去访问链表的下一个结点。对于一连串的结点而言,就形成了链表如下图:上文所说的插入删除操作只需要修改指针所指向的区域就可以了,不需要进行大量的数据移动操作。如下图:相比起数组,链表解决了数组不方便移动,插入,删除元素的弊端,但相应的,链表付出了更加大的内存牺牲换来的这些功能的实现。链表概述包含单链表,双链表,循环单链表,实际应用中的功能不同,但实现方式都差不多。单链表就像是美国男篮,一代一代往下传; 双链表像是中国男足,你传球给我,我传球给你,最终传给了守门员; 循环链表就像是中国男篮,火炬从姚明传给王治郅,王治郅传给易建联,现在易建联伤了,又传给了姚明 单链表单链表概念和简单的设计单链表是一种链式存取的数据结构,链表中的数据是以结点来表示的,每个结点由元素和指针构成。元素表示数据元素的映象,就是存储数据的存储单元;指针指示出后继元素存储位置,就是连接每个结点的地址数据。以结点的序列表示的线性表称作线性链表,也就是单链表,单链表是链式存取的结构。对于链表的每一个结点,我们使用结构体进行设计,其主要内容有: 其中,DATA数据元素,可以为你想要储存的任何数据格式,可以是数组,可以是int,甚至可以是结构体(这就是传说中的结构体套结构体)NEXT为一个指针,其代表了一个可以指向的区域,通常是用来指向下一个结点,链表的尾部NEXT指向NULL(空),因为尾部没有任何可以指向的空间了故,对于一个单链表的结点定义,可以代码描述成://定义结点类型
typedef struct Node {
int data; //数据类型,你可以把int型的data换成任意数据类型,包括结构体struct等复合类型
struct Node *next; //单链表的指针域
} Node,*LinkedList;
//Node表示结点的类型,LinkedList表示指向Node结点类型的指针类型
链表的初始化初始化主要完成以下工作:创建一个单链表的前置节点并向后逐步添加节点,一般指的是申请结点的空间,同时对一个结点赋空值(NULL),其代码可以表示为:LinkedList listinit(){
Node *L;
L=(Node*)malloc(sizeof(Node)); //开辟空间
if(L==NULL){ //判断是否开辟空间失败,这一步很有必要
printf("申请空间失败");
//exit(0); //开辟空间失败可以考虑直接结束程序
}
L->next=NULL; //指针指向空
}
注意:一定要判断是否开辟空间失败,否则生产中由于未知的情况造成空间开辟失败,仍然在继续执行代码,后果将不堪设想啦,因此养成这样的判断是很有必要的。头插入法创建单链表利用指针指向下一个结点元素的方式进行逐个创建,使用头插入法最终得到的结果是逆序的。如图所示: 从一个空表开始,生成新结点,并将读取到的数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头,即头结点之后。//头插法建立单链表
LinkedList LinkedListCreatH() {
Node *L;
L = (Node *)malloc(sizeof(Node)); //申请头结点空间
L->next = NULL; //初始化一个空链表
int x; //x为链表数据域中的数据
while(scanf("%d",&x) != EOF) {
Node *p;
p = (Node *)malloc(sizeof(Node)); //申请新的结点
p->data = x; //结点数据域赋值
p->next = L->next; //将结点插入到表头L-->|2|-->|1|-->NULL
L->next = p;
}
return L;
}
尾插入法创建单链表如图所示为尾插入法的创建过程。头插法生成的链表中,结点的次序和输入数据的顺序不一致。若希望两者次序一致,则需要尾插法。该方法是将新结点逐个插入到当前链表的表尾上,为此必须增加一个尾指针r, 使其始终指向当前链表的尾结点,代码如下://尾插法建立单链表
LinkedList LinkedListCreatT() {
Node *L;
L = (Node *)malloc(sizeof(Node)); //申请头结点空间
L->next = NULL; //初始化一个空链表
Node *r;
r = L; //r始终指向终端结点,开始时指向头结点
int x; //x为链表数据域中的数据
while(scanf("%d",&x) != EOF) {
Node *p;
p = (Node *)malloc(sizeof(Node)); //申请新的结点
p->data = x; //结点数据域赋值
r->next = p; //将结点插入到表头L-->|1|-->|2|-->NULL
r = p;
}
r->next = NULL;
return L;
}
遍历单链表如打印、修改从链表的头开始,逐步向后进行每一个元素的访问,称为遍历。对于遍历操作,我们可以衍生出很多常用的数据操作,比如查询元素,修改元素,获取元素个数,打印整个链表数据等等。进行遍历的思路极其简单,只需要建立一个指向链表L的结点,然后沿着链表L逐个向后搜索即可,代码如下://便利输出单链表
void printList(LinkedList L){
Node *p=L->next;
int i=0;
while(p){
printf("第%d个元素的值为:%d\n",++i,p->data);
p=p->next;
}
}
对于元素修改操作,以下是代码实现://链表内容的修改,在链表中修改值为x的元素变为为k。
LinkedList LinkedListReplace(LinkedList L,int x,int k) {
Node *p=L->next;
int i=0;
while(p){
if(p->data==x){
p->data=k;
}
p=p->next;
}
return L;
}
简单的遍历设计的函数只需要void无参即可,而当涉及到元素操作时,可以设计一个LinkedList类型的函数,使其返回一个操作后的新链表。插入操作链表的插入操作主要分为查找到第i个位置,将该位置的next指针修改为指向我们新插入的结点,而新插入的结点next指针指向我们i+1个位置的结点。其操作方式可以设置一个前驱结点,利用循环找到第i个位置,再进行插入。如图,在DATA1和DATA2数据结点之中插入一个NEW_DATA数据结点:从原来的链表状态 到新的链表状态:代码实现如下://单链表的插入,在链表的第i个位置插入x的元素
LinkedList LinkedListInsert(LinkedList L,int i,int x) {
Node *pre; //pre为前驱结点
pre = L;
int tempi = 0;
for (tempi = 1; tempi < i; tempi++) {
pre = pre->next; //查找第i个位置的前驱结点
}
Node *p; //插入的结点为p
p = (Node *)malloc(sizeof(Node));
p->data = x;
p->next = pre->next;
pre->next = p;
return L;
}
删除操作删除元素要建立一个前驱结点和一个当前结点,当找到了我们需要删除的数据时,直接使用前驱结点跳过要删除的结点指向要删除结点的后一个结点,再将原有的结点通过free函数释放掉。如图所示: 代码如下://单链表的删除,在链表中删除值为x的元素
LinkedList LinkedListDelete(LinkedList L,int x) {
Node *p,*pre; //pre为前驱结点,p为查找的结点。
p = L->next;
while(p->data != x) { //查找值为x的元素
pre = p;
p = p->next;
}
pre->next = p->next; //删除操作,将其前驱next指向其后继。
free(p);
return L;
}
双向链表双向链表的简介以及概念单链表是指结点中只有一个指向其后继的指针,具有单向性,但是有时需要搜索大量数据的时候,就需要多次进行从头开始的遍历,这样的搜索就不是很高效了。在单链表的基础上,对于每一个结点设计一个前驱结点,前驱结点与前一个结点相互连接,构成一个链表,就产生了双向链表的概念了。双向链表可以简称为双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一个完整的双向链表应该是头结点的pre指针指为空,尾结点的next指针指向空,其余结点前后相链。双向链表的结点设计对于每一个结点而言,有: 其中,DATA表示数据,其可以是简单的类型也可以是复杂的结构体;pre代表的是前驱指针,它总是指向当前结点的前一个结点,如果当前结点是头结点,则pre指针为空;next代表的是后继指针,它总是指向当前结点的下一个结点,如果当前结点是尾结点,则next指针为空其代码设计如下:typedef struct line{
int data; //data
struct line *pre; //pre node
struct line *next; //next node
}line,*a;
//分别表示该结点的前驱(pre),后继(next),以及当前数据(data)
双链表的创建创建双向链表需要先创建头结点,然后逐步的进行添加双向链表的头结点是有数据元素的,也就是头结点的data域中是存有数据的,这与一般的单链表是不同的。对于逐步添加数据,先开辟一段新的内存空间作为新的结点,为这个结点进行的data进行赋值,然后将已成链表的上一个结点的next指针指向自身,自身的pre指针指向上一个结点。其代码可以设计为://创建双链表
line* initLine(line * head){
int number,pos=1,input_data;
//三个变量分别代表结点数量,当前位置,输入的数据
printf("请输入创建结点的大小\n");
scanf("%d",&number);
if(number<1){return NULL;} //输入非法直接结束
//////头结点创建///////
head=(line*)malloc(sizeof(line));
head->pre=NULL;
head->next=NULL;
printf("输入第%d个数据\n",pos++);
scanf("%d",&input_data);
head->data=input_data;
line * list=head;
while (pos<=number) {
line * body=(line*)malloc(sizeof(line));
body->pre=NULL;
body->next=NULL;
printf("输入第%d个数据\n",pos++);
scanf("%d",&input_data);
body->data=input_data;
list->next=body;
body->pre=list;
list=list->next;
}
return head;
}
双向链表创建的过程可以分为:创建头结点->创建一个新的结点->将头结点和新结点相互链接->再度创建新结点,这样会有助于理解。双向链表的插入操作如图所示: 对于每一次的双向链表的插入操作,首先需要创建一个独立的结点,并通过malloc操作开辟相应的空间;其次我们选中这个新创建的独立节点,将其的pre指针指向所需插入位置的前一个结点;同时,其所需插入的前一个结点的next指针修改指向为该新的结点,该新的结点的next指针将会指向一个原本的下一个结点,而修改下一个结点的pre指针为指向新结点自身,这样的一个操作我们称之为双向链表的插入操作。其代码可以表示为://插入数据
line * insertLine(line * head,int data,int add){
//三个参数分别为:进行此操作的双链表,插入的数据,插入的位置
//新建数据域为data的结点
line * temp=(line*)malloc(sizeof(line));
temp->data=data;
temp->pre=NULL;
temp->next=NULL;
//插入到链表头,要特殊考虑
if (add==1) {
temp->next=head;
head->pre=temp;
head=temp;
}else{
line * body=head;
//找到要插入位置的前一个结点
for (int i=1; i body=body->next; } //判断条件为真,说明插入位置为链表尾 if (body->next==NULL) { body->next=temp; temp->pre=body; }else{ body->next->pre=temp; temp->next=body->next; body->next=temp; temp->pre=body; } } return head; } 双向链表的删除操作如图:删除操作的过程是:选择需要删除的结点->选中这个结点的前一个结点->将前一个结点的next指针指向自己的下一个结点->选中该节点的下一个结点->将下一个结点的pre指针修改指向为自己的上一个结点。在进行遍历的时候直接将这一个结点给跳过了,之后,我们释放删除结点,归还空间给内存,这样的操作我们称之为双链表的删除操作。其代码可以表示为://删除元素 line * deleteLine(line * head,int data){ //输入的参数分别为进行此操作的双链表,需要删除的数据 line * list=head; //遍历链表 while (list) { //判断是否与此元素相等 //删除该点方法为将该结点前一结点的next指向该节点后一结点 //同时将该结点的后一结点的pre指向该节点的前一结点 if (list->data==data) { list->pre->next=list->next; list->next->pre=list->pre; free(list); printf("--删除成功--\n"); return head; } list=list->next; } printf("Error:没有找到该元素,没有产生删除\n"); return head; } 双向链表的遍历双向链表的遍历利用next指针逐步向后进行索引即可。注意,在判断这里,我们既可以用while(list)的操作直接判断是否链表为空,也可以使用while(list->next)的操作判断该链表是否为空,其下一节点为空和本结点是否为空的判断条件是一样的效果。其简单的代码可以表示为://遍历双链表,同时打印元素数据 void printLine(line *head){ line *list = head; int pos=1; while(list){ printf("第%d个数据是:%d\n",pos++,list->data); list=list->next; } } 循环链表循环链表概念对于单链表以及双向链表,就像一个小巷,无论怎么走最终都能从一端走到另一端,顾名思义,循环链表则像一个有传送门的小巷,当你以为你走到结尾的时候,其实这就是开头。循环链表和非循环链表其实创建的过程唯一不同的是,非循环链表的尾结点指向空(NULL),而循环链表的尾指针指向的是链表的开头。通过将单链表的尾结点指向头结点的链表称之为循环单链表(Circular linkedlist)一个完整的循环单链表如图: 循环链表结点设计(以单循环链表为例)对于循环单链表的结点,可以完全参照于单链表的结点设计,如图:data表示数据;next表示指针,它总是指向自身的下一个结点,对于只有一个结点的存在,这个next指针则永远指向自身,对于一个链表的尾部结点,next永远指向开头。其代码如下:typedef struct list{ int data; struct list *next; }list; //data为存储的数据,next指针为指向下一个结点 循环单链表初始化先创建一个头结点并且给其开辟内存空间,在开辟内存空间成功之后,将头结点的next指向head自身,创建一个init函数来完成;为了重复创建和插入,我们可以在init函数重新创建的结点next指向空,而在主函数调用创建之后,将head头结点的next指针指向自身。这样的操作方式可以方便过后的创建单链表,直接利用多次调用的插入函数即可完成整体创建。其代码如下://初始结点 list *initlist(){ list *head=(list*)malloc(sizeof(list)); if(head==NULL){ printf("创建失败,退出程序"); exit(0); }else{ head->next=NULL; return head; } } 在主函数重调用可以是这样 //////////初始化头结点////////////// list *head=initlist(); head->next=head; 循环链表的创建操作如图所示:通过逐步的插入操作,创建一个新的节点,将原有链表尾结点的next指针修改指向到新的结点,新的结点的next指针再重新指向头部结点,然后逐步进行这样的插入操作,最终完成整个单项循环链表的创建。其代码如下://创建——插入数据 int insert_list(list *head){ int data; //插入的数据类型 printf("请输入要插入的元素:"); scanf("%d",&data); list *node=initlist(); node->data=data; //初始化一个新的结点,准备进行链接 if(head!=NULL){ list *p=head; //找到最后一个数据 while(p->next!=head){ p=p->next; } p->next=node; node->next=head; return 1; }else{ printf("头结点已无元素\n"); return 0; } } 循环单链表的插入操作如图,对于插入数据的操作,可以创建一个独立的结点,通过将需要插入的结点的上一个结点的next指针指向该节点,再由需要插入的结点的next指针指向下一个结点的方式完成插入操作。 其代码如下://插入元素 list *insert_list(list *head,int pos,int data){ //三个参数分别是链表,位置,参数 list *node=initlist(); //新建结点 list *p=head; //p表示新的链表 list *t; t=p; node->data=data; if(head!=NULL){ for(int i=1;i t=t->next; //走到需要插入的位置处 } node->next=t->next; t->next=node; return p; } return p; } 循环单链表的删除操作如下图所示,循环单链表的删除操作是先找到需要删除的结点,将其前一个结点的next指针直接指向删除结点的下一个结点即可。需要注意的是尾结点,因为删除尾节点后,尾节点前一个结点就成了新的尾节点,这个新的尾节点需要指向的是头结点而不是空。 其代码如下://删除元素 int delete_list(list *head) { if(head == NULL) { printf("链表为空!\n"); return 0; } //建立临时结点存储头结点信息(目的为了找到退出点) //如果不这么建立的化需要使用一个数据进行计数标记,计数达到链表长度时自动退出 //循环链表当找到最后一个元素的时候会自动指向头元素,这是我们不想让他发生的 list *temp = head; list *ptr = head->next; int del; printf("请输入你要删除的元素:"); scanf("%d",&del); while(ptr != head) { if(ptr->data == del) { if(ptr->next == head) { temp->next = head; free(ptr); return 1; } temp->next = ptr->next; //核心删除操作代码 free(ptr); //printf("元素删除成功!\n"); return 1; } temp = temp->next; ptr = ptr->next; } printf("没有找到要删除的元素\n"); return 0; } 循环单链表的遍历与普通的单链表和双向链表的遍历不同,循环链表需要进行结点的特殊判断。先找到尾节点的位置,由于尾节点的next指针是指向头结点的,所以不能使用链表本身是否为空(NULL)的方法进行简单的循环判断,我们需要通过判断结点的next指针是否等于头结点的方式进行是否完成循环的判断。此外还有一种计数的方法,即建立一个计数器count=0,每一次next指针指向下一个结点时计数器+1,当count数字与链表的节点数相同的时候即完成循环;但是这样做会有一个问题,就是获取到链表的节点数同时,也需要完成一次遍历才可以达成目标。其代码如下://遍历元素 int display(list *head) { if(head != NULL) { list *p = head; //遍历头节点到,最后一个数据 while(p->next != head ) { printf("%d ",p->next->data); p = p->next; } printf("\n"); //换行 //把最后一个节点赋新的节点过去 return 1; } else { printf("头结点为空!\n"); return 0; } } 进阶概念——双向循环链表循环单链表也有一个孪生兄弟——双向循环链表,其设计思路与单链表和双向链表的设计思路一样,就是在原有的双向链表的基础上,将尾部结点和头部结点进行互相连接。交给大家了。关于链表的总结在顺序表中做插入删除操作时,平均移动大约表中一半的元素,因此对n较大的顺序表效率低。 并且需要预先分配足够大的存储空间,估计过大,可能会导致顺序表后部大量闲置;预先分配过小,又会造成溢出。而链表恰恰是其中运用的精华。基于存储,运算,环境这几方面考虑,可以让我们更好的在项目中使用链表。下一期,我们再见!发布于 2020-09-16 23:05数据结构(书籍)数据结构编程赞同 90425 条评论分享喜欢收藏申请转载文章被以下专栏收录技术让梦想更伟大公众号:【技术让梦想更伟大】讲原理,抠细节, type point=^node; node=record data:longint; next:point; end; var i,n,e:longint; p,q,head,last:point; begin write('Input the number count:'); readln(n); i:=1; new(head); read(e); head^.data:=e; head^.next:=nil; last:=head; q:=head; while i begin inc(i); read(e); new(p); q^.next:=p; p^.data:=e; p^.next:=nil; last:=p; q:=last end; //建立链表 q:=head; while q^.next<>nil do begin write(q^.data,''); q:=q^.next; end; write(q^.data); //输出 readln; readln end.删除在以z为头的链表中搜索第一个n,如果找到则删去,返回值为1,否则返回0function delete(n:longint;var z:point):longint; var t,s:point; begin t:=z; while(t^.next<>nil)and(t^.data<>n)do begin s:=t; t:=t^.next; end; if t^.data<> nthen exit(0); s^.next:=t^.next; dispose(t); exit⑴ end;查找类似于删除,只需要找到不删即可插入插入,在以zz为头的链表第w个的前面插入nn元素,函数返回值正常是0,如果w超过了链表的长度,函数返回链表的长度function insert(w,nn:longint;var zz:point):longint; var d:longint;v,vp,vs:point; begin v:=zz; for d:=1 to w do if v^.next=nil then exit(d) else begin vp:=v; v:=v^.next; end; new(vs); vs^.data:=nn; vp^.next:=vs; vs^.next:=v; exit(0) end;链表函数播报编辑#include #include #include usingnamespacestd; structNode { intdata;//数据域 structNode*next;//指针域 }; /* Create *函数功能:创建链表. *输入:各节点的data *返回值:指针head */ Node*Create() { intn=0; Node*head,*p1,*p2; p1=p2=newNode; cin>>p1->data; head=NULL; while(p1->data!=0) { if(n==0) { head=p1; } else p2->next=p1; p2=p1; p1=newNode; cin>>p1->data; n++; } p2->next=NULL; returnhead; } /* insert *函数功能:在链表中插入元素. *输入:head链表头指针,p新元素插入位置,x新元素中的数据域内容 *返回值:无 */ voidinsert(Node*head,intp,intx) { Node*tmp=head;//for循环是为了防止插入位置超出了链表长度 for(inti=0;i { if(tmp==NULL) return; if(i tmp=tmp->next; } Node*tmp2=newNode; tmp2->data=x; tmp2->next=tmp->next; tmp->next=tmp2; } /* del *函数功能:删除链表中的元素 *输入:head链表头指针,p被删除元素位置 *返回值:被删除元素中的数据域.如果删除失败返回-1 */ intdel(Node*head,intp) { Node*tmp=head; for(inti=0;i { if(tmp==NULL) return-1; if(i tmp=tmp->next; } intret=tmp->next->data; tmp->next=tmp->next->next; returnret; } voidprint(Node*head) { for(Node*tmp=head;tmp!=NULL;tmp=tmp->next) printf("%d",tmp->data); printf("\n"); } intmain() { Node*head; head=newNode; head->data=-1; head->next=NULL; return0; } 例子 #include #defineNULL0 structstudent { longnum; structstudent*next; }; intmain() { inti,n; student*p=(structstudent*)malloc(sizeof(structstudent)); student*q=p; printf("输入几个值"); scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&(q->num)); q->next=(structstudent*)malloc(sizeof(structstudent)); q=q->next; } printf("值第几个"); intrank; scanf("%d%d",&(q->num),&rank); student*w=p; for(i=1;i { w=w->next; } q->next=w->next; w->next=q; for(i=1;i<=n+1;i++) { printf("%d",p->num); p=p->next; } return0; }//指针后移麻烦链表形式循环链表循环链表是与单链表一样,是一种链式的存储结构,所不同的是,循环链表的最后一个结点的指针是指向该循环链表的第一个结点或者表头结点,从而构成一个环形的链。循环链表的运算与单链表的运算基本一致。所不同的有以下几点:1、在建立一个循环链表时,必须使其最后一个结点的指针指向表头结点,而不是象单链表那样置为NULL。此种情况还使用于在最后一个结点后插入一个新的结点。2、在判断是否到表尾时,是判断该结点链域的值是否是表头结点,当链域值等于表头指针时,说明已到表尾。而非象单链表那样判断链域值是否为NULL。双向链表双向链表其实是单链表的改进。当我们对单链表进行操作时,有时你要对某个结点的直接前驱进行操作时,又必须从表头开始查找。这是由单链表结点的结构所限制的。因为单链表每个结点只有一个存储直接后继结点地址的链域,那么能不能定义一个既有存储直接后继结点地址的链域,又有存储直接前驱结点地址的链域的这样一个双链域结点结构呢?这就是双向链表。在双向链表中,结点除含有数据域外,还有两个链域,一个存储直接后继结点地址,一般称之为右链域;一个存储直接前驱结点地址,一般称之为左链域。应用举例概述约瑟夫环问题:已知n个人(以编号1,2,3...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。例如:n = 9,k = 1,m = 5参考代码#include #include #defineN41 #defineM5 typedefstructnode*link; structnode { intitem; linknext; }; linkNODE(intitem,linknext) { linkt=malloc(sizeof*t); t->item=item; t->next=next; returnt; } intmain(void) { inti; linkt=NODE(1,NULL); t->next=t; for(i=2;i<=N;i++) t=t->next=NODE(i,t->next); while(t!=t->next) { for(i=1;i t=t->next; t->next=t->next->next; } printf("%d\n",t->item); return0; }其他相关结语与个人总结C语言是学习数据结构的很好的学习工具。理解了C中用结构体描述数据结构,那么对于理解其C++描述,Java描述都就轻而易举了!链表的提出主要在于顺序存储中的插入和删除的时间复杂度是线性时间的,而链表的操作则可以是常数时间的复杂度。对于链表的插入与删除操作,个人做了一点总结,适用于各种链表如下:插入操作处理顺序:中间节点的逻辑,后节点逻辑,前节点逻辑。按照这个顺序处理可以完成任何链表的插入操作。删除操作的处理顺序:前节点逻辑,后节点逻辑,中间节点逻辑。按照此顺序可以处理任何链表的删除操作。如果不存在其中的某个节点略过即可。上面的总结,大家可以看到一个现象,就是插入的顺序和删除的顺序恰好是相反的,很有意思!操作-----悉尼大学工程学院张志刚(Stone Cold)作品#include #include #include typedefstructSlist { intdata; structSlist*next; } SLIST; SLIST*InitList_Sq()/*初始化函数*/ { inta; SLIST*h,*s,*r; h=(SLIST*)malloc(sizeof(SLIST));/*建立头指针,头指针不可以更改!!!*/ r=h; if(!h) { printf("分配失败"); exit(0); } scanf("%d",&a); for(;a!=-1;) { s=(SLIST*)malloc(sizeof(SLIST));/*每次都开辟一个结点空间并赋值*/ s->data=a; r->next=s; r=s; scanf("%d",&a); } r->next='\0'; returnh; } voidprint_list(SLIST*finder)/*打印函数*/ { while(finder!='\0') { printf("->%d",finder->data); finder=finder->next; } printf("->end\n"); } intDeleteNode(SLIST*killer)//删除节点函数 { inti,j=0; SLIST*p,*q; intx; p=killer; q=killer->next; printf("请输入您要删除的节点序号:"); scanf("%d",&i); while((p->next!='\0')&&(j { p=p->next; j++; q=p->next; } if(p->next=='\0'||j>i-1) { printf("\nerror"); return-1; } else { p->next=q->next; x=q->data; free(q); returnx; } } voidInsert_Node(SLIST*jumper)//插入函数,本算法为前插结点法 { intt,e,j=0; SLIST*p,*q; p=jumper; printf("请输入要插入位置的序号:"); scanf("%d",&t); printf("请输入要插入的元素:"); scanf("%d",&e); while(p->next!='\0'&&j { j++; p=p->next; } if(p=='\0'||j>t-1) printf("插入的目的位置不存在"); else { q=(SLIST*)malloc(sizeof(SLIST)); q->data=e; q->next=p->next; p->next=q; } } voidLocate_List(SLIST*reader)//查找值为e的元素 { inte,i=0; SLIST*p; p=reader; printf("请输入要查找的元素:"); scanf("%d",&e); while(p->next!='\0'&&p->data!=e) { i++; p=p->next; } if(p->data==e) printf("此元素在%d号位置\n",i); else printf("无此元素!"); } voidmain() { inti,k,y; SLIST*head; printf("\n1.建立线性表"); printf("\n2.在i位置插入元素e"); printf("\n3.删除第i个元素,返回其值"); printf("\n4.查找值为e的元素"); printf("\n5.结束程序运行"); printf("\n==================================================="); printf("请输入您的选择:"); scanf("%d",&k); switch(k) { case1: { head=InitList_Sq(); print_list(head->next); }break; case2: { head=InitList_Sq(); print_list(head->next); Insert_Node(head); print_list(head->next); } break; case3: { head=InitList_Sq(); print_list(head->next); y=DeleteNode(head); print_list(head->next); if(y!=-1) printf("被删除元素为:%d",y); }break;//头结点不算,从有数据的开始算第一个 case4: { head=InitList_Sq(); print_list(head->next); Locate_List(head); }break; } }本程序可在微软VC++下编译通过并且运行使用方法简介:运行程序后,先打数字1,然后回车,这样就可以先创建一个新的链表,比如你要创建一个4->5->6->7这样一个链表,你就输入数字4回车,输入5回车,输入6回车,输入7回车,最后输入-1回车,这个-1就是告诉程序到此为止的标志假如你要使用插入的功能位置插入,就输入3,回车,程序会问你插入的数值是什么,比如你要插入999,然后回车,999就被插进去了其他的功能都大同小异新手上路成长任务编辑入门编辑规则本人编辑我有疑问内容质疑在线客服官方贴吧意见反馈投诉建议举报不良信息未通过词条申诉投诉侵权信息封禁查询与解封©2024 Baidu 使用百度前必读 | 百科协议 | 隐私政策 | 百度百科合作平台 | 京ICP证030173号 京公网安备110000020000 c语言链表详解(超详细)-CSDN博客 c语言链表详解(超详细) 最新推荐文章于 2024-01-21 15:15:21 发布 Mr.Gzj 最新推荐文章于 2024-01-21 15:15:21 发布 阅读量10w+ 收藏 1w 点赞数 2.7k 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/endeavor_g/article/details/80552680 版权 链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用。链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。链表都有一个头指针,一般以head来表示,存放的是一个地址。链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。说到这里你应该就明白了,链表就如同车链子一样,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。 作为有强大功能的链表,对他的操作当然有许多,比如:链表的创建,修改,删除,插入,输出,排序,反序,清空链表的元素,求链表的长度等等。 初学链表,一般从单向链表开始 --->NULL head 这是一个空链表。 ---->[p1]---->[p2]...---->[pn]---->[NULL] head p1->next p2->next pn->next 有n个节点的链表。 创建链表 typedef struct student{ int score; struct student *next; } LinkList; 一般创建链表我们都用typedef struct,因为这样定义结构体变量时,我们就可以直接可以用LinkList *a;定义结构体类型变量了。 初始化一个链表,n为链表节点个数。 LinkList *creat(int n){ LinkList *head, *node, *end;//定义头节点,普通节点,尾部节点; head = (LinkList*)malloc(sizeof(LinkList));//分配地址 end = head; //若是空链表则头尾节点一样 for (int i = 0; i < n; i++) { node = (LinkList*)malloc(sizeof(LinkList)); scanf("%d", &node->score); end->next = node; end = node; } end->next = NULL;//结束创建 return head; } 修改链表节点值 修改链表节点值很简单。下面是一个传入链表和要修改的节点,来修改值的函数。 void change(LinkList *list,int n) {//n为第n个节点 LinkList *t = list; int i = 0; while (i < n && t != NULL) { t = t->next; i++; } if (t != NULL) { puts("输入要修改的值"); scanf("%d", &t->score); } else { puts("节点不存在"); } } 删除链表节点 删除链表的元素也就是把前节点的指针域越过要删除的节点指向下下个节点。即:p->next = q->next;然后放出q节点的空间,即free(q); void delet(LinkList *list, int n) { LinkList *t = list, *in; int i = 0; while (i < n && t != NULL) { in = t; t = t->next; i++; } if (t != NULL) { in->next = t->next; free(t); } else { puts("节点不存在"); } } 插入链表节点 我们可以看出来,插入节点就是用插入前节点的指针域链接上插入节点的数据域,再把插入节点的指针域链接上插入后节点的数据域。根据图,插入节点也就是:e->next = head->next; head->next = e; 增加链表节点用到了两个结构体指针和一个int数据。 void insert(LinkList *list, int n) { LinkList *t = list, *in; int i = 0; while (i < n && t != NULL) { t = t->next; i++; } if (t != NULL) { in = (LinkList*)malloc(sizeof(LinkList)); puts("输入要插入的值"); scanf("%d", &in->score); in->next = t->next;//填充in节点的指针域,也就是说把in的指针域指向t的下一个节点 t->next = in;//填充t节点的指针域,把t的指针域重新指向in } else { puts("节点不存在"); } } 输出链表 输出链表很简单,边遍历边输出就行了。 while (h->next != NULL) { h = h->next; printf("%d ", h->score); } 优惠劵 Mr.Gzj 关注 关注 2762 点赞 踩 10933 收藏 觉得还不错? 一键收藏 知道了 241 评论 c语言链表详解(超详细) 链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用。链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。链表都有一个头指针,一般以head来表示,存放的是一个地址。链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。说到这里你应该就明白... 复制链接 扫一扫 C语言链表.pdf 09-27 C语言链表.pdf C语言链表详解 09-07 比较适合学习C语言的用户,文档中对链表给出了详尽的讲解。。。。 241 条评论 您还未登录,请先 登录 后发表或查看评论 C语言之双向链表详解及实例代码 12-31 1,双向链表简介。 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。 2,例子要求: 完成双向链表的插入、删除以及查找,将学生管理系统使用的数组,以双向链表的方式实现,能够支持无限制的学生人数的增删改查以及保存。 3,代码实现。 #include #include #include #include typedef struct Student{ 【数据结构】单链表的基本操作 (C语言版) 最新发布 weixin_56814370的博客 01-21 950 【数据结构】单链表的基本操作 (C语言版) 数据结构---单向链表,双向链表,单向环形链表 root1994的博客 07-09 4504 链表介绍 链表是以节点的方式来存储,是链式存储 每个节点包含 data 域, next 域:指向下一个节点. 如图:发现链表的各个节点不一定是连续存储. 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定 修改节点功能 思路(1) 先找到该节点,通过遍历,(2) temp.name = newHeroNode.name ; temp.nickname= newHeroNode.n... c语言简单的链表详解 12-16 详细解释了链表的基本操作,附有实例代码! +《链表——>小阿豪带你写链表》!!!!进入正文 1.首先,先想好自己要创建的链表格式以及最后的的显示界面!!! 2.主函数尽量放到后面写,前面写功能函数,在调用主函数之前要声明一下!! 3.先写链表主结构体,再写成员结构体,将成员结构体嵌入主结构体!!! ~4.可以给你所需要的功能创建一个菜单,用switch-case语句实现功能!!! 5.注意功能函数的放置次序,要不然会报错,必须声明!!! 【数据结构】链表 knous 03-13 6万+ 链表 [数据结构]——链表详解 m0_62023005的博客 09-19 3534 数据结构---链表详解 链表基础知识详解(非常详细简单易懂) 热门推荐 qq_61672347的博客 07-10 14万+ 数据结构链表详解,创建,遍历,释放,节点的查找,节点的删除,插入节点,链表排序等操作,非常详细 C语言数据结构——链表 zcxmjw的博客 12-22 1万+ 大家好,本篇文章主要带领大家了解一下什么是链表,链表的两个主要结构,以及链表和顺序表的差别。 数据结构(一)--- 链表 weixin_53072248的博客 08-05 2125 数据结构概述:在计算机中存储和组织数据的方式。算法概述:解决方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制。算法复杂度O(1) 常数阶O(log(n)) 对数阶O(n) 线性阶O(nlog(n)) 线性和对数乘积O(n2) 平方阶O(2n) 指数阶既可以从头遍历到尾,又可以从尾遍历到头。也就是说链表连接的过程是双向的,它的实现原理是:一个节点既有向前连接的引用,也有一个向后连接的引用。 C语言——链表的归并_c语言链表详解 12-21 用c语言写链表归并 C语言链表详解PPT课件.pptx 10-06 C语言链表详解PPT课件.pptx C语言链表详解.ppt 11-15 C语言链表详解.ppt C语言链表详解附实例 03-11 链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。链表和数组比较,不用事先确定存储空间,而是根据需要开辟内存单元。 这些资料是链表的实例 C语言链表详解实用教案.ppt 12-12 C语言链表详解实用教案.ppt C语言单向链表的表示与实现实例详解 12-31 C语言中的单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始。 链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的... C语言链表详解PPT学习教案.pptx 10-06 C语言链表详解PPT学习教案.pptx c语言 链表的查询 09-06 在C语言中,链表的查询可以通过遍历链表来实现。具体的步骤如下: 1. 首先,定义一个指向链表头节点的指针,用于遍历链表。 2. 从头节点开始,依次比较每个节点的值是否与目标值相等。 3. 如果找到匹配的节点,返回该节点的位置或者其他需要的信息。 4. 如果遍历完整个链表都没有找到匹配的节点,则表示目标值不存在于链表中。 这样,我们就可以通过遍历链表来进行查询操作。 需要注意的是,在进行链表查询时,要确保链表的指针不为空,以避免出现空指针异常。另外,在遍历链表时,可以利用循环结构来实现,直到遍历到链表的末尾或者找到匹配的节点为止。 总结起来,链表的查询操作可以通过遍历链表,依次比较每个节点的值来实现。这是一个常见且基础的链表操作,对于理解链表的概念和应用非常重要。123 #### 引用[.reference_title] - *1* *2* [一看就懂-c语言链表的查找,删除,清空【初阶】](https://blog.csdn.net/weixin_64524066/article/details/122373656)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [C语言链表超详解](https://blog.csdn.net/k666499436/article/details/124787990)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ] “相关推荐”对你有帮助么? 非常没帮助 没帮助 一般 有帮助 非常有帮助 提交 Mr.Gzj CSDN认证博客专家 CSDN认证企业博客 码龄6年 暂无认证 401 原创 2万+ 周排名 67万+ 总排名 76万+ 访问 等级 8163 积分 1133 粉丝 2838 获赞 299 评论 1万+ 收藏 私信 关注 热门文章 c语言链表详解(超详细) 606864 c++实现进制转换 8193 java的对象存储在哪里? 6577 欧拉通路、欧拉回路、欧拉图概念区分 4116 快速排序(用递归实现) 3705 分类专栏 设计模式 18篇 基础知识 3篇 Think in java读书笔记 5篇 网络流 16篇 差分 4篇 LCA 4篇 莫队 1篇 动态规划 dp 34篇 最小生成树 6篇 最短路 29篇 并查集 12篇 DFS & BFS 17篇 拓扑排序 7篇 Java基础 19篇 欧拉回路 4篇 二分 8篇 差分约束 4篇 连通图 21篇 树状数组 12篇 codeforces 12篇 牛客 2篇 线段树 30篇 codevs 9篇 博弈论 9篇 数论 44篇 字符串 12篇 树链剖分 1篇 思维 21篇 hdu 4篇 快速幂 10篇 离散化 2篇 优先队列、堆 3篇 单调队列、单调栈 分治 8篇 数据结构 1篇 二分图 5篇 计算机网络 1篇 传递闭包 3篇 主席树 8篇 hash 最新评论 c语言链表详解(超详细) 逢时午: ”活“的才好,哈哈哈哈 poj2566Bound Found(前缀和+尺取法) 22岁机器: 你这是从小到大 poj2566Bound Found(前缀和+尺取法) 22岁机器: 排序都写错了 poj2566Bound Found(前缀和+尺取法) 22岁机器: 看了半天原来你发的帖子有问题 c语言链表详解(超详细) m0_72761900: 代码有问题,初始化头节点应该是空的 您愿意向朋友推荐“博客详情页”吗? 强烈不推荐 不推荐 一般般 推荐 强烈推荐 提交 最新文章 sql练习题 9-1拼多多笔试第四题 MySQL Innodb 数据页结构分析 2021年1篇 2020年29篇 2019年276篇 2018年120篇 目录 目录 分类专栏 设计模式 18篇 基础知识 3篇 Think in java读书笔记 5篇 网络流 16篇 差分 4篇 LCA 4篇 莫队 1篇 动态规划 dp 34篇 最小生成树 6篇 最短路 29篇 并查集 12篇 DFS & BFS 17篇 拓扑排序 7篇 Java基础 19篇 欧拉回路 4篇 二分 8篇 差分约束 4篇 连通图 21篇 树状数组 12篇 codeforces 12篇 牛客 2篇 线段树 30篇 codevs 9篇 博弈论 9篇 数论 44篇 字符串 12篇 树链剖分 1篇 思维 21篇 hdu 4篇 快速幂 10篇 离散化 2篇 优先队列、堆 3篇 单调队列、单调栈 分治 8篇 数据结构 1篇 二分图 5篇 计算机网络 1篇 传递闭包 3篇 主席树 8篇 hash 目录 评论 241 被折叠的 条评论 为什么被折叠? 到【灌水乐园】发言 查看更多评论 添加红包 祝福语 请填写红包祝福语或标题 红包数量 个 红包个数最小为10个 红包总金额 元 红包金额最低5元 余额支付 当前余额3.43元 前往充值 > 需支付:10.00元 取消 确定 下一步 知道了 成就一亿技术人! 领取后你会自动成为博主和红包主的粉丝 规则 hope_wisdom 发出的红包 实付元 使用余额支付 点击重新获取 扫码支付 钱包余额 0 抵扣说明: 1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。 2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。 余额充值 数据结构之链表---从入门到精通 - 知乎首发于数据结构切换模式写文章登录/注册数据结构之链表---从入门到精通计算机菜鸟一枚无欲无求,开心最好1.链表定义【1】概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。【2】组成:由一系列节点(Node)通过指针连接而成,从一个头节点(Head)开始,头节点作为链表的入口点,它包含了对第一个节点的引用。最后一个节点的指针指向一个空值(NULL),表示链表的结束。【3】存储:链表在内存中的存储方式则是随机存储(见缝插针),每一个节点分布在内存的不同位置,依靠指针关联起来。(只要有足够的内存空间,就能为链表分配内存)【4】优缺点:相对于顺序储存(例如数组):优点:链表的插入操作更快( O(1) ),无需预先分配内存空间缺点:失去了随机读取的优点(需要从头节点开始依次遍历,直到找到目标节点。),内存消耗较大(每个节点都需要存储指向下一个节点的指针)。【5】对比:链表是通过节点把离散的数据链接成一个表,通过对节点的插入和删除操作从而实现对数据的存取。而数组是通过开辟一段连续的内存来存储数据,这是数组和链表最大的区别。数组的每个成员对应链表的节点,成员和节点的数据类型可以是标准的 C 类型或者是 用户自定义的结构体。数组有起始地址和结束地址,而链表是一个圈,没有头和尾之分, 但是为了方便节点的插入和删除操作会人为的规定一个根节点。【5】分类:链表一般有单向链表,双向链表,循环链表,带头链表这四种形式。常用的两种结构:1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单。2.链表的实现2.1链表的创建// 定义链表节点结构 struct Node { int data; // 数据 struct Node* next; // 指向下一个节点的指针 }; // 创建链表函数 struct Node* createLinkedList(int arr[], int n) { struct Node* head = NULL; // 头节点指针 struct Node* tail = NULL; // 尾节点指针 // 遍历数组,为每个元素创建一个节点,并加入链表 for (int i = 0; i < n; i++) { // 创建新节点并为其分配内存 struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); if (newNode == NULL) { printf("内存分配失败."); return NULL; } // 设置新节点的数据 newNode->data = arr[i]; newNode->next = NULL; // 如果链表为空,将新节点设置为头节点和尾节点 if (head == NULL) { head = tail = newNode; } // 如果链表非空,将新节点加入到尾部,并更新尾节点指针 else { tail->next = newNode; tail = newNode; } } return head; }2.2链表的插入同顺序表一样,向链表中增添元素,根据添加位置不同,可分为以下 3 种情况:插入到链表的头部,作为首元节点;插入到链表中间的某个位置;插入到链表的最末端,作为链表中最后一个结点;// 在链表中插入节点 void insert(struct Node** headRef, int position, int value) { // 创建新节点并为其分配内存 struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); if (newNode == NULL) { printf("内存分配失败。\n"); return; } newNode->data = value; // 如果要插入的位置是链表的头部或链表为空 if (*headRef == NULL || position == 0) { newNode->next = *headRef; *headRef = newNode; return; } struct Node* current = *headRef; int count = 1; // 找到要插入位置的前一个节点 while (current->next != NULL && count < position) { current = current->next; count++; } // 在指定位置插入节点 newNode->next = current->next; current->next = newNode; } 代码解惑:{struct Node** headRef}在链表节点删除中使用双指针的原因是为了更改指针的指向。在单指针情况下,如果我们想要删除当前节点,我们无法直接修改前一个节点的 next 指针,因为我们只有当前节点的指针。所以我们需要一个额外的指针来保存前一个节点的地址,这个额外的指针就是双指针中的 prev。通过双指针的方式,我们可以在遍历链表的过程中同时记录前一个节点和当前节点的位置。当找到要删除的节点时,我们可以使用 prev 指针修改前一个节点的 next 指针,将其指向当前节点的下一个节点,从而实现删除操作。同时,我们还可以使用 current 指针来释放要删除的节点的内存。2.3链表的查询// 在链表中查询节点 struct Node* search(struct Node* head, int value) { struct Node* current = head; while (current != NULL) { if (current->data == value) { return current; // 返回匹配的节点地址 } current = current->next; } return NULL; // 若没有找到匹配节点,则返回NULL }2.4链表的删除// 在链表中删除节点 void delete(struct Node** headRef, int value) { struct Node* current = *headRef; struct Node* prev = NULL; // 处理头节点为目标节点的情况 if (current != NULL && current->data == value) { *headRef = current->next; free(current); return; } // 遍历链表找到要删除的节点 while (current != NULL && current->data != value) { prev = current; current = current->next; } // 如果找到了目标节点,则删除它 if (current != NULL) { prev->next = current->next; free(current); } }2.5链表的释放// 释放链表内存 void freeList(struct Node* head) { struct Node* current = head; struct Node* next; while (current != NULL) { next = current->next; free(current); current = next; } }2.6链表的遍历void printLinkedList(struct Node* head) { struct Node* current = head; while (current != NULL) { printf("%d ", current->data); current = current->next; } printf("\n"); }3.双向链表【1】起源:单链表的结点中只有一个指向其后继的指针,使得单链表要访问某个结点的前驱结点时,只能从头开始遍历,访问后驱结点的复杂度为O(1),访问前驱结点的复杂度为O(n)。(例如,若实际问题中需要频繁地查找某个结点的前驱结点,使用单链表存储数据显然没有优势,因为单链表的强项是从前往后查找目标元素,不擅长从后往前查找元素。)为了克服上述缺点,引入了双链表。【2】定义:双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。图1:双链表从图 1 中可以看到,双向链表中各节点包含以下 3 部分信息(如图 2 所示):指针域:用于指向当前节点的直接前驱节点;数据域:用于存储数据元素。指针域:用于指向当前节点的直接后继节点;typedef struct line{ struct line * prior; //指向直接前趋 int data; struct line * next; //指向直接后继 }Line;【3】特点: 1.每次在插入或删除某个节点时, 需要处理四个节点的引用, 而不是两个. 实现起来要困难一些; 2.相对于单向链表, 必然占用内存空间更大一些; 3.既可以从头遍历到尾, 又可以从尾遍历到头;4.双向链表的实现4.1双向链表的创建需要注意的是,与单链表不同,双链表创建过程中,每创建一个新节点都要与其前驱节点建立两次联系,分别是:将新节点的 prior 指针指向直接前驱节点;将直接前驱节点的 next 指针指向新节点;Line* initLine(Line* head) { Line* list = NULL; head = (Line*)malloc(sizeof(Line));//创建链表第一个结点(首元结点) head->prior = NULL; head->next = NULL; head->data = 1; list = head; for (int i = 2; i <= 5; i++) { //创建并初始化一个新结点 Line* body = (Line*)malloc(sizeof(Line)); body->prior = NULL; body->next = NULL; body->data = i; //直接前趋结点的next指针指向新结点 list->next = body; //新结点指向直接前趋结点 body->prior = list; list = list->next; } return head; }4.2双向链表的添加根据数据添加到双向链表中的位置不同,可细分为以下 3 种情况:1,添加至表头2,添加至中间位置3,添加至表尾Line* insertLine(Line* head, int data, int add) { //新建数据域为data的结点 Line* temp = (Line*)malloc(sizeof(Line)); temp->data = data; temp->prior = NULL; temp->next = NULL; //插入到链表头,要特殊考虑 if (add == 1) { temp->next = head; head->prior = temp; head = temp; } else { int i; Line* body = head; //找到要插入位置的前一个结点 for (i = 1; i < add - 1; i++) { body = body->next; //只要 body 不存在,表明插入位置输入错误 if (!body) { printf("插入位置有误!\n"); return head; } } //判断条件为真,说明插入位置为链表尾,实现第 2 种情况 if (body && (body->next == NULL)) { body->next = temp; temp->prior = body; } else { //第 2 种情况的具体实现 body->next->prior = temp; temp->next = body->next; body->next = temp; temp->prior = body; } } return head; }4.3双向链表的删除和添加结点的思想类似,在双向链表中删除目标结点也分为 3 种情况。1,删除表头结点2,删除表中结点3,删除表尾结点//删除结点的函数,data为要删除结点的数据域的值 Line* delLine(Line* head, int data) { Line* temp = head; while (temp) { if (temp->data == data) { //删除表头结点 if (temp->prior == NULL) { head = head->next; if (head) { head->prior = NULL; temp->next = NULL; } free(temp); return head; } //删除表中结点 if (temp->prior && temp->next) { temp->next->prior = temp->prior; temp->prior->next = temp->next; free(temp); return head; } //删除表尾结点 if (temp->next == NULL) { temp->prior->next = NULL; temp->prior = NULL; free(temp); return head; } } temp = temp->next; } printf("表中没有目标元素,删除失败\n"); return head; }4.4双向链表的查询//head为原双链表,elem表示被查找元素 int selectElem(line * head,int elem){ //新建一个指针t,初始化为头指针 head line * t=head; int i=1; while (t) { if (t->data==elem) { return i; } i++; t=t->next; } //程序执行至此处,表示查找失败 return -1; }4.5双向链表的更改//更新函数,其中,add 表示要修改的元素,newElem 为新数据的值 void amendElem(Line* p, int oldElem, int newElem) { Line* temp = p; int find = 0; //找到要修改的目标结点 while (temp) { if (temp->data == oldElem) { find = 1; break; } temp = temp->next; } //成功找到,则进行更改操作 if (find == 1) { temp->data = newElem; return; } //查找失败,输出提示信息 printf("链表中未找到目标元素,更改失败\n"); }5.双向循环链表的简单实现#include #include // 定义双向循环链表结构体 struct Node { int data; struct Node* prev; struct Node* next; }; // 在链表末尾插入节点 void insert(struct Node** headRef, int data) { // 创建新节点 struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = data; if (*headRef == NULL) { // 如果链表为空,让新节点成为唯一一个节点 newNode->prev = newNode; newNode->next = newNode; *headRef = newNode; } else { // 如果链表不为空,在链表末尾插入新节点 struct Node* lastNode = (*headRef)->prev; newNode->prev = lastNode; newNode->next = *headRef; lastNode->next = newNode; (*headRef)->prev = newNode; } } // 在链表中删除指定值的节点 void delete(struct Node** headRef, int data) { if (*headRef == NULL) { // 链表为空,无需删除 return; } struct Node* current = *headRef; // 查找要删除的节点 while (current->data != data && current->next != *headRef) { current = current->next; } if (current->data == data) { // 找到要删除的节点 if (current->next == current) { // 要删除的节点是唯一一个节点 *headRef = NULL; } else { // 从链表中删除节点 current->prev->next = current->next; current->next->prev = current->prev; if (*headRef == current) { // 如果要删除的节点是头节点,更新头指针 *headRef = current->next; } } free(current); } } // 打印链表 void printList(struct Node* head) { if (head != NULL) { struct Node* current = head; do { printf("%d ", current->data); current = current->next; } while (current != head); } printf("\n"); } // 测试代码 int main() { struct Node* head = NULL; insert(&head, 1); insert(&head, 2); insert(&head, 3); printf("原始链表:"); printList(head); // 输出:1 2 3 delete(&head, 2); printf("删除节点后的链表:"); printList(head); // 输出:1 3 return 0; }发布于 2023-08-10 09:32・IP 属地安徽算法算法工程师排序算法赞同 392 条评论分享喜欢收藏申请转载文章被以下专栏收录数据结构数据结构从入门 2 3 4struct Node { int value; Node *next; }; 1 2 3 4class Node: def __init__(self, value = None, next = None): self.value = value self.next = next 双向链表双向链表中同样有数据域和指针域。不同之处在于,指针域有左右(或上一个、下一个)之分,用来连接上一个结点、当前结点、下一个结点。实现 C++Python1 2 3 4 5struct Node { int value; Node *left; Node *right; }; 1 2 3 4 5class Node: def __init__(self, value = None, left = None, right = None): self.value = value self.left = left self.right = right 向链表中插入(写入)数据单向链表流程大致如下:初始化待插入的数据 node;将 node 的 next 指针指向 p 的下一个结点;将 p 的 next 指针指向 node。具体过程可参考下图:代码实现如下:实现 C++Python1 2 3 4 5 6void insertNode(int i, Node *p) { Node *node = new Node; node->value = i; node->next = p->next; p->next = node; } 1 2 3 4 5def insertNode(i, p): node = Node() node.value = i node.next = p.next p.next = node 单向循环链表将链表的头尾连接起来,链表就变成了循环链表。由于链表首尾相连,在插入数据时需要判断原链表是否为空:为空则自身循环,不为空则正常插入数据。大致流程如下:初始化待插入的数据 node;判断给定链表 p 是否为空;若为空,则将 node 的 next 指针和 p 都指向自己;否则,将 node 的 next 指针指向 p 的下一个结点;将 p 的 next 指针指向 node。具体过程可参考下图:代码实现如下:实现 C++Python 1 2 3 4 5 6 7 8 9 10 11 12void insertNode(int i, Node *p) { Node *node = new Node; node->value = i; node->next = NULL; if (p == NULL) { p = node; node->next = node; } else { node->next = p->next; p->next = node; } } 1 2 3 4 5 6 7 8 9 10def insertNode(i, p): node = Node() node.value = i node.next = None if p == None: p = node node.next = node else: node.next = p.next p.next = node 双向循环链表在向双向循环链表插入数据时,除了要判断给定链表是否为空外,还要同时修改左、右两个指针。大致流程如下:初始化待插入的数据 node;判断给定链表 p 是否为空;若为空,则将 node 的 left 和 right 指针,以及 p 都指向自己;否则,将 node 的 left 指针指向 p;将 node 的 right 指针指向 p 的右结点;将 p 右结点的 left 指针指向 node;将 p 的 right 指针指向 node。代码实现如下:实现 C++Python 1 2 3 4 5 6 7 8 9 10 11 12 13 14void insertNode(int i, Node *p) { Node *node = new Node; node->value = i; if (p == NULL) { p = node; node->left = node; node->right = node; } else { node->left = p; node->right = p->right; p->right->left = node; p->right = node; } } 1 2 3 4 5 6 7 8 9 10 11 12def insertNode(i, p): node = Node() node.value = i if p == None: p = node node.left = node node.right = node else: node.left = p node.right = p.right p.right.left = node p.right = node 从链表中删除数据单向(循环)链表设待删除结点为 p,从链表中删除它时,将 p 的下一个结点 p->next 的值覆盖给 p 即可,与此同时更新 p 的下下个结点。流程大致如下:将 p 下一个结点的值赋给 p,以抹掉 p->value;新建一个临时结点 t 存放 p->next 的地址;将 p 的 next 指针指向 p 的下下个结点,以抹掉 p->next;删除 t。此时虽然原结点 p 的地址还在使用,删除的是原结点 p->next 的地址,但 p 的数据被 p->next 覆盖,p 名存实亡。具体过程可参考下图:代码实现如下:实现 C++Python1 2 3 4 5 6void deleteNode(Node *p) { p->value = p->next->value; Node *t = p->next; p->next = p->next->next; delete t; } 1 2 3def deleteNode(p): p.value = p.next.value p.next = p.next.next 双向循环链表流程大致如下:将 p 左结点的右指针指向 p 的右节点;将 p 右结点的左指针指向 p 的左节点;新建一个临时结点 t 存放 p 的地址;将 p 的右节点地址赋给 p,以避免 p 变成悬垂指针;删除 t。代码实现如下:实现 C++Python1 2 3 4 5 6 7void deleteNode(Node *&p) { p->left->right = p->right; p->right->left = p->left; Node *t = p; p = p->right; delete t; } 1 2 3 4def deleteNode(p): p.left.right = p.right p.right.left = p.left p = p.right 技巧异或链表异或链表(XOR Linked List)本质上还是 双向链表,但它利用按位异或的值,仅使用一个指针的内存大小便可以实现双向链表的功能。我们在结构 Node 中定义 lr = left ^ right,即前后两个元素地址的 按位异或值。正向遍历时用前一个元素的地址异 或当前节点的 lr 可得到后一个元素的地址,反向遍历时用后一个元素的地址异或当前节点的 lr 又可得到前一个的元素地址。 这样一来,便可以用一半的内存实现双向链表同样的功能。本页面最近更新:2023/10/4 21:50:08,更新历史发现错误?想一起完善? 在 GitHub 上编辑此页!本页面贡献者:aofall, iamtwz, EarthMessenger, Enter-tainer, Ir1d, ksyx, mcendu, Menci, NachtgeistW, shawlleyw, slanterns, StudyingFather, Xeonacid本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用Copyright © 2016 - 2024 OI Wiki Team Made with Material for MkDocs 最近更新:7ff011ae, 2024-03- 链表的创建及基本操作(增、删、改、查)_链表创建-CSDN博客 链表的创建及基本操作(增、删、改、查) 最新推荐文章于 2023-01-15 21:20:51 发布 Fttt. 最新推荐文章于 2023-01-15 21:20:51 发布 阅读量6.8k 收藏 89 点赞数 27 分类专栏: 笔记 文章标签: 链表 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/qq_45836906/article/details/105712032 版权 笔记 专栏收录该内容 6 篇文章 0 订阅 订阅专栏 一、链表概述 链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。与数组区别: 链表是链式的存储结构,数组是顺序的存储结构。 链表通过指针来连接元素与元素,数组则是把所有元素按次序依次存储。 优缺点如图: 二、创建链表前的准备工作 1.结点构成 数据域:存放用户需要的实际数据指针域:存放下一个结点的地址 2.malloc函数 函数原型:void *malloc(unsigned int size);功能:在内存的动态存储区申请一个长度为size字节的连续存储空间,并返回该存储空间的起始地址。如果没有足够的内存空间可分配,则函数的返回值为空指针NULL。说明:这个函数返回的是个void类型指针,所以在使用时应注意强制类型转换成需要的指针类型。 3.free函数 函数原型:void free(void *p);功能:将指针变量p指向的存储空间释放,交换给系统。 三、创建链表 1.尾插法 特点:输入顺序(存储顺序)与输出顺序相同图解:核心代码 end->next = p; //尾节点连接新节点 end = p; //尾节点向后移动成为新的尾节点 2.头插法 特点:输入顺序(存储顺序)与输出顺序相反图解: 核心代码: p->next = head->next; //新节点指向头节点的下一位 head->next = p; //头节点连接新节点 3.完整代码 struct node *creat() { //定义头指针head,尾指针end,和指向新节点的指针p struct node *head= NULL, *end = NULL, *p = NULL; //初始化 head = (struct node *)malloc(sizeof(strcut node)); //动态申请内存 end = head; head->next = NULL; p = (struct node *)malloc(sizeof(strcut node)); scanf("%d", &p->data); while(p->data != 0) { //当输入的值为0时结束 //被注释掉的是头插法 //p->next = head ->next; //head->next = p; end->next = p; p->next = NULL; end = p; //以上三行未注释是尾插法 p = (struct node *)malloc(sizeof(strcut node)); scanf("%d", &p->data); } free(p); //释放最后申请的空间 return head; } 四、链表的基本操作 1.增 遍历链表找到需要插入的位置,操作指针插入新节点。 代码: void add(struct node *head) { struct node *p = NULL, *p0 = NULL; //定义p0为需增加的新节点 int a=0; p = head; printf("请输入增加在哪个数字的后面\n"); scanf("%d", &a); while(p && p->data != a) { //遍历链表直至结点数据域为a 或p为NULL p = p->next; } if(p != NULL) { p0 = (struct node *)malloc(sizeof(struct node)); scanf("%d", &p0->data); p0->next = p->next; p->next = p0; } else { printf("未找到该数\n"); } return; } 2.删 遍历链表找到需要删除的结点,操作指针删除该节点。 代码: void delete_(struct node *head) { struct node *p = head, p0 = NULL; int a = 0; printf("输入需要删除的数字\n"); scanf("%d", &a); while(p && p->data != a) { //遍历链表找到要删除的数 或 p为NULL p0 = p; //保存当前指针的指向 p = p->next; } if(p !=NULL) { p0->next = p->next; } else { printf("未找到该数\n"); } return; } 3.改 遍历链表找到需要修改的结点,修改其数据域 代码: void modity_(struct node *head) { struct node *p = head; int a=0; printf("请输入需要修改的数字\n"); scanf("%d", &a); while(p && p->data != a) { //遍历链表直至p->data 等于需要修改的数字 或p为NULL p = p->next; } if( p!= NULL) { printf("修改为:"); scanf("%d", p->data); } else { printf("未找到该数\n"); } return; } 4.查 遍历链表直至找到需要查询的数据。 代码: void search(struct node *head) { struct node *p = head; int a=0; printf("请输入需要查询的数字\n"); scanf("%d", &a); while(p && p->data != a) { //遍历链表直至p->data == a 或p为NULL p = p->next; } if(p != NULL) { printf("%d\n", p->data); } else { printf("未找到该数\n"); } return; } 优惠劵 Fttt. 关注 关注 27 点赞 踩 89 收藏 觉得还不错? 一键收藏 知道了 5 评论 链表的创建及基本操作(增、删、改、查) 链表概述链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。与数组区别:链表是链式的存储结构,数组是顺序的存储结构。链表通过指针来连接元素与元素,数组则是把所有元素按次序依次存储。优缺点如图:创建链表前的准备工作1.数据元素构成... 复制链接 扫一扫 专栏目录 Java单链表增删改查的实现 08-15 Java实现单链表的增删改查以及选择、冒泡、反转排序等功能的实现! 线性表和链表的存储和增删改查c++ 01-28 线性表和链表的存储和增删改查c++ 5 条评论 您还未登录,请先 登录 后发表或查看评论 链表操作,包含增删改查 10-11 链表操作,包含增删改查 链表的创建与使用 m0_74214501的博客 01-15 2412 链表的基本介绍与使用 单链表的基本操作之创建链表并进行简单操作 weixin_56169816的博客 05-03 2183 单链表的创建与基本操作 1、单链表的理解 1.1单链表的定义 线性表的链式存储又称单链表,它是指通过依据任意的存储单元来存储线性表中的数据元素。为了建立数据元素之间的线性关系,对每个链表结点,除存放元素自身的信息外,还需要存放一个指向后继的指针。单链表结点如下所示。其中data为数据域,存放数据元素;next为指针域,存放其后继结点的地址。 1.2单链表的图形解释 1.1.3单链表的特点 1)单链表不要求逻辑上相邻的两个元素在物理位置上也相邻,因此不需要连续的存储空间。 2)单链表是非随机的存储结 链表基本操作(详解) Czc1357618897的博客 11-30 1万+ 链表的基本操作 这里写目录标题链表的基本操作一:单链表的基础知识二:单链表的建立头插法尾插法三:单链表的遍历四:单链表结点数目判断五:单链表的插入链表头插入任意结点插入链表尾部插入六:单链表的删除七 :单链表的查询 一:单链表的基础知识 为什么需要链表? 我们在使用数组存放数据是非常方便,但是由于数组的长度是固定的,所以当存储不同的元素数量时,就很容易出现问题。如果向数组中添加的数量大于数组大小时候,信息无法完全被保存。所以我们需要另一种存储方式来存储数据,其中存储的元素的个数不受限制。这种存储方式就是链 【数据结构-链表】链表的基本操作 Mount256的博客 10-26 1495 【数据结构-链表】链表的基本操作 链表的基本操作 eminemi的博客 07-13 6647 链表的基本操作包括建立(批量存入数据)、打印(输出所有数据)、删除(在批量数据中删除指定数据)、插入(在批量数据中添加一个数据)等。 如何创建链表? 白日梦想家的博客 07-17 6万+ 引言: 最近我们c语言课学到了链表,好多同学都在说:“哇!链表怎么这么难,根本看不懂呀!” 不要怕,在这一篇博客中,我会给你详细讲解每一行代码! 链表: 链表的组成其实很简单,就是由很多结点组成的。 一个结点包含数据域和指针域,数据域用来存放数据,指针域负责指向其他结点,起到链接的作用。 创建链表: 其实创建一个链表也很简单,在我看来,可以分为以下几步: 1.创建头结点。(命名为:head... 四种创建单链表的方法 兔子王 09-23 1万+ 学习了这么久的数据结构,终于把链表吃透啦,下面是我整理的四种创建单链表的的方法,以及一些非常容易犯的错误,让我们一起来看看吧~ c++实现链表增删改查 07-17 c++实现链表增删改查 用c语言实现链表增删改查 11-07 自己写的,能够使用,对c语言的指针用的淋漓精致,有需要的小伙伴可以下载看下 链表基础操作 weixin_73494910的博客 09-06 162 链表基本操作 一步一步教你从零开始写C语言链表 热门推荐 Bruce.yang的嵌入式之旅 04-02 17万+ 完整源码获取: 微信关注:嵌入式开发圈 发送"链表"即可获取。 为什么要学习链表? 链表主要有以下几大特性: 1、解决数组无法存储多种数据类型的问题。 2、解决数组中,元素个数无法改变的限制(C99的变长数组,C++也有变长数组可以实现)。 3、数组移动元素的过程中,要对元素进行大范围的移动,很耗时间,效率也不高。 先来感性的认识一下链表,我们先来认识下简单的链表: 从这幅图我们... 数据结构链表的创建 雾的博客 05-11 3654 实现链表的创建、遍历、排序、插入、删除 。 C语言来实现链表创建 xinzhilinger的博客 10-11 3万+ 前言 链表是数据结构中很重要的内容,要想学会数据结构,首先要了解链表 链表的基本知识 1,链表的构成 链表是由一个头指针和一个个节点构成的,如图: 而节点则是一个个结构体,包含数据域和指针域两部分,其中,指针域存储的是指向下一个节点的指针 2,对于链表的操作 1,创建链表 2,初始化链表 3,遍历链表 4,对于链表进行增删查等操作 C语言对于链表的代码的实现 1,创建结构体并命名 创建链表前需要先创建结构体作为节点和头指针: typedef struct Node //typedef方法函数可以对于st 链表基本操作 kakaka666的博客 06-13 620 链表基本操作 1. C语言链表的增删改查 最新发布 09-01 链表是一种常见的数据结构,用于存储和操作数据元素。下面是关于C语言链表的增删改查的一些基本操作: 1. 添加节点(增加): - 创建一个新节点。 - 将新节点的数据赋值。 - 将新节点的next指针指向当前链表的头节点(或者指向NULL,如果链表为空)。 - 更新链表的头节点指针,使其指向新节点。 2. 删除节点(删除): - 找到要删除的节点以及它的前一个节点。 - 将前一个节点的next指针指向要删除节点的下一个节点。 - 释放要删除节点的内存。 3. 修改节点(改变): - 遍历链表,找到要修改的节点。 - 修改该节点的数据。 4. 查找节点(查询): - 从链表的头节点开始遍历,直到找到包含所需数据的节点。 - 如果找到了,返回该节点;如果没有找到,返回NULL或者其他特定值。 下面是一个简单的示例代码,演示了链表的增删改查操作: ```c #include #include struct Node { int data; struct Node* next; }; void insertNode(struct Node** head, int data) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = data; newNode->next = *head; *head = newNode; } void deleteNode(struct Node** head, int data) { struct Node* currentNode = *head; struct Node* previousNode = NULL; if (currentNode != NULL && currentNode->data == data) { *head = currentNode->next; free(currentNode); return; } while (currentNode != NULL && currentNode->data != data) { previousNode = currentNode; currentNode = currentNode->next; } if (currentNode == NULL) { printf("Node not found.\n"); return; } previousNode->next = currentNode->next; free(currentNode); } void modifyNode(struct Node* head, int data, int newData) { while (head != NULL) { if (head->data == data) { head->data = newData; return; } head = head->next; } printf("Node not found.\n"); } struct Node* searchNode(struct Node* head, int data) { while (head != NULL) { if (head->data == data) { return head; } head = head->next; } return NULL; } void printList(struct Node* node) { while (node != NULL) { printf("%d ", node->data); node = node->next; } printf("\n"); } int main() { struct Node* head = NULL; insertNode(&head, 3); insertNode(&head, 5); insertNode(&head, 7); printf("Initial list: "); printList(head); deleteNode(&head, 5); printf("Updated list after deletion: "); printList(head); modifyNode(head, 7, 9); printf("Updated list after modification: "); printList(head); struct Node* searchedNode = searchNode(head, 9); if (searchedNode != NULL) { printf("Searched node found: %d\n", searchedNode->data); } else { printf("Searched node not found.\n"); } return 0; } ``` 这个示例代码展示了链表的增删改查操作,你可以根据需要进行修改和扩展。希望对你有帮助! “相关推荐”对你有帮助么? 非常没帮助 没帮助 一般 有帮助 非常有帮助 提交 Fttt. CSDN认证博客专家 CSDN认证企业博客 码龄4年 暂无认证 41 原创 38万+ 周排名 55万+ 总排名 4万+ 访问 等级 710 积分 72 粉丝 113 获赞 24 评论 152 收藏 私信 关注 热门文章 链表的创建及基本操作(增、删、改、查) 6868 iOS—GCD详解 4624 iOS—RunLoop详解 3023 iOS—自定义cell及两种复用方式 2733 iOS—持久化的几种方案 2053 分类专栏 iOS 18篇 总结 1篇 笔记 6篇 最新评论 链表的创建及基本操作(增、删、改、查) SUHE~: 现在都没人用这种链表了,发点主流的 Xcode下使用bits/stdc++.h 头文件 新手小白向您请教: 依然报错…求大佬指点,,显示找不到文件 链表的创建及基本操作(增、删、改、查) Learn more11111: 被删的节点的空间不用被free掉吗? iOS—持久化的几种方案 抓手: 忍不住就是一个赞啊,写得很棒哦 iOS—NSOperation、NSOperationQueue简单了解 复杂化: 牛蛙 您愿意向朋友推荐“博客详情页”吗? 强烈不推荐 不推荐 一般般 推荐 强烈推荐 提交 最新文章 iOS—分类、load 、initialize iOS—事件传递、响应者链 iOS—UICollectionView的简单使用 2021年23篇 2020年18篇 目录 目录 分类专栏 iOS 18篇 总结 1篇 笔记 6篇 目录 评论 5 被折叠的 条评论 为什么被折叠? 到【灌水乐园】发言 查看更多评论 添加红包 祝福语 请填写红包祝福语或标题 红包数量 个 红包个数最小为10个 红包总金额 元 红包金额最低5元 余额支付 当前余额3.43元 前往充值 > 需支付:10.00元 取消 确定 下一步 知道了 成就一亿技术人! 领取后你会自动成为博主和红包主的粉丝 规则 hope_wisdom 发出的红包 实付元 使用余额支付 点击重新获取 扫码支付 钱包余额 0 抵扣说明: 1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。 2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。 余额充值 「数据结构与算法」链表 —— 看这一篇就够了(超详细) - 知乎切换模式写文章登录/注册「数据结构与算法」链表 —— 看这一篇就够了(超详细)AndyPomeloMars一、链表简介 链表是一种物理存储单元上非连续、非顺序的存储结构,数据结构的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比数组快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而数组只需要O(1) 使用链表结构可以克服数组需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。二、链表创建 一、节点结构体 根据以上的简介,我们知道链表的的每一个结点都有存储数据元素的数据域与存储下一个结点地址的指针域.所以我们在创建新链表时需要设置一个结点结构体(如下SingleLinkedListNode).且还要拥有一个head指针与tail指针SLL为单向链表,DLL为双向链表,()为value,-->为指针(单向链表),````与____为指针(双向链表) ====================================================================== SLL[0](1)----->SLL[1](2)----->SLL[2](3)----->SLL[3](4) Head Tail 这里我们创建了一个拥有四个元素的SLL ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ DLL[0](1)`_`_`_`_`DLL[1](2)`_`_`_`_`DLL[2](3)`_`_`_`_`DLL[3](4) Head Tail 这里我们创建了一个拥有四个元素的DLL 注意,head和tail指针在链表中不一定是指向开头和结尾元素,还可能指向其他元素,这一点因人而异.你可以选择自己喜好的方式进行设置(如下代码块)Head---->SLL[0](1)---->SLL[0](2)---->SLL[0](3)---->Tail 再附上两张单双链表的示意图: 这是单链表 这是双链表 好,现在我们就开始进行结点结构体的创建 : 单向链表: struct SingleLinkedListNode{ int value; struct SingleLinkedListNode *next; }*Shead=NULL,*Stail=NULL; 双向链表(如果是双向链表的话则需要有prev指针):struct DoubleLinkedListNode{ int value; struct DoubleLinkedListNode *next; struct DoubleLinkedListNode *prev; }*Dhead=NULL,*Dtail=NULL; 单双链表完整创建: 有了结点结构体,我们就可以进行单双链表创建了:void CreateNewSingleLinkedList(int len){ for (int i=0;i int inputvalue; cin >> inputvalue; SingleLinkedListNode *node = new SingleLinkedListNode; node->value = inputvalue; node->next = NULL; if (!Shead){Shead=node;Stail=node;} else{Stail->next=node;Stail=node;} } } void CreateNewDoubleLinkedList(int len){ for (int i=0;i int inputvalue; cin >> inputvalue; DoubleLinkedListNode *node = new DoubleLinkedListNode; node->value = inputvalue; node->next = NULL; if (!Dhead){Dhead=node;Dtail=node;} else{Dtail->next=node;node->prev=Dtail;Dtail=node;} } } 先看单链表,我们将CreateNewSingleLinkedList的参数len作为单向链表的长度.接着用for循环依次输入每项数据元素的值,再创建一个新的节点(关键字new),并把它的value设成刚刚输入的值,再将它的next项设为NULL.接下来判读如果!Shead(即单链表中没有元素),将Shead设为新节点,如果已经有其他元素,再将现在的Stail的next项设为新节点,最后让Stail变成新节点. 双链表也一样,只不过在上述代码第21行(倒数第3行)多了一个新节点的prev项设为Dtail的操作 三、链表输出 链表的输出从头指针head开始,依次打印数据元素的value,接着再将指针指向下一个元素(单双链表都适用):void OutputSingleLinkedList(){ SingleLinkedListNode *point = Shead; while (point) { cout << point->value << " "; point = point->next; } cout << endl; } void OutputDoubleLinkedList(){ DoubleLinkedListNode *point = Dhead; while (point) { cout << point->value << " "; point = point->next; } cout << endl; } 四、链表长度 链表的长度也可以用遍历解决,依次遍历链表的每一项元素,只要它不为NULL,len就加一(另一种方法就是再创建一个LinkedList类,里面的成员变量有它的长度):int SingleLinkedListLength(){ SingleLinkedListNode *point = Shead; int len; while (point) { len++; } return len; } int DoubleLinkedListLength(){ DoubleLinkedListNode *point = Dhead; int len; while (point) { len++; } return len; } 五、链表元素改值 我们只需要把指针定位到元素上,再将它的value改变即可:void SingleLinkedListChange(int pos,int newvalue){ SingleLinkedListNode *point = Shead; for (int i=0;i<=pos;i++) { point = point->next; } point->value = newvalue; } void DoubleLinkedListChange(int pos,int newvalue){ DoubleLinkedListNode *point = Dhead; for (int i=0;i<=pos;i++) { point = point->next; } point->value = newvalue; } 六、链表插入 一张图时间又到了: 从图中可以看出,我们想要插入15这个节点在10之后,于是我们将10的next项设为15,然后将15的next项设为20.注意,前面这段话如果用代码写出来是不对的:10->next=15;15->next=10->next 为什么不对呢,因为你如果先将10这个节点的下一个节点设为15,那15的下一个节点就又是15了——因为我们已经将10的下一个节点设为15,在将15的下一个节点设为10的下一个节点就没有任何意义了,大家不信可以用计算机试试(不试也行,底下两张实验结果图).所以上述代码应该反过来:15->next=10->next;10->next=15 而双向链表也是同样的,我们先设新插入节点的next与prev值,再设已有节点的prev与next值:15->next = 10->next; 15->prev = 10; 10->next->prev = 15; 10->next = 15; 好,把完整代码拿上来:void SingleLinkedListInsert(int pos,int newvalue){ SingleLinkedListNode *point = Shead; for (int i=0;i point = point->next; } SingleLinkedListNode *node = new SingleLinkedListNode; node->value=newvalue; node->next = point->next; point->next = node; } void DoubleLinkedListInsert(int pos,int newvalue){ DoubleLinkedListNode *point = Dhead; for (int i=0;i point = point->next; } DoubleLinkedListNode *node = new DoubleLinkedListNode; node->value=newvalue; node->next = point->next; node->prev = point; if (!point->next){ Dtail = node; } else{ point->next->prev = node; } if (!point->prev){ Dhead = node; } else{ point->next = node; } } 注意在双向链表进行对表中已有元素的prev与next操作时,要判断prev与next是否为NULL七、链表删除 删除的原理与插入差不多,大家可以自己想想,这里不再阐述:void SingleLinkedListDelete(int pos){ SingleLinkedListNode *point = Shead; for (int i=0;i point = point->next; } point->next = point->next->next; } void DoubleLinkedListDelete(int pos){ DoubleLinkedListNode *point = Dhead; for (int i=0;i point = point->next; } DoubleLinkedListNode *s1 = point->prev,*s2 = point->next; if (s1!=NULL){ s1->next = s2; } else{ if (!pos){ Dhead = s2; } else{ Dhead = s1; } } if (s2!=NULL){ s2->prev = s1; } else{ Dtail = s1; } } 八、链表元素查找 非常的简单,只要判断当前元素的值是否对应即可,依次遍历:bool SingleLinkedListFind(int pos,int findvalue){ SingleLinkedListNode *point = Shead; for (int i=0;i<=pos;i++) { if (point->value==findvalue) return true; point = point->next; } return false; } bool DoubleLinkedListFind(int pos,int findvalue){ DoubleLinkedListNode *point = Dhead; for (int i=0;i<=pos;i++) { if (point->value==findvalue) return true; point = point->next; } return false; } 九、结束语 好了,今天就到这里啦,如果这篇10000字的文章对你有帮助的话,请转发给更多的朋友一起学习,并给予我你们的支持,咱们下期见!!!(附完整代码)#include using namespace std; struct SingleLinkedListNode{ int value; struct SingleLinkedListNode *next; }*Shead=NULL,*Stail=NULL; struct DoubleLinkedListNode{ int value; struct DoubleLinkedListNode *next; struct DoubleLinkedListNode *prev; }*Dhead=NULL,*Dtail=NULL; void CreateNewSingleLinkedList(int len){ for (int i=0;i int inputvalue; cin >> inputvalue; SingleLinkedListNode *node = new SingleLinkedListNode; node->value = inputvalue; node->next = NULL; if (!Shead){Shead=node;Stail=node;} else{Stail->next=node;Stail=node;} } } void CreateNewDoubleLinkedList(int len){ for (int i=0;i int inputvalue; cin >> inputvalue; DoubleLinkedListNode *node = new DoubleLinkedListNode; node->value = inputvalue; node->next = NULL; if (!Dhead){Dhead=node;Dtail=node;} else{Dtail->next=node;node->prev=Dtail;Dtail=node;} } } void OutputSingleLinkedList(){ SingleLinkedListNode *point = Shead; while (point) { cout << point->value << " "; point = point->next; } cout << endl; } void OutputDoubleLinkedList(){ DoubleLinkedListNode *point = Dhead; while (point) { cout << point->value << " "; point = point->next; } cout << endl; } int SingleLinkedListLength(){ SingleLinkedListNode *point = Shead; int len; while (point) { len++; } return len; } int DoubleLinkedListLength(){ DoubleLinkedListNode *point = Dhead; int len; while (point) { len++; } return len; } void SingleLinkedListChange(int pos,int newvalue){ SingleLinkedListNode *point = Shead; for (int i=0;i<=pos;i++) { point = point->next; } point->value = newvalue; } void DoubleLinkedListChange(int pos,int newvalue){ DoubleLinkedListNode *point = Dhead; for (int i=0;i<=pos;i++) { point = point->next; } point->value = newvalue; } void SingleLinkedListInsert(int pos,int newvalue){ SingleLinkedListNode *point = Shead; for (int i=0;i point = point->next; } SingleLinkedListNode *node = new SingleLinkedListNode; node->value=newvalue; node->next = point->next; point->next = node; } void DoubleLinkedListInsert(int pos,int newvalue){ DoubleLinkedListNode *point = Dhead; for (int i=0;i point = point->next; } DoubleLinkedListNode *node = new DoubleLinkedListNode; node->value=newvalue; node->next = point->next; node->prev = point; if (!point->next){ Dtail = node; } else{ point->next->prev = node; } if (!point->prev){ Dhead = node; } else{ point->next = node; } } void SingleLinkedListDelete(int pos){ SingleLinkedListNode *point = Shead; for (int i=0;i point = point->next; } point->next = point->next->next; } void DoubleLinkedListDelete(int pos){ DoubleLinkedListNode *point = Dhead; for (int i=0;i point = point->next; } DoubleLinkedListNode *s1 = point->prev,*s2 = point->next; if (s1!=NULL){ s1->next = s2; } else{ if (!pos){ Dhead = s2; } else{ Dhead = s1; } } if (s2!=NULL){ s2->prev = s1; } else{ Dtail = s1; } } bool SingleLinkedListFind(int pos,int findvalue){ SingleLinkedListNode *point = Shead; for (int i=0;i<=pos;i++) { if (point->value==findvalue) return true; point = point->next; } return false; } bool DoubleLinkedListFind(int pos,int findvalue){ DoubleLinkedListNode *point = Dhead; for (int i=0;i<=pos;i++) { if (point->value==findvalue) return true; point = point->next; } return false; } 编辑于 2022-08-24 19:49算法与数据结构链表C / C++赞同 3添加评论分享喜欢收藏申请 class Node E item; Node Node Node(Node this.item = element; this.next = next; this.prev = prev; } }复制基本概念链表基本结构是节点,节点一般包含数据和指向节点的指针;节点只有指向下一个节点指针的叫单链表(Singly Linked List),有指向上一个节点的指针的叫双链表(Doubly Linked List)。链表的一些关键特点:节点(Node): 链表的基本构建块是节点,每个节点包含两(三)部分,即 数据 element 和 指向下一个节点的指针 next(指向上一个节点的指针 prev)。单链表(Singly Linked List): 单链表中每个节点只有一个指针,即指向下一个节点的指针。双链表(Doubly Linked List): 双链表中每个节点有两个指针,一个指向下一个节点,另一个指向前一个节点,使得可以双向遍历链表。头节点(Head): 链表的头节点是链表的第一个节点,用于标识整个链表的起始位置。尾节点(Tail): 链表的尾节点是最后一个节点,其下一个节点引用通常指向null。链表的性质:插入和删除元素的时间复杂度通常为O(1),因为只需要调整节点的指针。链表大小可以动态增长,不受固定内存大小的限制。访问元素的时间复杂度为O(n),因为必须从头节点开始遍历链表,直到找到目标元素。存储上占用较多内存空间,因为每个节点都需要存储数据和指针。基本应用(Basic)链表最基本的一些算法应用 是 根据要求操作节点指针 next 指针。Leetcode 83. 删除排序链表中的重复元素【简单】给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。Leetcode 203. 移除链表元素【简单】给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。多节点(More Node)在解决一些算法问题,同样可以定义多个指针指向多个链表节点(Node)来进行操作来完成解答。Leetcode 19. 删除链表的倒数第 N 个结点【中等】给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。Leetcode 2. 两数相加【中等】给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。总结下本篇主要介绍了链表基本结构,以及相关一些算法问题分析。链表还可以结合其他数据结构、算法思想比如 哈希(Hash)、优先队列(Priority Queue)等解决一些算法问题;考虑到本系列文章希望能够承前启后,不至于出现一些先前文章未介绍到的数据结构与算法,因此后续文章中再代入分析。另外,从出题人的角度分析算法的问题也是一个不错的选择,可能会带来不一样的总结与经验。欢迎点个小红心,关注公众号 Java研究者 联系、交流~原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。如有侵权,请联系 cloudcommunity@tencent.com 删除。java编程算法数据结构链表入门原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。如有侵权,请联系 cloudcommunity@tencent.com 删除。java编程算法数据结构链表入门评论登录后参与评论0 条评论热度最新登录 后参与评论推荐阅读LV.关注文章0获赞0目录基本概念基本应用(Basic)Leetcode 83. 删除排序链表中的重复元素【简单】Leetcode 203. 移除链表元素【简单】多节点(More Node)Leetcode 19. 删除链表的倒数第 N 个结点【中等】Leetcode 2. 两数相加【中等】总结下领券社区专栏文章阅读清单互动问答技术沙龙技术视频团队主页腾讯云TI平台活动自媒体分享计划邀请作者入驻自荐上首页技术竞赛资源技术周刊社区标签开发者手册开发者实验室关于社区规范免责声明联系我们友情链接腾讯云开发者扫码关注腾讯云开发者领取腾讯云代金券热门产品域名注册云服务器区块链服务消息队列网络加速云数据库域名解析云存储视频直播热门推荐人脸识别腾讯会议企业云CDN加速视频通话图像分析MySQL 数据库SSL 证书语音识别更多推荐数据安全负载均衡短信文字识别云点播商标注册小程序开发网站监控数据迁移Copyright © 2013 - 2024 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有 深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档Copyright © 2013 - 2024 Tencent Cloud.All Rights Reserved. 腾讯云 版权所有登录 后参与评论100 链表 - 知乎首页知乎知学堂发现等你来答切换模式登录/注册链表链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时…查看全部内容关注话题管理分享百科讨论精华视频等待回答详细内容概述链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)[1]。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)[2]。链表有很多种不同的类型:单向链表,双向链表以及循环链表[3]。链表也可以在多种编程语言中实现。像Lisp和Scheme这样的语言的内建数据类型中就包含了链表的访问和操作。程序语言或面向对象语言,如C/C++[4]和Java依靠易变工具来生成链表。链表是一种常见的基础数据结构历史链表开发于1955-56,由当时所属于兰德公司(英语:RAND Corporation)的艾伦纽维尔(Allen Newell),克里夫肖(Cliff Shaw)和赫伯特西蒙(Herbert Simon)在他们编写的信息处理语言(IPL)中做为原始数据类型所编写。IPL被作者们用来开发几种早期的人工智能程序,包括逻辑推理机,通用问题解算器和一个计算机象棋程序。结构单向链表链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。一个单向链表的节点被分成两个部分。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址。单向链表只可向一个方向遍历[5]。链表也有很多种不同的变化:双向链表一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个连接:一个指向前一个节点,(当此“连接”为第一个“连接”时,指向空值或者空列表);而另一个指向下一个节点,(当此“连接”为最后一个“连接”时,指向空值或者空列表)。在一些低级语言中, XOR-linking 提供一种在双向链表中通过用一个词来表示两个链接(前后),我们通常不提倡这种做法。双向链表也叫双链表。双向链表中不仅有指向后一个节点的指针,还有指向前一个节点的指针。这样可以从任何一个节点访问前一个节点,当然也可以访问后一个节点,以至整个链表。一般是在需要大批量的另外储存数据在链表中的位置的时候用。双向链表也可以配合下面的其他链表的扩展使用。循环链表在一个 循环链表中, 首节点和末节点被连接在一起。这种方式在单向和双向链表中皆可实现。要转换一个循环链表,你开始于任意一个节点然后沿着列表的任一方向直到返回开始的节点。循环链表中第一个节点之前就是最后一个节点,反之亦然。循环链表的无边界使得在这样的链表上设计算法会比普通链表更加容易。另外有一种模拟的循环链表,就是在访问到最后一个节点之后的时候,手工的跳转到第一个节点。访问到第一个节点之前的时候也一样块状链表块状链表本身是一个链表,但是链表储存的并不是一般的数据,而是由这些数据组成的顺序表。每一个块状链表的节点,也就是顺序表,可以被叫做一个块。块状链表通过使用可变的顺序表的长度和特殊的插入、删除方式,可以在达到的复杂度。块状链表另一个特点是相对于普通链表来说节省内存,因为不用保存指向每一个数据节点的指针。其它扩展根据情况,也可以自己设计链表的其它扩展。但是一般不会在边上附加数据,因为链表的点和边基本上是一一对应的(除了第一个或者最后一个节点,但是也不会产生特殊情况)。不过有一个特例是如果链表支持在链表的一段中把前和后指针反向,反向标记加在边上可能会更方便。存储结构链表中的节点不需要以特定的方式存储,但是集中存储也是可以的,主要分下面这几种具体的存储方法:共享存储空间链表的节点和其它的数据共享存储空间,优点是可以存储无限多的内容(不过要处理器支持这个大小,并且存储空间足够的情况下),不需要提前分配内存;缺点是由于内容分散,有时候可能不方便调试。独立存储空间一个链表或者多个链表使用独立的存储空间,一般用数组或者类似结构实现,优点是可以自动获得一个附加数据:唯一的编号,并且方便调试;缺点是不能动态的分配内存。当然,另外的在上面加一层块状链表用来分配内存也是可以的,这样就解决了这个问题。[6]。应用链表用来构建许多其它数据结构,如堆栈,队列和他们的派生。节点的数据域也可以成为另一个链表。通过这种手段,我们可以用列表来构建许多链性数据结构;这个实例产生于Lisp编程语言,在Lisp中链表是初级数据结构,并且现在成为了常见的基础编程模式。有时候,链表用来生成联合数组,在这种情况下我们称之为联合数列。此外,还常用于组织检索较少,而删除、添加、遍历较多的数据。如果与上述情形相反,应采用其他数据结构或者与其他数据结构组合使用。C代码实例范例代码是一个ADT(抽象数据类型)双向环形链表的基本操作部分的实例(未包含线程安全机制),全部遵从ANSI C标准。一、接口声明#ifndef LLIST_H #define LLIST_H typedef void node_proc_fun_t(void*); typedef int node_comp_fun_t(const void*, const void*); typedef void LLIST_T; LLIST_T *llist_new(int elmsize); int llist_delete(LLIST_T *ptr); int llist_node_append(LLIST_T *ptr, const void *datap); int llist_node_prepend(LLIST_T *ptr, const void *datap); int llist_travel(LLIST_T *ptr, node_proc_fun_t *proc); void llist_node_delete(LLIST_T *ptr, node_comp_fun_t *comp, const void *key); void *llist_node_find(LLIST_T *ptr, node_comp_fun_t *comp, const void *key); #endif二、接口实现类型定struct node_st { void *datap; struct node_st *next, *prev; }; struct llit_st { struct node_st head; int lmsize; int elmnr; };初始化和销毁LLIST_T * llist_new(int elmsize) { struct llist_st *newlist; newlist = malloc(sizeof(struct llist_st)); if (newlist == NULL) return NULL; newlist->head.datap = NULL; newlist->head.next = &newlist->head; newlist->head.prev = &newlist->head; newlist->elmsize = elmsize; return (void *)newlist; } int llist_delete(LLIST_T *ptr) { struct llist_st *me = ptr; struct node_st *curr, *save; for (curr = me->head.next ; curr != &me->head ; curr = save) { save = curr->next; free(curr->datap); free(curr); } free(me); return 0; }节点插入int llist_node_append(LLIST_T *ptr, const void *datap) { struct llist_st *me = ptr; struct node_st *newnodep; newnodep = malloc(sizeof(struct node_st)); if (newnodep == NULL) return -1; newnodep->datap = malloc(me->elmsize); if (newnodep->datap == NULL) { free(newnodep); return -1; } memcpy(newnodep->datap, datap, me->elmsize); me->head.prev->next = newnodep; newnodep->prev = me->head.prev; me->head.prev = newnodep; newnodep->next = &me->head; return 0; } int llist_node_prepend(LLIST_T *ptr, const void *datap) { struct llist_st *me = ptr; struct node_st *newnodep; newnodep = malloc(sizeof(struct node_st)); if (newnodep == NULL) return -1; newnodep->datap = malloc(me->elmsize); if (newnodep->datap == NULL) { free(newnodep); return -1; } memcpy(newnodep->datap, datap, me->elmsize); me->head.next->prev = newnodep; newnodep->next = me->head.next; me->head.next = newnodep; newnodep->prev = &me->head; return 0; } 遍历 int llist_travel(LLIST_T *ptr, node_proc_fun_t *proc) { struct llist_st *me = ptr; struct node_st *curr; for (curr = me->head.next; curr != &me->head ; curr = curr->next) proc(curr->datap); // proc(something you like) return 0; }删除和查找void llist_node_delete(LLIST_T *ptr, node_comp_fun_t *comp, const void *key) { struct llist_st *me = ptr; struct node_st *curr; for (curr = me->head.next; curr != &me->head; curr = curr->next) { if ( (*comp)(curr->datap, key) == 0 ) { struct node_st *_next, *_prev; _prev = curr->prev; _next = curr->next; _prev->next = _next; _next->prev = _prev; free(curr->datap); free(curr); break; } } return; } void *llist_node_find(LLIST_T *ptr, node_comp_fun_t *comp, const void *key) { struct llist_st *me = ptr; struct node_st *curr; for (curr = me->head.next; curr != &me->head; curr = curr->next) { if ( (*comp)(curr->datap, key) == 0 ) return curr->datap; } return NULL; }C宏实例以下代码摘自Linux内核2.6.21.5源码(部分),展示了链表的另一种实现思路,未采用ANSI C标准,采用GNU C标准。struct list_head { struct list_head *next, *prev; }; #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; } static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) { next->prev = new; new->next = next; new->prev = prev; prev->next = new; } static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } static inline void __list_del(struct list_head *prev, struct list_head *next) { next->prev = prev; prev->next = next; } static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = NULL; entry->prev = NULL; } #define __list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) #define list_for_each_entry(pos, head, member) \ for (pos = list_entry((head)->next, typeof(*pos), member); \ prefetch(pos->member.next), &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) 百科摘录2「链表」是一种怎样的数据结构,它有什么特点?下的回答内容摘录皮皮关编程等 2 个话题下的优秀答主1、链表可以没有表示整体的对象,只需要用节点就够了。表示一整条链表可以用“头节点”,也就是第一个节点来表示整条链。 2、总是可以用一个节点的next指针走到下一个节点。而且大部分情况下只有这一种方法——链表没有下标,也不能直接跳转到某一环。只能从一个节点出发,一步一步往后挪。 3、链表的删除操作很快,与链表长度无关。想象一下:你拿着一条项链的一个环,如何拆掉这个环?只需要拆开与它相邻的两个环,然后把相邻的两环接到一起即可。无论项链长短,拆除的操作没有区别。 4、链表的插入也很快,与链表长度无关。可以用项链试一试 :) 5、链表的查找需要从头遍历,与数组类似,越长速度越慢。但由于没有下标可用,链表的遍历实际上比数组更慢一些。 看山看水不如看我 摘录于 2021-06-15「链表」是一种怎样的数据结构,它有什么特点?下的回答内容摘录SDLTF『その風車に挑戦する馬鹿は、無謀さも弱気よりも勇敢さに近い』1 链表的由来 熟悉编程的人都知道“数组”这一概念,在笔者所熟悉的C语言中,数组是一种可以连续储存同一类型值的变量类型,是一种线性表,可以O(1)访问每一个值,还可以利用“储存单元连续”这一特性做许许多多的事情。 但是数组也有一个让人恼火的缺陷:在声明的时候,长度已经固定了。例如int a[10];这句代码,他声明了一个int类型的数组a,长度为10,也就是说他只能储存10个值。这一点非常的不友好——想象一下,当你的产品发布的时候,假设你定义了一个长度为1000的数组,随着用户数量的不断扩大,可能你得经常修改这个数组的长度,重新发布新版本,这显然是一种不好的方式。由此,链表就产生了。知乎话题 摘录于 2021-07-21浏览量994 万讨论量9941 帮助中心知乎隐私保护指引申请开通机构号联系我们 举报中心涉未成年举报网络谣言举报涉企虚假举报更多 关于知乎下载知乎知乎招聘知乎指南知乎协议更多京 ICP 证 110745 号 · 京 ICP 备 13052560 号 - 1 · 京公网安备 11010802020088 号 · 京网文[2022]2674-081 号 · 药品医疗器械网络信息服务备案(京)网药械信息备字(2022)第00334号 · 广播电视节目制作经营许可证:(京)字第06591号 · 服务热线:400-919-0001 · Investor Relations · © 2024 知乎 北京智者天下科技有限公司版权所有 · 违法和不良信息举报:010-82716601 · 举报邮箱:jubao@zhihu.
链表_百度百科
度百科 网页新闻贴吧知道网盘图片视频地图文库资讯采购百科百度首页登录注册进入词条全站搜索帮助首页秒懂百科特色百科知识专题加入百科百科团队权威合作下载百科APP个人中心链表播报上传视频计算机结构术语收藏查看我的收藏0有用+10本词条由“科普中国”科学百科词条编写与应用工作项目 审核 。链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。链表可以在多种编程语言中实现。像Lisp和Scheme这样的语言的内建数据类型中就包含了链表的存取和操作。程序语言或面向对象语言,如C,C++和Java依靠易变工具来生成链表。中文名链表外文名linked list分 类计算机数据结构构 成一系列结点组成类 型计算机结构术语目录1特点2基本操作▪建立▪查找3链表函数特点播报编辑单链表,箭头末尾为结点线性表的链式存储表示的特点是用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。因此,为了表示每个数据元素 与其直接后继数据元素 之间的逻辑关系,对数据元素 来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。由这两部分信息组成一个"结点"(如概述旁的图所示),表示线性表中一个数据元素。线性表的链式存储表示,有一个缺点就是要找一个数,必须要从头开始找起,十分麻烦。根据情况,也可以自己设计链表的其它扩展。但是一般不会在边上附加数据,因为链表的点和边基本上是一一对应的(除了第一个或者最后一个节点,但是也不会产生特殊情况)。不过有一个特例是如果链表支持在链表的一段中把前和后指针反向,反向标记加在边上可能会更方便。对于非线性的链表,可以参见相关的其他数据结构,例如树、图。另外有一种基于多个线性链表的数据结构:跳表,插入、删除和查找等基本操作的速度可以达到O(平衡二叉树一样。其中存储数据元素信息的域称作数据域(设域名为data),存储直接后继存储位置的域称为指针域(设域名为next)。指针域中存储的信息又称做指针或链。由分别表示,,…,的N 个结点依次相链构成的链表,称为线性表的链式存储表示,由于此类链表的每个结点中只包含一个指针域,故又称单链表或线性链表。基本操作播报编辑(pascal语言)建立第一行读入n,表示n个数第二行包括n个数以链表的形式存储输出这些数program project1;c语言链表详解(超详细)-CSDN博客
>数据结构之链表---从入门到精通 - 知乎
链表 - OI Wiki
OI Wiki 跳转至 OI Wiki 链表 正在初始化搜索引擎 OI-wiki/OI-wiki 简介 比赛相关 工具软件 语言基础 算法基础 搜索 动态规划 字符串 数学 数据结构 图论 计算几何 杂项 专题 关于 Hulu OI Wiki OI-wiki/OI-wiki 简介 简介 Getting Started 关于本项目 如何参与 OI Wiki 不是什么 格式手册 数学符号表 F.A.Q. 用 Docker 部署 OI Wiki 镜像站列表 致谢 比赛相关 比赛相关 比赛相关简介 赛事 赛事 OI 赛事与赛制 ICPC/CCPC 赛事与赛制 题型 题型 题型概述 交互题 学习路线 学习资源 技巧 技巧 读入、输出优化 分段打表 常见错误 常见技巧 出题 工具软件 工具软件 工具软件简介 代码编辑工具 代码编辑工具 Vim Emacs VS Code Atom Eclipse Notepad++ Kate Dev-C++ CLion Geany Xcode GUIDE Sublime Text CP Editor 评测工具 评测工具 评测工具简介 Arbiter Cena CCR Plus Lemon 命令行 编译器 WSL (Windows 10) Special Judge Testlib Testlib Testlib 简介 通用 Generator Validator Interactor Checker Polygon OJ 工具 LaTeX 入门 Git 语言基础 语言基础 语言基础简介 C++ 基础 C++ 基础 Hello, World! C++ 语法基础 变量 运算 流程控制语句 流程控制语句 分支 循环 高级数据类型 高级数据类型 数组 结构体 联合体 指针 函数 文件操作 C++ 标准库 C++ 标准库 C++ 标准库简介 STL 容器 STL 容器 STL 容器简介 迭代器 序列式容器 关联式容器 无序关联式容器 容器适配器 STL 算法 bitset string pair C++ 进阶 C++ 进阶 类 命名空间 值类别 重载运算符 引用 常值 新版 C++ 特性 Lambda 表达式 pb_ds pb_ds pb_ds 简介 堆 平衡树 编译优化 C++ 与其他常用语言的区别 Pascal 转 C++ 急救 Python 速成 Java 速成 Java 进阶 算法基础 算法基础 算法基础简介 复杂度 枚举 模拟 递归 & 分治 贪心 排序 排序 排序简介 选择排序 冒泡排序 插入排序 计数排序 基数排序 快速排序 归并排序 堆排序 桶排序 希尔排序 锦标赛排序 tim排序 排序相关 STL 排序应用 前缀和 & 差分 二分 倍增 构造 搜索 搜索 搜索部分简介 DFS(搜索) BFS(搜索) 双向搜索 启发式搜索 A* 迭代加深搜索 IDA* 回溯法 Dancing Links Alpha-Beta 剪枝 优化 动态规划 动态规划 动态规划部分简介 动态规划基础 记忆化搜索 背包 DP 区间 DP DAG 上的 DP 树形 DP 状压 DP 数位 DP 插头 DP 计数 DP 动态 DP 概率 DP DP 优化 DP 优化 单调队列/单调栈优化 斜率优化 四边形不等式优化 状态设计优化 其它 DP 方法 字符串 字符串 字符串部分简介 字符串基础 标准库 字符串匹配 字符串哈希 字典树 (Trie) 前缀函数与 KMP 算法 Boyer–Moore 算法 Z 函数(扩展 KMP) 自动机 AC 自动机 后缀数组 (SA) 后缀数组 (SA) 后缀数组简介 最优原地后缀排序算法 后缀自动机 (SAM) 后缀平衡树 广义后缀自动机 后缀树 Manacher 回文树 序列自动机 最小表示法 Lyndon 分解 Main–Lorentz 算法 数学 数学 数学部分简介 符号 进位制 位运算 二进制集合操作 平衡三进制 高精度计算 快速幂 置换和排列 弧度制与坐标系 复数 数论 数论 数论基础 素数 最大公约数 数论分块 欧拉函数 筛法 Meissel–Lehmer 算法 分解质因数 裴蜀定理 类欧几里德算法 欧拉定理 & 费马小定理 乘法逆元 线性同余方程 中国剩余定理 升幂引理 威尔逊定理 卢卡斯定理 同余方程 二次剩余 原根 离散对数 剩余 莫比乌斯反演 杜教筛 Powerful Number 筛 Min_25 筛 洲阁筛 连分数 Stern–Brocot 树与 Farey 序列 二次域 循环连分数 Pell 方程 多项式与生成函数 多项式与生成函数 多项式与生成函数简介 代数基本定理 快速傅里叶变换 快速数论变换 快速沃尔什变换 Chirp Z 变换 多项式牛顿迭代 多项式多点求值|快速插值 多项式初等函数 常系数齐次线性递推 多项式平移|连续点值平移 符号化方法 普通生成函数 指数生成函数 狄利克雷生成函数 组合数学 组合数学 排列组合 抽屉原理 容斥原理 康托展开 斐波那契数列 错位排列 卡特兰数 斯特林数 贝尔数 伯努利数 Entringer Number Eulerian Number 分拆数 范德蒙德卷积 图论计数 线性代数 线性代数 线性代数简介 向量 内积和外积 矩阵 初等变换 行列式 线性空间 线性基 线性映射 特征多项式 对角化 Jordan标准型 线性规划 线性规划 线性规划简介 单纯形算法 群论 群论 群论简介 置换群 概率论 概率论 基本概念 条件概率与独立性 随机变量 随机变量的数字特征 概率不等式 博弈论 博弈论 博弈论简介 公平组合游戏 非公平组合游戏 反常游戏 数值算法 数值算法 插值 数值积分 高斯消元 牛顿迭代法 傅里叶-莫茨金消元法 序理论 杨氏矩阵 Schreier–Sims 算法 Berlekamp–Massey 算法 数据结构 数据结构 数据结构部分简介 栈 队列 链表 链表 目录 引入 与数组的区别 构建链表 单向链表 双向链表 向链表中插入(写入)数据 单向链表 单向循环链表 双向循环链表 从链表中删除数据 单向(循环)链表 双向循环链表 技巧 异或链表 哈希表 并查集 并查集 并查集 并查集复杂度 堆 堆 堆简介 二叉堆 配对堆 左偏树 块状数据结构 块状数据结构 分块思想 块状数组 块状链表 树分块 Sqrt Tree 单调栈 单调队列 ST 表 树状数组 线段树 李超线段树 区间最值操作 & 区间历史最值 划分树 二叉搜索树 & 平衡树 二叉搜索树 & 平衡树 二叉搜索树 & 平衡树 Treap Splay 树 WBLT Size Balanced Tree AVL 树 B 树 B+ 树 替罪羊树 Leafy Tree 笛卡尔树 红黑树 左偏红黑树 AA 树 2-3 树 2-3-4 树 跳表 可持久化数据结构 可持久化数据结构 可持久化数据结构简介 可持久化线段树 可持久化块状数组 可持久化平衡树 可持久化字典树 可持久化可并堆 树套树 树套树 线段树套线段树 平衡树套线段树 线段树套平衡树 树状数组套权值线段树 分块套树状数组 K-D Tree 动态树 动态树 Link Cut Tree 全局平衡二叉树 Euler Tour Tree Top Tree 析合树 PQ 树 手指树 霍夫曼树 图论 图论 图论部分简介 图论相关概念 图的存储 DFS(图论) BFS(图论) 树上问题 树上问题 树基础 树的直径 最近公共祖先 树的重心 树链剖分 树上启发式合并 虚树 树分治 动态树分治 AHU 算法 树哈希 树上随机游走 矩阵树定理 有向无环图 拓扑排序 最小生成树 斯坦纳树 最小树形图 最小直径生成树 最短路 拆点 差分约束 k 短路 同余最短路 连通性相关 连通性相关 强连通分量 双连通分量 割点和桥 圆方树 点/边连通度 环计数问题 2-SAT 欧拉图 哈密顿图 二分图 最小环 平面图 图的着色 网络流 网络流 网络流简介 最大流 最小割 费用流 上下界网络流 Stoer–Wagner 算法 图的匹配 图的匹配 图匹配 增广路 二分图最大匹配 二分图最大权匹配 一般图最大匹配 一般图最大权匹配 Prüfer 序列 LGV 引理 弦图 最大团搜索算法 支配树 图上随机游走 计算几何 计算几何 计算几何部分简介 二维计算几何基础 三维计算几何基础 距离 Pick 定理 三角剖分 凸包 扫描线 旋转卡壳 半平面交 平面最近点对 随机增量法 反演变换 计算几何杂项 杂项 杂项 杂项简介 离散化 双指针 离线算法 离线算法 离线算法简介 CDQ 分治 整体二分 莫队算法 莫队算法 莫队算法简介 普通莫队算法 带修改莫队 树上莫队 回滚莫队 二维莫队 莫队二次离线 莫队配合 bitset 分数规划 随机化 随机化 随机函数 随机化技巧 爬山算法 模拟退火 悬线法 计算理论基础 字节顺序 约瑟夫问题 格雷码 表达式求值 在一台机器上规划任务 主元素问题 Garsia–Wachs 算法 15-puzzle Kahan 求和 珂朵莉树/颜色段均摊 专题 专题 RMQ 并查集应用 括号序列 线段树与离线询问 关于 Hulu 关于 Hulu 关于 Hulu 目录 引入 与数组的区别 构建链表 单向链表 双向链表 向链表中插入(写入)数据 单向链表 单向循环链表 双向循环链表 从链表中删除数据 单向(循环)链表 双向循环链表 技巧 异或链表 链表本页面将简要介绍链表。引入链表是一种用于存储数据的数据结构,通过如链条一般的指针来连接元素。它的特点是插入与删除数据十分方便,但寻找与读取数据的表现欠佳。与数组的区别链表和数组都可用于存储数据。与链表不同,数组将所有元素按次序依次存储。不同的存储结构令它们有了不同的优势:链表因其链状的结构,能方便地删除、插入数据,操作次数是 。但也因为这样,寻找、读取数据的效率不如数组高,在随机访问数据中的操作次数是 。数组可以方便地寻找并读取数据,在随机访问中操作次数是 。但删除、插入的操作次数是 次。构建链表Tip 构建链表时,使用指针的部分比较抽象,光靠文字描述和代码可能难以理解,建议配合作图来理解。单向链表单向链表中包含数据域和指针域,其中数据域用于存放数据,指针域用来连接当前结点和下一节点。实现 C++Python1链表的创建及基本操作(增、删、改、查)_链表创建-CSDN博客
>「数据结构与算法」链表 —— 看这一篇就够了(超详细) - 知乎
数据结构与算法 | 链表(Linked List)-腾讯云开发者社区-腾讯云
与算法 | 链表(Linked List)-腾讯云开发者社区-腾讯云Java研究者数据结构与算法 | 链表(Linked List)原创关注作者腾讯云开发者社区文档建议反馈控制台首页学习活动专区工具TVP最新优惠活动文章/答案/技术大牛搜索搜索关闭发布登录/注册首页学习活动专区工具TVP最新优惠活动返回腾讯云官网Java研究者首页学习活动专区工具TVP最新优惠活动返回腾讯云官网社区首页 >专栏 >数据结构与算法 | 链表(Linked List)数据结构与算法 | 链表(Linked List)原创Java研究者关注发布于 2023-10-19 10:27:085891发布于 2023-10-19 10:27:08举报文章被收录于专栏:数据结构与算法数据结构与算法链表(Linked List)是一种线性数据结构,它由一系列节点(Node)组成,每个节点包含两部分:数据和指向下(上)一个节点的引用(或指针)。链表中的节点按照线性顺序连接在一起(相邻节点不需要存储在连续内存位置),不像数组一样存储在连续的内存位置。链表通常由头节点(Head)来表示整个链表,而尾节点的下一个节点指向null,表示链表的结束。链表有几种常见的类型,其中最常见的包括单链表、双链表。 // Java LinkedList 中Node的结构链表 - 知乎