数据结构————单链表

news2024/9/19 23:47:41

目录

一、单链表的定义及其特点

定义

特点

二、单链表的实现

  准备工作:

1.单链表的创建

1.1头插法介绍

1.2尾插法介绍

总结:

2.单链表的初始化

3.单链表的求表长

4.单链表的销毁

5.单链表的插入

6.单链表的查找

 6.1按序查找

6.2按值查找

7.单链表的删除元素

全部代码:

结语: 


一、单链表的定义及其特点

定义

  单链表(Singly Linked List)是一种基本的数据结构,它由一系列节点组成,每个节点包含两部分:数据域和指针域。数据域用于存储实际的数据,而指针域则存储指向链表中下一个节点的地址。在单链表中,除了最后一个节点外,每个节点的指针域都指向下一个节点,最后一个节点的指针域通常设置为NULL,以标识链表的结束。单链表的特点是其存储不需要连续的内存空间,节点可以分散在内存中的任意位置,通过指针连接形成线性结构。 

特点

  1. 动态存储:单链表的长度可以动态变化,可以根据需要动态地添加或删除节点。

  2. 非连续存储:节点在内存中的位置不需要连续,只需通过指针连接即可。

  3. 易于插入和删除:在单链表中插入或删除节点只需修改相应节点的指针,时间复杂度为O(1),但前提是能够快速访问到要操作的节点或其前驱节点。

  4. 不支持随机访问:访问单链表中的特定节点需要从头节点开始逐个遍历,时间复杂度为O(n)。

  5. 额外空间存储指针:每个节点除了存储数据外,还需要额外的空间来存储指向下一个节点的指针。

  6. 头指针:单链表通常有一个头指针,用于访问链表的第一个节点。头指针可能指向头节点(头节点包含数据)或直接指向第一个实际数据节点(头节点不存储数据)。 

二、单链表的实现

  准备工作:

自定义数据元素类型:

typedef int ElemType;

结构体:

typedef struct LNode
{
	ElemType data;//数据域
	struct LNode* next;//指针域
}LinkNode;

1.单链表的创建

单链表的创建通常涉及两种主要的插入方法:头插法和尾插法。

1.1头插法介绍

定义

  头插法指的是在单链表的头部插入节点。新节点的指针将指向当前的头节点,然后更新头指针以指向新节点。

特点

  • 实现简单,插入操作时间复杂度为O(1)。
  • 但链表元素的顺序与插入顺序相反,如果需要保持原有顺序,需要在插入后进行反转

大致实现步骤:

  1. 创建新节点。
  2. 将新节点的next指针设置为头指针所指向的节点。
  3. 更新头指针,使其指向新节点。

代码:整体建立单链表,给定数组

void CreateListF(LinkNode *&L,ElemType a[],int n)
{
	LinkNode * s;  //新节点
	L = (LinkNode*)malloc(sizeof(LinkNode));//创建头节点
	L->next = NULL;//头节点初始化

	for (int i = 0; i < n; i++)
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));//新节点创建
		s->data = a[i];// 初始化
		s->next = L->next;
		L->next = s;
	}
}

1.2尾插法介绍

定义

  尾插法指的是在单链表的尾部插入节点。此方法下,新节点成为链表的最后一个节点。

特点

  • 插入操作的时间复杂度为O(n),因为可能需要遍历整个链表。
  • 保持了数据的插入顺序,链表元素的顺序与插入顺序一致。

大致实现步骤:

  1. 创建新节点。
  2. 如果链表为空,新节点同时作为头节点和尾节点。
  3. 如果链表非空,遍历链表找到尾节点,将尾节点的next指针指向新节点。

代码:整体建立单链表,给定数组

//<2>尾插法建立单链表
void CreateListR(LinkNode*& L, ElemType a[], int n)
{
	LinkNode* r, * s;//尾指针和新的节点指针
	L = (LinkNode*)malloc(sizeof(LinkNode));
	r = L;   //r始终指向尾节点,初始指向L
	for (int i = 0; i < n; i++)
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));//新节点创建和初始化
		s->data = a[i];
		r->next = s;//将节点s插入到r后面
		r = s;
	}
	r->next = NULL;  //尾节点的next域置为NULL
}

总结:

 头插法和尾插法各有优劣,选择哪种方法取决于具体的应用场景。如果需要快速插入节点且不关心元素的顺序,头插法是更好的选择。反之,如果要保持元素的插入顺序,尾插法则更为合适。在实际应用中,根据需求灵活选择插入方法,可以更高效地创建和管理单链表。

2.单链表的初始化

  创建一个头结点,并将其头结点的next域置空

void  InitList(LinkNode*&L)
{
	L = (LinkNode*)malloc(sizeof(LinkNode));
	L->next = NULL;
}

3.单链表的求表长

 用p指向头结点,用n来累计数据节点的个数,n初始值为0;

int ListLength(LinkNode *L)
{
	int n = 0;
	LinkNode* p = L;
	while (p->next != NULL)
	{
		n++;
		p = p->next;
	}
	return n;
}

4.单链表的销毁

实现步骤:

1.  pre 和 p指向相邻的节点,初始pre 指向头结点,p指向首个数据节点。

2.p不为空时循环,释放pre节点,然后pre 和p 俩个节点后移。

3. 循环结束后pre指向尾节点,将其释放

代码:

/*【3】基本运算 ----销毁*/
void DestoryList(LinkNode*& L)
{
	LinkNode* pre = L;
	LinkNode* p = L->next;//pre指向p的前驱节点
	while (p != NULL)  //遍历单链表L
	{
		free(pre);//释放前驱节点
		pre = p;  //移动
		p = pre->next; 
	}
	free(pre);  //循环结束,p为NULLL,pre指向尾节点,释放它
}

5.单链表的插入

 给定单链表L ,在第i个位置插入节点s,值为e。

实现步骤:

1.在单链表L中找到第i-1个结点,由p指向他

2.若存在该结点,将值为e的s结点插入p节点的后面。

代码:

/*【7】基本运算 ----插入   在第i个位置前面插入节点, p指向第i-1个节点,将s节点插入p后面 */  
bool ListInsert(LinkNode*& L, int i, ElemType e)
{
	int j = 0;
	if (i <= 0)return false;
	LinkNode* s, * p=L;
	while (j < i-1 && p != NULL)//寻找i-1的节点,然后由p指向它
	{
		j++;
		p = p->next;
	}
	if (p == NULL)//未找到·返回false
	{
		return  false;
	}
	else
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));
		s->data = e;
		s->next = p->next;//将新节点插入到p之后(i-1的节点)
		p->next = s;
		return true;
	}
}

6.单链表的查找

 6.1按序查找

实现步骤:

1. p指向头结点,用j来累计遍历过的数据结点的个数(初始值为0),当j<i且不为空时循环:j增1,p指向下一个结点。

2. 循环结束后有两种情况,若为空,表示单链表L中没有第i个数据结点(参数i错误),返回false;否则找到第i个数据结点力,提取它的值并返回true。

代码:

/<2>按序号求线性表中的元素,给定单链表L,查找第i个节点的元素值,提取到e,存在返回true 否则返回false
bool GetElem(LinkNode* L, int i, ElemType& e)
{
	LinkNode* p = L;//p指向头结点i,j置为0,即头节点序号为0
	int j = 0;
	if (i < 0)return false;
	while (j < i && p != NULL)
	{
		j++;
		p = p->next;
	}
	if (p == NULL)//不存在第 i 个数据节点,返回false
	{
		return false;
	}
	else//存在,取值 并返回true
	{
		e = p->data;
		return true;
	}
}

6.2按值查找

 在单链表L中从头开始找到第一个值域为e的结点,找到返回逻辑序号,否则返回0

实现步骤: 

代码:

//<1>按元素查找节点的序号  给定单链表 L 查找值为e的节点的序号i,存在返回该序号,否则返回0
int  LocateElem(LinkNode* L, ElemType e)
{
	LinkNode* p = L->next;
	int n = 1;  //逻辑序号,首节点位置为1
	while (p->next != NULL && p->data != e)//查找该节点
	{
		n++;
		p = p->next;
	}
	if (p == NULL)//不存在元素为e的节点
	{
		return 0;
	}
	else   //存在,返回逻辑序号n
	{
		return n;
	}
}

7.单链表的删除元素

  给定单链表L,删除第i个节点,并将删除节点的值带回,p指向第i-1个节点,q为第i个节点,*/

实现步骤:

1.在单链表L中找到第i一1个结点,由p指向它。

2.若存在这样结点,且也存在后继结点(由q指向它),则删除通过结点pq所指的结点,返回true;否则回false,表示参数i错误。

代码:

/*【8】基本运算  ---- 删除  ,给定L,删除第i个节点,并将删除节点的值带回p指向第i-1个节点,q为第i个节点,*/
bool ListDelete(LinkNode*& L, int i, ElemType& e)
{
	int j = 0;
	LinkNode* q, * p = L;
	if (i <= 0)return false;
	while (j < i - 1 && p != NULL)
	{
		j++;
		p = p->next;
	}
	if (p == NULL)//没有找到第i-1个节点 返回false
	{
		return false;
	}
	else//找到第i-1和节点 ,进行删除操作
	{
		q = p->next;//q指向第i个节点
		if (q == NULL)return false;  //这个容易忽略,我们也因该判断第i个元素是否为空
		e = q->data;
		p->next = q->next;
		free(q);
		return true;
	}
}

全部代码:

#include<stdlib.h>
#include<stdio.h>

typedef int ElemType;

typedef struct LNode
{
	ElemType data;//数据域
	struct LNode* next;//指针域
}LinkNode;

