浅析链表结构

一、单向链表

        C语言中数组是常用的一种数据类型,但可惜数组长度是固定大小的,不能动态扩展,使用起来有时不是很方便。然后就有了自定义的动态数组结构,动态数组就比较好用了,长度可以任意扩展,但还有一个问题不好解决,就是每次插入数据时,数组后面的数据都得乾坤大挪移一回,如果数组长度较大的话效率就比较低了。再然后就有了链表结构的出现。链表的原理如下图所示:

具体代码如下:

#include <stdio.h>
#include <malloc.h>
#include <string.h>// 链表中节点结构体
typedef struct _stu_linkNode
{void* data;						// 本节点存储的数据(由于不知道本节点中要存储何种类型数据,因此用万能指针void*来代表所有数据类型包括自定义类型。)struct _stu_linkNode* next;		// 下个节点的地址
} stu_linkNode;// 链表结构体
typedef struct _stu_linkList
{stu_linkNode head;		// 链表头节点int size;				// 链表长度
} stu_linkList;// 用万能指针来代替链表结构体,这是封装的关键
typedef void* linkList;// 链表初始化
linkList linkListInit()
{// 在堆区开辟链表stu_linkList* pList = (stu_linkList*)malloc(sizeof(stu_linkList));if (pList == NULL) { return NULL; }// 设置初始大小pList->head.data = NULL;pList->head.next = NULL;pList->size = 0;return pList;
}// 链表指定位置插入
void linkListInsert(linkList ll, int pos, void* val)
{if (ll == NULL) { return; }if (val == NULL) { return; }stu_linkList* pList = (stu_linkList*)ll;					// 强制转换if (pos < 0 || pos > pList->size) { pos = pList->size; }	// 位置不正确则默认尾插// 找到pos位置所在节点的前驱节点stu_linkNode* prevNode = &pList->head;	// 定义节点变量指向头节点(如果链表为空则前驱节点就是头节点)for (int i = 0; i < pos; i++)			// 循环改变节点变量指向,直至pos位置所在节点的前驱节点{prevNode = prevNode->next;			// 重点:prevNode是当前节点的指针地址,prevNode->next是下一个节点的指针地址}// 创建要插入的新节点stu_linkNode* newNode = (stu_linkNode*)malloc(sizeof(stu_linkNode));if (newNode == NULL) { return; }newNode->data = val;newNode->next = NULL;// 将新节点插入到链表中newNode->next = prevNode->next;prevNode->next = newNode;// 更新链表大小pList->size++;
}// 链表尾插法
void linkListPushBack(linkList ll, void* val)
{if (ll == NULL) { return; }if (val == NULL) { return; }stu_linkList* pList = (stu_linkList*)ll;					// 强制转换linkListInsert(ll, pList->size, val);
}// 链表指定位置删除
void linkListErase(linkList ll, int pos)
{if (ll == NULL) { return; }stu_linkList* pList = (stu_linkList*)ll;					// 强制转换if (pos < 0 || pos > pList->size - 1) { return; }			// 位置不正确则返回// 找到pos位置所在节点的前驱节点stu_linkNode* prevNode = &pList->head;for (int i = 0; i < pos; i++){prevNode = prevNode->next;}// 得到当前pos所在节点stu_linkNode* delNode = prevNode->next;// 开始删除prevNode->next = delNode->next;free(delNode);delNode = NULL;// 更新元素大小pList->size--;
}// 链表尾删法
void linkListPopBack(linkList ll)
{if (ll == NULL) { return; }stu_linkList* pList = (stu_linkList*)ll;					// 强制转换linkListErase(ll, pList->size - 1);
}// 链表指定值删除(利用回调函数让用户自己去比较)
void linkListRemove(linkList ll, void* data, int (*myCompare)(void*, void*))
{if (ll == NULL) { return; }if (data == NULL) { return; }// 强制转换stu_linkList* pList = (stu_linkList*)ll;// 在遍历查找该值匹配的节点时还要记录该节点的前驱节点,因此这里我们用双指针。stu_linkNode* prevNode = &pList->head;			// 当前节点的前驱节点stu_linkNode* curNode = pList->head.next;		// 当前节点for (int i = 0; i < pList->size; i++){if (myCompare(data, curNode->data))	// 找到了{prevNode->next = curNode->next;free(curNode);curNode = NULL;pList->size--;break;}// 未找到,双指针向后移动prevNode = curNode;							// 前驱节点指向当前节点curNode = curNode->next;					// 当前节点指向下一节点}
}// 链表大小
int linkListSize(linkList ll)
{if (ll == NULL) { return -1; }stu_linkList* pList = (stu_linkList*)ll;		// 强制转换return pList->size;
}// 链表遍历(利用回调函数)
void linkListForEach(linkList ll, int (*myForEach)(void*))
{if (ll == NULL) { return; }if (myForEach == NULL) { return; }stu_linkList* pList = (stu_linkList*)ll;		// 强制转换stu_linkNode* curNode = pList->head.next;	// 第一个节点for (int i = 0; i < pList->size; i++){if (myForEach(curNode->data) == -1) { break; }	// 根据返回值判断是否中途退出遍历curNode = curNode->next;}
}// 链表清空
void linkListClear(linkList ll)
{if (ll == NULL) { return; }// 强制转换	stu_linkList* pList = (stu_linkList*)ll;// 释放内部每个节点stu_linkNode* curNode = pList->head.next;for (int i = 0; i < pList->size; i++){stu_linkNode* nextNode = curNode->next;	// 得到当前节点的后继节点free(curNode);curNode = nextNode;}// 将头节点的next设为NULLpList->head.next = NULL;// 更新元素大小pList->size = 0;
}// 链表销毁
void linkListDestroy(linkList ll)
{if (ll == NULL) { return; }linkListClear(ll);free(ll);ll = NULL;
}// 测试用结构体
struct _stu_person
{char name[31];int age;
};// 测试用回调函数(返回-1则退出遍历)
int personPrint(void* val)
{struct _stu_person* p = (struct _stu_person*)val;printf("姓名:%s 年龄:%d\n", p->name, p->age);return 0;
}// 测试用比较回调函数(1-成功,0-失败)
int personCompare(void* data1, void* data2)
{struct _stu_person* p1 = (struct _stu_person*)data1;struct _stu_person* p2 = (struct _stu_person*)data2;if (strcmp(p1->name,p2->name) == 0 && p1->age == p2->age){return 1;}else{return 0;}
}// 测试链表
void testLinkList()
{// 创建链表linkList list = linkListInit();// 测试数据struct _stu_person p1 = { "刘备",39 };struct _stu_person p2 = { "关羽",34 };struct _stu_person p3 = { "张飞",32 };struct _stu_person p4 = { "赵云",28 };struct _stu_person p5 = { "吕布",30 };// 开始插入linkListPushBack(list, &p1);linkListInsert(list, 10, &p2);linkListInsert(list, 1, &p3);linkListPushBack(list, &p4);linkListInsert(list, 0, &p5);// 遍历printf("=====元素个数:%d=====\n", linkListSize(list));linkListForEach(list, personPrint);// 删除指定位置数据linkListErase(list, 1);printf("\n=====删除第一个位置数据后的元素个数:%d=====\n", linkListSize(list));linkListForEach(list, personPrint);linkListPopBack(list);printf("\n=====删除最后位置数据后的元素个数:%d=====\n", linkListSize(list));linkListForEach(list, personPrint);// 删除指定值数据struct _stu_person pp = { "张飞",32 };linkListRemove(list, &pp, personCompare);printf("\n=====删除指定值【张飞,32】数据后的元素个数:%d=====\n", linkListSize(list));linkListForEach(list, personPrint);// 清空链表linkListClear(list);printf("\n=====链表清空后的元素个数:%d=====\n", linkListSize(list));linkListForEach(list, personPrint);// 销毁链表linkListDestroy(list);printf("\n=====链表已经销毁=====\n");}// 链表
int main()
{testLinkList();return 0;
}

二、双向链表

        单向链表已经基本实现了用户想要的功能,但是有一个问题啊,在插入或删除时都得查找该节点的前一个节点,找到后更改其next指针,问题是找该节点的前驱节点的方法就得从头节点开始遍历链表啊,这效率太低了,如果我们在每个节点中不仅能存储它的后继节点指针还能存储其前驱节点的指针就方便了,这就是双向链表的原理了。

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

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

相关文章

easyexcel 3.0.x 版本实现指定列 锁定以及指定列隐藏

1&#xff1a;效果示例 2&#xff1a;代码示例&#xff1a; UnLockCell.java package com.example.juc.zhujie;/*** Author * Date Created in 2023/12/19 10:09* DESCRIPTION:* Version V1.0*/import java.lang.annotation.*;/*** 用于标记锁定哪些列不需要锁定* author 12…

YOLOv8改进 | 检测头篇 | 利用DySnakeConv改进检测头专用于分割的检测头(全网独家首发,Seg)

一、本文改进 本文给大家带来的改进机制是一种我进行优化的专用于分割的检测头,在分割的过程中,最困难的无非就是边缘的检测,动态蛇形卷积(Dynamic Snake Convolution)通过自适应地聚焦于细长和迂回的局部结构,准确地捕捉管状结构的特征。这种卷积方法的核心思想是,通过…

【LangChain学习之旅】—(7) 调用模型:使用OpenAI API还是微调开源Llama2/ChatGLM?

【LangChain学习之旅】—&#xff08;7&#xff09; 调用模型&#xff1a;使用OpenAI API还是微调开源Llama2/ChatGLM&#xff1f; 大语言模型发展史预训练 微调的模式用 HuggingFace 跑开源模型申请使用 Meta 的 Llama2 模型通过 HuggingFace 调用 LlamaLangChain 和 Hugging…

若依在表格中如何将字典的键值转为中文

文章目录 一、需求&#xff1a;二、问题解决步骤1、给需要转换的列绑定formatter属性2、获取字典项3、编写formatter属性绑定的方法 一、需求&#xff1a; 后端有时候返回的是字典的键值&#xff0c;在前端展示时需要转成中文值 后端返回的是dictValue&#xff0c;现在要转换…

【Git】本地仓库文件的创建、修改和删除

目录 一、基本信息设置 1、设置用户名2、设置用户名邮箱 二、Git仓库操作介绍 1、创建一个新的文件夹2、在文件内初始化git仓库&#xff08;创建git仓库&#xff09;3、向仓库中添加文件 1.创建一个文件2.将文件添加到暂存区3.将暂存区添加到仓库 4、修改仓库文件 1.修改文件2.…

java中数组

文章目录 java中数组思维导图数组数组概念 数组定义格式详解数组的访问 常见异常数组索引值越界异常&#xff1a;ArrayIndexOutOfBoundsException空指针异常&#xff1a;NullPointerException 案例例一打印A-Z和0-9例二数组转置输出 java中数组 思维导图 数组 数组概念 组就…

Java8常用新特性

目录 简介 1.默认方法 2..Lambda表达式 3.Stream API 4.方法引用 5.Optional类 简介 Java 8是Java编程语言的一个重要版本&#xff0c;引入了许多令人兴奋和强大的新特性。这些特性使得Java程序更加现代化、灵活和高效。让我们一起来探索一些Java 8的常用新特性吧&#…

NestJS 如何自定义中间件以及实际项目基于中间件提升项目开发效率

前言 NestJS 作为一个强大的 Node.js 框架&#xff0c;允许你通过中间件对请求和响应进行处理。中间件的概念在其他许多框架中也存在&#xff0c;它们在请求处理流程的早期执行&#xff0c;因此非常适合执行如日志记录、请求验证、设置响应头等任务。 在这篇教程中&#xff0…

Mac 下载 nvm 后执行nvm -v 命令报错 nvm: command not found

1、问题&#xff1a;Mac 使用命令下载nvm 成功后执行 nvm -v 查看&#xff0c;报错&#xff1a;nvm command not found 2、原因&#xff1a;可能是系统更新后&#xff0c;默认的 shell 是 zsh&#xff0c;所以找不到配置文件 3、解决&#xff1a;可添加编辑.bash_profile 和 …

回归和拟合的关系

在统计学和机器学习中&#xff0c;回归&#xff08;Regression&#xff09;和拟合&#xff08;Fitting&#xff09;是密切相关的概念&#xff0c;它们通常一起使用来描述如何通过模型来逼近或拟合数据。 回归&#xff08;Regression&#xff09;&#xff1a; 回归是一种统计学…

Docker 安装部署

1、Docker 安装 ① 卸载docker&#xff0c;清空之前的docker文件 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-selinux \docker-engine-selinux \docker-engine \docker-ce…

2021腾讯、华为前端面试题集(基础篇)

Vue 面试题 生命周期函数面试题 1.什么是 vue 生命周期2.vue 生命周期的作用是什么 3.第一次页面加载会触发哪几个钩子 4.简述每个周期具体适合哪些场景 5.created 和 mounted 的区别 6.vue 获取数据在哪个周期函数 7.请详细说下你对 vue 生命周期的理解&#xff1f; **vue 路由…

达梦数据库主备集群

1&#xff1a;服务器硬件需求 按实际业务需求&#xff0c;选择合适的服务器&#xff0c;准备 3 台服务器&#xff0c;一台主库服务器&#xff0c;一台备库服务器&#xff0c;一台监视器服务器&#xff0c;服务器参数建议如下&#xff1a; 硬件要求物理内存>16 GB交换区Swa…

MySQL同步ES的几种方案

MySQL数据同步ES的几种方案 1. 同步双写 与业务耦合深&#xff0c;且业务响应时间长 2. 异步双写 这时可以使用类似MQ这样的中间件&#xff0c;业务主写时向MQ发送一条信息&#xff0c;再由一个聚合服务区消费&#xff0c;最终同步到ES 3. 定时任务 不好配置时间&#xff0c;…

Spring Boot - Application Events 的发布顺序_ContextRefreshedListener

文章目录 Pre概述Code源码分析 Pre Spring Boot - Application Events 的发布顺序_ApplicationEnvironmentPreparedEvent 概述 Spring Boot 的广播机制是基于观察者模式实现的&#xff0c;它允许在 Spring 应用程序中发布和监听事件。这种机制的主要目的是为了实现解耦&#…

2024年最新软件测试面试题

Part1 1、你的测试职业发展是什么&#xff1f;【文末有面试文档免费领取】 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有初步的职业规划&#xff0c;前3年积累测试经验&#xff0c;按如何做…

Python——猜猜心里的数字(2)

1、数字随机产生&#xff0c;范围1-10 2、有三次机会猜数字通过三层嵌套 3、每次猜不中&#xff0c;提示大小 import random numrandom.randint(1,10) guess_num int(input("请输入您猜测的值&#xff1a;")) if guess_numnum:print("恭喜你&#xff0c;第一次…

【漏洞复现】Office365-Indexs-任意文件读取

漏洞描述 Office 365 Indexs接口存在一个任意文件读取漏洞,攻击者可以通过构造精心设计的请求,成功利用漏洞读取服务器上的任意文件,包括敏感系统文件和应用程序配置文件等。通过利用此漏洞,攻击者可能获得系统内的敏感信息,导致潜在的信息泄露风险 免责声明 技术文章…

LLM之长度外推(二)| Self-Extend:无需微调的自扩展大模型上下文窗口

论文链接&#xff1a;https://simg.baai.ac.cn/paperfile/a34ae7f4-f0ce-4f8f-b8f2-e8e4d84bbee5.pdf 目前大模型基本都采用transformer结构&#xff0c;而transformer中attention机制的计算复杂度与序列长度呈平方关系&#xff0c;因此大模型在训练时候通常会设置固定的上下文…

案例117:基于微信小程序的新闻资讯系统设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder …