C语言数据结构(链表概念讲解和插入操作)

news2024/11/26 9:29:59

文章目录

  • 前言
  • 一、什么是链表
  • 二、链表的优点和缺点
  • 三、链表节点的定义
  • 四、初始化链表
  • 五、链表的插入
    • 1.头部插入
    • 2.尾部插入
    • 3.中间插入
  • 六、遍历链表
  • 七、释放链表
  • 总结


前言

本篇文章带大家正式的来学习数据结构,数据结构是学习操作系统,和深入C语言必不可少的,所以这篇文章开始带大家学习数据结构的知识。

一、什么是链表

链表(Linked List)是一种常见的数据结构,用于存储和组织数据元素。它由一系列节点(Node)组成,每个节点包含存储的数据(或称为元素/值)以及指向下一个节点的引用(或链接/指针)。

链表中的节点可以通过指针连接在一起,形成一个链式结构,而不像数组那样在内存中连续存储。每个节点只需要存储指向下一个节点的引用,而不需要预先分配固定大小的内存空间。这使得链表具有动态性,能够高效地插入、删除节点,但对于随机访问则效率较低。

链表分为单向链表(Singly Linked List)和双向链表(Doubly Linked List)两种常见类型。

在单向链表中,每个节点的引用指向下一个节点,最后一个节点的引用为空(null)。从头节点(首节点)开始遍历链表,通过一次次跳转到下一个节点来访问或操作数据。

双向链表在单向链表的基础上添加了一个指向前一个节点的引用。这使得双向链表可以从头节点和尾节点两个方向遍历,增加了一些操作的灵活性,但也增加了一些额外的空间开销。

链表适用于频繁的插入和删除操作,而不需要频繁的随机访问。它在内存使用上可以比数组更灵活,但在访问速度上相对较慢。链表常用于实现其他高级数据结构,如队列、栈和图等。

需要注意的是,链表的节点并不需要连续的内存空间,可以在内存中分散存储。这与数组的存储方式不同,数组在内存中需要一段连续的空间来存储所有元素。这是链表与数组的主要区别之一。

二、链表的优点和缺点

链表的优点包括:

1.动态性:链表的大小可以根据需要动态地增长或缩小,不像数组需要预先分配固定大小的空间。

2.插入和删除操作高效:由于链表中的节点仅需修改相邻节点的指针,插入和删除节点的操作复杂度为O(1),效率很高。

3.内存利用率高:链表节点可以在内存中分散存储,可以充分利用零散的空间。

4.支持灵活的数据结构:链表可以用于实现其他高级数据结构,如队列、栈和图等。

链表的缺点包括:

1.随机访问效率低:由于链表的内存存储不是连续的,要访问特定位置的节点,必须从头节点开始遍历,直到达到目标位置,这使得随机访问的效率较低。时间复杂度为O(n)。

2.额外的存储空间:链表需要额外的指针来维护节点之间的连接关系,这会占用一定的存储空间。

3.不支持随机访问:由于链表的节点间没有直接的索引关系,不能像数组那样通过索引快速访问任意位置的节点。

4.需要遍历整个链表:若要访问链表中的所有节点,需要从头节点开始遍历整个链表,这会增加一定的时间开销。

综上所述,链表适合在插入和删除操作较频繁、对随机访问要求较低的场景下使用,而在需要频繁的随机访问和占用连续内存空间的场景中,数组可能是更好的选择。选择使用链表或数组取决于具体的应用需求和优化目标。

三、链表节点的定义

链表节点(Node)是链表中的基本组成单元,包含存储的数据(或称为元素/值)以及指向下一个节点(或前一个节点)的引用。

基本的链表节点定义通常包括以下成员:

1.数据(Data):节点存储的数据或元素值。

2.下一个节点引用(Next):指向链表中下一个节点的引用。在单向链表中,该引用指向链表中的后继节点;在双向链表中,该引用则指向链表中的下一个节点。

3.(可选)前一个节点引用(Previous):仅在双向链表中存在,指向链表中的前一个节点。

// 定义链表节点结构体
struct Node {
    int data;          // 节点存储的数据
    struct Node* next; // 指向下一个节点的指针
};

四、初始化链表

在C语言中,初始化链表通常需要创建一个头节点并将其置为NULL,表示链表为空。

#include <stdio.h>
#include <malloc.h>

/* 定义链表节点 */
typedef struct node
{
	int data;          // 节点存储的数据
	struct node* PNext; // 指向下一个节点的指针
}Node;

/* 初始化链表 */
Node* InitList(void)
{
	Node* head = (Node*)malloc(sizeof(Node));
	if (head != NULL)
	{
		head->data = 0;
		head->PNext = NULL;
	}
	return head;
}

int main(void)
{
	Node* Head = InitList();

	return 0;
}

五、链表的插入

1.头部插入

当我们在链表的头部插入节点时,意味着我们要将一个新的节点插入到链表的开头位置。
首先需要使用malloc函数来分配一个节点,有了节点后我们才可以进行插入操作。
进行头部插入只需要将新分配的节点的PNext指向原来的头指针,然后再将头指针指向新的节点。
这个时候新的节点就成为了头节点。

#include <stdio.h>
#include <malloc.h>

/* 定义链表节点 */
typedef struct node
{
	int data;          // 节点存储的数据
	struct node* PNext; // 指向下一个节点的指针
}Node;

/* 初始化链表 */
Node* InitList(void)
{
	Node* head = (Node*)malloc(sizeof(Node));
	if (head != NULL)
	{
		head->data = 0;
		head->PNext = NULL;
	}
	return head;
}

/* 头部插入 */
void HeadAdd(Node** HeadNode, int data)
{
	Node* NewNode = (Node*)malloc(sizeof(Node));
	if (NewNode != NULL)
	{
		NewNode->data = data;
		NewNode->PNext = *HeadNode;
		*HeadNode = NewNode;
	}
}

int main(void)
{
	Node* Head = InitList();
	HeadAdd(&Head, 1);

	printf("%d\n", Head->data);//打印1
	printf("%d\n", Head->PNext->data);//打印0

	free(Head);

	return 0;
}


这里使用一张图来帮助大家理解:
在这里插入图片描述

2.尾部插入

尾部插入要比头部插入稍微难一点,因为需要先找到最后的一个节点,然后将最后一个节点的下一个指针指向新的节点,这里就涉及到了链表的遍历了。我们放下面讲解。

#include <stdio.h>
#include <malloc.h>

/* 定义链表节点 */
typedef struct node
{
	int data;          // 节点存储的数据
	struct node* PNext; // 指向下一个节点的指针
}Node;

/* 初始化链表 */
Node* InitList(void)
{
	Node* head = (Node*)malloc(sizeof(Node));
	if (head != NULL)
	{
		head->data = 0;
		head->PNext = NULL;
	}
	return head;
}

/* 头部插入 */
void HeadAdd(Node** HeadNode, int data)
{
	Node* NewNode = (Node*)malloc(sizeof(Node));
	if (NewNode != NULL)
	{
		NewNode->data = data;
		NewNode->PNext = *HeadNode;
		*HeadNode = NewNode;
	}
}

/* 尾部插入 */
void TailAdd(Node** HeadNode, int data)
{
	Node* NewNode = (Node*)malloc(sizeof(Node));
	if (NewNode != NULL)
	{
		NewNode->data = data;
		NewNode->PNext = NULL;
		
		/* 判断链表是否为空 */
		if (*HeadNode == NULL)
		{
			*HeadNode = NewNode;
		}
		else
		{
			Node* current = *HeadNode; 
			while (current->PNext != NULL)
			{
				/* 遍历链表找出最后一个节点 */
				current = current->PNext;
			}
			/* 将最后一个节点的下一个指针指向新的节点 */
			current->PNext = NewNode;
		}
	}
}

int main(void)
{
	Node* Head = InitList();
	TailAdd(&Head, 1);

	printf("%d\n", Head->data);//打印0
	printf("%d\n", Head->PNext->data);//打印1

	free(Head);

	return 0;
}


在这里插入图片描述

3.中间插入

中间插入的话就会稍微复杂一点了,因为是在中间进行插入操作,首先需要找到对应的位置,并且需要关注前后两个节点的PNext指针指向。

/* 中间插入 */
void MiddleAdd(Node** HeadNode, int data, int index)
{
	Node* NewNode = (Node*)malloc(sizeof(Node));
	Node* current = *HeadNode;
	int i = 0;
	if (NewNode != NULL)
	{
		NewNode->data = data;
		/* 判断链表是否为空 */
		if (*HeadNode == NULL)
		{
			*HeadNode = NewNode;
		}
		else if (current->PNext == NULL)
		{
			/* 只有一个节点直接使用尾部插入 */
			TailAdd(HeadNode, data);
		}
		else
		{			
			for (i = 0; i < index - 1; i++)
			{
				/* 遍历链表找到要插入位置的前一个链表节点 */
				current = current->PNext;
			}
			Node* currentNext = current->PNext;/* 记录要插入位置的下一个节点 */

			current->PNext = NewNode;
			NewNode->PNext = currentNext;
		}
	}
}

在这里插入图片描述

六、遍历链表

链表的遍历其实并不难,我们使用一个名为current的指针来追踪当前节点,初始化为*HeadNode。如果当前节点不为NULL,就继续移动current指针到下一个节点,遍历到链表的结尾。

/* 遍历链表 */
void TraverseList(Node* HeadNode)
{
	Node* current = HeadNode;
	while (current != NULL)
	{
		printf("Node data : %d\n", current->data);
		current = current->PNext;//向后移动一个位置
	}
}

七、释放链表

释放链表也是比较简单的,和遍历链表的操作有一些类似,只不过这里多了一步操作就是需要记录当前节点,当当前节点移动后对当前节点进行free释放,这样就可以完成链表的释放了。

/* 释放链表 */
void FreeList(Node* HeadNode)
{
	Node* Nowcurrent = HeadNode;
	Node* Oldcurrent = NULL;
	while (Nowcurrent != NULL)
	{
		Oldcurrent = Nowcurrent;/* 记录当前链表节点 */
		Nowcurrent = Nowcurrent->PNext;//向后移动一个位置
		free(Oldcurrent);/* 释放当前链表节点 */
	}
}

总结

本篇文章开始我们会开始使用C语言来讲解数据结构中的知识,希望大家可以跟着我好好的掌握这些知识,掌握了数据结构后对C语言的提高会有很大的帮助。
本篇文章的代码将同步到公众号中提供给大家使用。

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

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

相关文章

影响伦敦金走势的两大因素是什么?

在伦敦金市场中经历过一段时间&#xff0c;很多人发现&#xff0c;其实要精准预测伦敦金的走势&#xff0c;尤其是短线的走势&#xff0c;是非常难的。但是&#xff0c;判断其大势&#xff0c;却是有一定规律的。在投资世界中&#xff0c;伦敦金投资之外的一些品种的涨跌号称是…

Ubuntu22.04安装显卡驱动(高速、避错版)

关于显卡驱动安装踩坑不少坑&#xff0c;前前后后重装了6、7次&#xff0c;总结了一下目前网上的各种安装方式&#xff0c;整理了本文。 目录导航 1 准备工作1.1 关闭安全模式1.2 切换独显模式1.3 更新软件列表和安装必要软件、依赖1.4 禁用nouveau (nouveau是通用的驱动程序)1…

SpringCloud学习路线(5)—— Nacos配置管理

一、统一配置管理 需求&#xff1a; 微服务配置能实现统一的管理&#xff0c;比如希望改动多个配置&#xff0c;但不希望逐个配置&#xff0c;而是在一个位置中改动&#xff0c;并且服务不用重启即用&#xff08;热更新&#xff09;。 &#xff08;一&#xff09;使用配置管理…

【大模型】与 ChatGPT 齐平、可商用、更强的 LLaMA2 来了

【大模型】可商用且更强的 LLaMA2 来了 LLaMA2 简介论文GitHubhuggingface模型列表训练数据训练信息模型信息 许可证参考 LLaMA2 简介 2023年7月19日&#xff1a;Meta 发布开源可商用模型 Llama 2。 Llama 2是一个预训练和微调的生成文本模型的集合&#xff0c;其规模从70亿到…

【极简 亲测】已拦截跨源请求:同源策略禁止读取位于....的远程资源。(原因:CORS 头缺少 ‘Access-Control-Allow-Origin‘)

CORS是Cross-Origin Resource Sharing。 解决 首先这个是浏览器层面的拦截。下面的方法都是解除浏览器拦截的方式。 解除了之后还是有可能其他方面有问题的&#xff0c;但是那个会提示其他错误。 比如CORs Failed之类的&#xff0c;这个是没收到response&#xff0c;大概率是…

施耐德plc编程软件转以太网模块

捷米特JM-ETH-SC 是一款经济型的以太网通讯处理器&#xff0c;是为满足日益增多的工厂设备信息化需求&#xff08;设备网络监控和生产管理&#xff09;而设计&#xff0c;用于施耐德Quantumn/Premiun/TSXMicro/Twdio/M200/M218/M221/M241/M238/M25 等系列 PLC 的以太网数据采集…

分布式光伏电站运维平台在石化行业的应用光伏发电数据实时监控

摘要&#xff1a;为实现绿色发展和“净零排放”的目标&#xff0c;近些年来国内外不少能源化工企业进入光伏发电领域。如何做好光伏电站的运行维护&#xff0c;成为石化企业不得不思考的重要课题。本文从分布式光伏电站消防安全、作业安全、环保管理等方面进行思考&#xff0c;…

浮点类型详解及 IEEE754 规定

【C语言趣味教程】(3) 浮点类型&#xff1a;单精度浮点数 | 双精度浮点型 | IEEE754 标准 &#x1f517; 《C语言趣味教程》&#x1f448; 猛戳订阅&#xff01;&#xff01;&#xff01; ​—— 热门专栏《维生素C语言》的重制版 —— &#x1f4ad; 写在前面&#xff1a;这是…