/*【1】整体建立单链表  给定数组a[n] */
//<1>头插法建立单链表
void CreateListF(LinkNode *&L,ElemType a[],int n)
{
	LinkNode * s;  //新节点
	L = (LinkNode*)malloc(sizeof(LinkNode));//创建头节点
	L->next = NULL;//头节点初始化

	for (int i = 0; i < n; i++)
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));//新节点创建
		s->data = a[i];// 初始化
		s->next = L->next;
		L->next = s;
	}
}
//<2>尾插法建立单链表
void CreateListR(LinkNode*& L, ElemType a[], int n)
{
	LinkNode* r, * s;//尾指针和新的节点指针
	L = (LinkNode*)malloc(sizeof(LinkNode));
	r = L;   //r始终指向尾节点,初始指向L
	for (int i = 0; i < n; i++)
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));//新节点创建和初始化
		s->data = a[i];
		r->next = s;//将节点s插入到r后面
		r = s;
	}
	r->next = NULL;  //尾节点的next域置为NULL
}
/*【2】基本运算 ----初始化*/
void  InitList(LinkNode*&L)
{
	L = (LinkNode*)malloc(sizeof(LinkNode));
	L->next = NULL;
}
/*【3】基本运算 ----销毁*/
void DestoryList(LinkNode*& L)
{
	LinkNode* pre = L;
	LinkNode* p = L->next;//pre指向p的前驱节点
	while (p != NULL)  //遍历单链表L
	{
		free(pre);//释放前驱节点
		pre = p;  //移动
		p = pre->next; 
	}
	free(pre);  //循环结束,p为NULLL,pre指向尾节点,释放它
}
/*【4】基本运算 ----求表的长度*/
int ListLength(LinkNode *L)
{
	int n = 0;
	LinkNode* p = L;
	while (p->next != NULL)
	{
		n++;
		p = p->next;
	}
	return n;
}
/*【5】基本运算 ----输出线性表*/
void printLinkNode(LinkNode* L)
{
	LinkNode* p = L->next;//指向头节点的下一个节点,头节点不存放数据
	while (p!= NULL)
	{
		printf("%d->", p->data);
		p = p->next;
	}
	printf("end\n");
}
/*【6】基本运算 ----查找 */
//<1>按元素查找节点的序号  给定单链表 L 查找值为e的节点的序号i,存在返回该序号,否则返回0
int  LocateElem(LinkNode* L, ElemType e)
{
	LinkNode* p = L->next;
	int n = 1;  //逻辑序号,首节点位置为1
	while (p->next != NULL && p->data != e)//查找该节点
	{
		n++;
		p = p->next;
	}
	if (p == NULL)//不存在元素为e的节点
	{
		return 0;
	}
	else   //存在,返回逻辑序号n
	{
		return n;
	}
}
//<2>按序号求线性表中的元素,给定单链表L,查找第i个节点的元素值,提取到e,存在返回true 否则返回false
bool GetElem(LinkNode* L, int i, ElemType& e)
{
	LinkNode* p = L;//p指向头结点i,j置为0,即头节点序号为0
	int j = 0;
	if (i < 0)return false;
	while (j < i && p != NULL)
	{
		j++;
		p = p->next;
	}
	if (p == NULL)//不存在第 i 个数据节点,返回false
	{
		return false;
	}
	else//存在,取值 并返回true
	{
		e = p->data;
		return true;
	}
}
/*【7】基本运算 ----插入   在第i个位置前面插入节点, p指向第i-1个节点,将s节点插入p后面 */  
bool ListInsert(LinkNode*& L, int i, ElemType e)
{
	int j = 0;
	if (i <= 0)return false;
	LinkNode* s, * p=L;
	while (j < i-1 && p != NULL)//寻找i-1的节点,然后由p指向它
	{
		j++;
		p = p->next;
	}
	if (p == NULL)//未找到·返回false
	{
		return  false;
	}
	else
	{
		s = (LinkNode*)malloc(sizeof(LinkNode));
		s->data = e;
		s->next = p->next;//将新节点插入到p之后(i-1的节点)
		p->next = s;
		return true;
	}
}
/*【8】基本运算  ---- 删除  ,给定L,删除第i个节点,并将删除节点的值带回p指向第i-1个节点,q为第i个节点,*/
bool ListDelete(LinkNode*& L, int i, ElemType& e)
{
	int j = 0;
	LinkNode* q, * p = L;
	if (i <= 0)return false;
	while (j < i - 1 && p != NULL)
	{
		j++;
		p = p->next;
	}
	if (p == NULL)//没有找到第i-1个节点 返回false
	{
		return false;
	}
	else//找到第i-1和节点 ,进行删除操作
	{
		q = p->next;//q指向第i个节点
		if (q == NULL)return false;  //这个容易忽略,我们也因该判断第i个元素是否为空
		e = q->data;
		p->next = q->next;
		free(q);
		return true;
	}
}
int main()
{
	int a[5];
	int b[5] = { 1,2,3,4,5 };
	for (int i = 0; i < 5; i++)
	{
		scanf_s("%d", &a[i]);
	}

	//用数组创建单链表
	LinkNode * L1;
	LinkNode * L2;
	CreateListF(L1, a,5); //头插法
	CreateListR(L2, b,5);//尾插法
	printLinkNode(L1);//输出单链表
	printLinkNode(L2);//输出单链表


	//按值查找,返回序号
	printf("L2 's number 5 is in the %d position\n", LocateElem(L2, 5));
	printf("L2 's number 3 is in the %d position\n", LocateElem(L2, 3));
	//按序号查找元素值
	int e1, e2;
	if(GetElem(L1,5,e1))
	{
		printf("The value at the 5 position of the L1 is %d\n", e1);
	}
	if (GetElem(L1, 1, e2))
	{
		printf("The value at the 1 position of the L1 is %d\n", e2);
	}


	//插入元素,插入到第i 个位置
	ListInsert(L1,5,6);
	printLinkNode(L1);//输出单链表

	ListInsert(L2, 1, 2);
	printLinkNode(L2);//输出单链表
	//删除元素
	int e = 0;
	if (ListDelete(L1,5,e))
	{
		printf("删除成功,删除的值为%d\n",e);
	}
	else
	{
		printf("删除失败,节点不存在");
	}
	printLinkNode(L1);//输出单链表

	DestoryList(L1);
	DestoryList(L2);
	return 0;
}

结语: 

  单链表的实现,不仅是一次理论与实践的完美结合,更是一个程序员逻辑思维与动手能力的双重考验。通过对单链表的深入理解与实现,我们不仅掌握了数据结构的精髓,更在编程的道路上迈出了坚实的一步。未来,无论是面对复杂的数据处理,还是高效算法的设计,单链表都将成为我们手中得心应手的工具,助力我们在编程的世界里自由翱翔。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2135071.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UDP聊天室项目

代码思路 服务器 #include <stdio.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h>…

每周心赏|极致浪漫,这些中秋仪式感AI住了

盈月揽星辉&#xff0c;万家赴团圆。✨ 月光散尽&#xff0c;总是带着一丝温柔的凉意。 &#x1f342;清风拨弦&#xff0c;总是留存着诗意的古韵悠扬。 千年前&#xff0c;李白对月独酌&#xff0c;苏轼对影成双&#xff0c; 千年后&#xff0c;文心智能体便利店与你穿越时…

adb的安装和使用 以及安装Frida 16.0.10+雷电模拟器

.NET兼职社区 .NET兼职社区 .NET兼职社区 1.下载adb Windows版本&#xff1a;https://dl.google.com/android/repository/platform-tools-latest-windows.zip 2.配置adb环境变量 按键windowsr打开运行&#xff0c;输入sysdm.cpl&#xff0c;回车。 高级》环境变量》系统变量》…

【C++】_stack和_queue容器适配器、_deque

当别人都在关注你飞的有多高的时候&#xff0c;只有父母在关心你飞的累不累。&#x1f493;&#x1f493;&#x1f493; 目录 ✨说在前面 &#x1f34b;知识点一&#xff1a;stack •&#x1f330;1.stack介绍 •&#x1f330;2.stack的基本操作 &#x1f34b;知识点二&…

电脑之间如何快速传大文件?

这里&#xff0c;为大家介绍一款免费的远程桌面软件——远程看看&#xff0c;该软件不仅支持远程控制&#xff0c;还提供了文件传输功能&#xff0c; 用户可以传输单个文件大小不超过2TB&#xff0c;且传输速度可达每秒10MB。若您不知道电脑之间如何快速传大文件&#xff0c;远…

城市交通标线检测系统源码分享

城市交通标线检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer…

redis基本数据类型和常见命令

引言 Redis是典型的key-value&#xff08;键值型&#xff09;数据库&#xff0c;key一般是字符串&#xff0c;而value包含很多不同的数据类型&#xff1a; Redis为了方便我们学习&#xff0c;将操作不同数据类型的命令也做了分组&#xff0c;在官网&#xff08; Commands | Do…

Kafka 基于SASL/SCRAM动态认证部署,kafka加账号密码登录部署

文章目录 前言下载 kafka安装启动zookeeper添加账号密码 启动kafka修改kafka配置文件增加jaas授权文件修改启动文件&#xff0c;启动kafka检查是否部署成功 offset explore 连接 前言 其实挺简单的几个配置文件&#xff0c;问大模型一直没说到点上&#xff0c;绕晕了。SASL/SC…

Vue3.0组合式API:setup()函数

1、什么是组合式API Vue 3.0 中新增了组合式 API 的功能&#xff0c;它是一组附加的、基于函数的 API&#xff0c;可以更加灵活地组织组件代码。通过组合式 API 可以使用函数而不是声明选项的方式来编写 Vue 组件。因此&#xff0c;使用组合式 API 可以将组件代码编写为多个函…

浅谈EXT2文件系统----超级块

超级块概述 在 EXT2 文件系统中&#xff0c;超级块&#xff08;superblock&#xff09;是一个非常重要的数据结构&#xff0c;包含了文件系统的全局信息。每个文件系统都有一个超级块&#xff0c;位于文件系统的第一个块之后&#xff0c;通常在块组的起始处。 超级块包含以下关…

Autosar模式管理实战系列-COMM模块状态机及重要函数讲解

