浅析链表结构

一、单向链表

        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…

【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的常用新特性吧&#…

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 和 …

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…

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 …

学习Vue封装的过渡与动画总结

今天学习了Vue封装的过渡与动画&#xff0c;接下来说一下Vue是如何实现的&#xff0c;首先原生的方法是在style元素中给指定元素添加过渡的过渡或动画&#xff0c;但Vue就不需要直接获取到需要过渡或动画的元素&#xff0c;而是使用一个<transition>的标签来包裹住想要过…

py的函数讲解

前言:本章节我们来讲函数&#xff0c;主播略微感觉到有点小难&#xff0c;友友们需要认真看 目录 一.初始函数 1.1关于函数 1.2举例 1.3小结 二.函数的基础语法 2.1关于函数的语法 2.2举例 2.3小结 三.函数的参数 3.1关于函数的参数 3.2举例 3.3小结 四.函数的返回…

如何在 SwiftUI 中实现音频图表

文章目录 前言DataPoint 结构体BarChartView 结构体ContentView 结构体实现协议实现线图总结 前言 在可访问性方面&#xff0c;图表是复杂的事物之一。iOS 15 引入了一项名为“音频图表”的新功能。 下面我们将学习如何通过使用 accessibilityChartDescriptor 视图修饰符为任…

用友NC Cloud IUpdateService接口存在XXE漏洞

免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。 1. 用友NC 简介 微信公众号搜索:南风漏洞复现文库 该…

Shutter Encoder多媒体转换v17.8

软件介绍 多媒体包含种类繁多的各种文件格式&#xff0c;每种格式都有其不同的特征和所谓的“怪癖”。 因此&#xff0c;如果使用多种图像、视频或音频格式&#xff0c;找到一个集中的软件来从一个地方处理所有这些格式可能会非常棘手。 这就是 Shutter Encoder 基本上允许做的…

【算法】信使(最短路问题)

题目 战争时期&#xff0c;前线有 n 个哨所&#xff0c;每个哨所可能会与其他若干个哨所之间有通信联系。 信使负责在哨所之间传递信息&#xff0c;当然&#xff0c;这是要花费一定时间的&#xff08;以天为单位&#xff09;。 指挥部设在第一个哨所。 当指挥部下达一个命令…