亿发软件:数字化大中型制造企业生产管理应用,实现智慧工厂信息化

随着信息技术与制造业的深度协调&#xff0c;作为企业发展的趋势&#xff0c;大中型制造企业需要拥抱信息化建设。通过运用信息技术和数字化运营&#xff0c;大中型制造企业的生产、设计、经营、管理、后续服务等都实现自动化、智能化。大中型制造企业信息化建设解决方案&#…

Spark(31):Spark性能调优之算子调优

目录 0. 相关文章链接 1. mapPartitions 2. foreachPartition优化数据库操作 3. filter与coalesce的配合使用 4. repartition解决SparkSQL低并行度问题 5. reduceByKey预聚合 0. 相关文章链接 Spark文章汇总 1. mapPartitions 普通的 map 算子对 RDD 中的每一个元素进行…

肖sir___讲解环境__001

1.jdk是什么&#xff1f; jdk是java代码的编译器&#xff0c;可以理解为“翻译”。 &#xff08;1&#xff09;windows中jdk是在dos中查询&#xff1a;java -version &#xff08;2&#xff09;linux中jdk是在linux服务器查询&#xff1a;java -version ** ** 2、搭建环境服务…

C++初阶 - 3.类和对象(中)

目录 1.类的6个默认成员函数 2.构造函数 2.2特性 3.析构函数 3.1 概念 3.2 特性 4. 拷贝构造函数 4.1 概念 4.2 特征 5.赋值运算符重载 5.1运算符重载 5.2 赋值运算符重载 5.3 前置和后置重载 6.日期类的实现 7.const成员 8.取地址及const取地址操作符重载 1.类…

guava-31.1-android.jar时出错; zip file is empty

配置nacos-client时&#xff0c;启动报错guava-31.1-android.jar时出错; zip file is empty 翻看了一下依赖的nacos-api的maven包中&#xff0c;果然有这个版本的guava 在nacos-api中屏蔽掉 <dependency><groupId>com.alibaba.nacos</groupId><artifactI…

【技能实训】DMS数据挖掘项目-Day14

文章目录 任务16【任务16.1】数据的请求和响应【任务16.2】创建JTable的数据适配器类MatchedTableModel&#xff0c;直接从数据库获取数据。 任务16 【任务16.1】数据的请求和响应 数据的请求和响应方法 程序设计 package com.qst.dms.service;import com.qst.dms.net.Requ…

安装 PyCharm

网址&#xff1a;Download PyCharm: Python IDE for Professional Developers by JetBrains 安装文件&#xff1a; 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 第四步&#xff1a; 正在安装&#xff1a; 安装完成&#xff1a;

ubuntu中下载、构建、使用raylib

目录 先决条件 [1]下载raylib方式一方式二 构建 [1]使用终端中使用Clion中使用 先决条件 [1] ubuntu系统上需要先安装GCC, make(或者cmake)和git (下载raylib) 执行下面的命令可以安装GCC,make,cmake,git sudo apt install build-essential git #build-essential是一套工具集…

2020年美国大学生数学建模竞赛A题向北移动解题全过程文档及程序

2020年美国大学生数学建模竞赛 A题 向北移动 原题再现&#xff1a; 全球海洋温度影响某些海洋生物的栖息地质量。当温度变化太大而无法持续生长时&#xff0c;这些物种便开始寻找其他更适合其现在和将来的生活和生殖的栖息地。在美国缅因州的龙虾种群中就可以看到一个例子&am…

C++底层分析

文章目录 进程的地址空间划分用户空间内核空间 程序的链接原理 进程的地址空间划分 任何的编程语言》产生两种东西&#xff1a;指令和数据 程序加载到内存中&#xff0c;不可能加载到物理内存。 linux系统会给当前进程分配一个2^32&#xff08;32位系统&#xff0c;4G&#xf…

python自动化测试selenium定位frame及iframe示例

这篇文章主要为大家介绍了python自动化测试selenium定位frame及iframe示例的示例详解&#xff0c;有需要的朋友可以借鉴参考下&#xff0c;希望能够有所帮助 frame标签有frameset、frame、iframe三种&#xff0c;frameset和其它普通标签没有区别&#xff0c;不会影响正常定位&…

电脑C盘哪些文件可以删除?最全总结分享!

“怎么会这样呢&#xff1f;我的电脑c盘明明没东西却爆满。实在不知道应该怎么处理了。有哪位朋友知道电脑c盘哪些文件可以删除吗&#xff1f;快来帮帮我吧&#xff01;” C盘是计算机中的系统盘&#xff0c;存储着操作系统和很多程序文件。在c盘中有些文件是不可以随意删除的&…