1.Channel状态管理 上一节提到ComM进行通信模式管理提供有两大状态机,另外一个就是Channel状态管理。这里的Channel指的是一个通信总线,目前项目主要是采用CAN总线。ComM 模块对每一个Channel都定义了一个状态机,用于描述通道的各种状态、状态转移关系和状态转移动作。该状…

NFT Insider #147:Sandbox 人物化身九月奖励上线;Catizen 付费用户突破百万

市场数据 加密艺术及收藏品新闻 Doodles 动画特别剧《Dullsville and The Doodleverse》在多伦多国际电影节首映 Doodles 最近在多伦多国际电影节&#xff08;TIFF&#xff09;首映了其动画特别剧《Dullsville and The Doodleverse》&#xff0c;这是该品牌的一个重要里程碑。…

享元模式详解:解锁高效资源管理的终极武器

&#x1f3af; 设计模式专栏&#xff0c;持续更新中 欢迎订阅&#xff1a;JAVA实现设计模式 &#x1f6e0;️ 希望小伙伴们一键三连&#xff0c;有问题私信都会回复&#xff0c;或者在评论区直接发言 享元模式 享元模式&#xff08;Flyweight Pattern&#xff09; 是一种结构型…

yaml配置文件(SpringBoot学习4)

SpringBoot使用一个全局的配置文件&#xff0c;配置文件名是固定的 application.properties 语法结构&#xff1a;keyvalue application.yaml 语法结构&#xff1a;key:空格value #是注释 #yaml 普通的key - value 例&#xff1a;name&#xff1a;…

力扣题解2390

大家好&#xff0c;欢迎来到无限大的频道。 今日继续给大家带来力扣题解。 题目描述​&#xff08;中等&#xff09;&#xff1a; 从字符串中移除星号 给你一个包含若干星号 * 的字符串 s 。 在一步操作中&#xff0c;你可以&#xff1a; 选中 s 中的一个星号。 移除星号…

23. Revit API: 几何对象(四)- BrepBuilder

一、前言 上一篇写了Solid的创建、展示、变换、布尔操作&#xff0c;这一篇写另一种Solid的创建方法。 需要再次强调的是&#xff0c;BrepBuilder不适合用来创建Solid。 二、边界表示 【Wiki】&#xff1a;边界表示&#xff08;Boundary representation&#xff0c;简称B-Re…

顶刊算法 | 鹈鹕算法POA-Transformer-LSTM多变量回归预测

顶刊算法 | 鹈鹕算法POA-Transformer-LSTM多变量回归预测 目录 顶刊算法 | 鹈鹕算法POA-Transformer-LSTM多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现顶刊算法 | 鹈鹕算法POA-Transformer-LSTM多变量回归预测&#xff08;程序可以作为JCR…

外贸展会全流程、如何高效转化,获取更多名片

马上又到展会季了&#xff0c;九十月份是各行各业的展会旺季&#xff0c;很多伙伴们比较关注的是&#xff0c;展会上如何拿到更多名片&#xff0c;如何高效转化客户 一、展会的好处 1、扩展客户资源&#xff1a;接触潜在客户、扩大市场覆盖 2、提升品牌知名度&#xff1a;展示…

学生学籍管理系统可行性分析报告

引言 一、编写目的 随着科学技术的不断提高,计算机科学日渐成熟,其强大的功能已为人们深刻认识,它已进入人类社会的各个领域并发挥着越来越重要的作用。而学籍管理系统软件&#xff0c;可广泛应用于全日制大、中小学及其他各类学校&#xff0c;系统涵盖了小学、初中、高中学籍…

122.rk3399 uboot(2017.09) 源码分析2-initf_dm(2024-09-09)

这里接着上一篇来吧&#xff1a; https://blog.csdn.net/zhaozhi0810/article/details/141927053 本文主要是dm_init_and_scan函数的分析&#xff0c;这个内容比较复杂&#xff0c;我也是第一次阅读&#xff0c;错误之处在所难免&#xff0c;请多指教。 uboot的dm框架需要了解…