数据结构 day4

news2024/12/23 16:22:46

目录

思维导图:

学习内容:

1. 链表的引入

1.1 顺序表的优缺点

1.1.1 优点

1.1.2 不足

1.1.3 缺点

1.2 链表的概念

1.2.1 链式存储的线性表叫做链表

1.2.2 链表的基础概念

1.3 链表的分类

2. 单向链表

2.1 节点结构体类型

2.2 创建链表 

2.3 申请节点封装数据 

2.4 链表判空 

2.5 头插 

2.6 链表遍历 

2.7 通过位置查找节点 

2.8 任意位置插入元素

2.9 头删 

 2.10 任意位置删除函数

2.11 按值查找返回位置 

2.12 按位置修改 

2.13 按值进行修改函数 

2.14 链表的反转 

2.15 链表的释放

课外作业:


思维导图:


学习内容:

1. 链表的引入

1.1 顺序表的优缺点

1.1.1 优点

        能够直接通过下标进行定位元素,访问效率高,对元素进行查找和修改比较快

1.1.2 不足

        插入和删除元素需要移动大量的元素,效率较低

1.1.3 缺点

        存储数据元素有上限,当达到MAX后,就不能再添加元素了

1.2 链表的概念

1.2.1 链式存储的线性表叫做链表

        链式存储:表示数据元素的存储地址不一定连续

        线性表:数据元素之间存在一对一的关系

1.2.2 链表的基础概念

        1、节点:节点是链表的基本单位,由数据域和指针域组成

        2、数据域:存放数据元素的部分

        3、指针域:存放下一个节点地址的部分

        4、前驱节点:当前节点的上一个节点

        5、后继节点:当前节点的下一个节点

        6、头节点:虚设的一个节点,数据域不存放数据元素,可以存放链表的长度

        7、头指针:指向第一个节点的指针称为头指针

        8、第一个节点:实际存储数据元素的链表上的第一个节点

        注意:头节点的指针域其实就是头指针,也可以单独定义一个指针,指向第一个节点

1.3 链表的分类

        1、单向链表:只能从头节点或第一个节点出发,单向访问其后继节点的链表称为单向链表

        2、双向链表:从头部出发,既可以访问前驱节点,也可以访问后继节点

        3、循环链表:首尾相接的链表称为循环链表

2. 单向链表

2.1 节点结构体类型

        1> 头节点和普通节点数据域可以合到一起,使用一格共用体表示

        2> 指针域都是指向普通节点的地址

//定义数据类型
typedef int datatype;

//定义结点类型
typedef struct Node
{
    union
    {
        int len;    //头结点数据域
        datatype data;  //普通结点数据域
    };

    struct Node *next;   //指针域
};

2.2 创建链表 

        1> 在堆区申请一格头节点的空间,就创建了一个链表

        2> 需要对头节点的数据域初始化链表长度,指针域初始化NULL

//创建链表
NodePtr list_create()
{
    //只需要在堆区申请一个头结点
    NodePtr L = (NodePtr)malloc(sizeof(Node));
    if(NULL == L)
    {
        printf("创建失败\n");
        return NULL;
    }

    //程序执行至此,说明头结点创建结束
    L->len = 0;    //表示链表长度为0
    L->next = NULL;      ///防止野指针

    printf("链表创建成功\n");
    return L;
}

2.3 申请节点封装数据 

        1> 需要将要封装的数据当做函数的参数进行传递

        2> 同样在堆区申请节点,就传入的数据放入数据域

//申请结点封装数据函数
NodePtr apply_node(datatype e)
{
    //在堆区申请一个结点的大小
    NodePtr p = (NodePtr)malloc(sizeof(Node));
    if(NULL == p)
    {
        printf("结点申请失败\n");
        return NULL;
    }

    //给结点内容赋值
    p->data = e;          //数据域赋值
    p->next = NULL;        //指针域

    return p;
}

2.4 链表判空 

        只需要判断头节点的指针域中是否为空即可

//链表判空
int list_empty(NodePtr L)
{
    return L->next == NULL;
}

2.5 头插 

        1> 表示将新插入的节点放入第一个节点中

        2> 插入数据时,不能先将前面节点与后面节点先断开。一定要从新节点出发,指向后面的节点,然后将前驱节点指向字节

//头插
int list_insert_head(NodePtr L, datatype e)
{
    //判断逻辑
    if(NULL==L)
    {
        printf("链表不合法\n");
        return -1;
    }

    //申请结点封装数据
    NodePtr p = apply_node(e);
    if(NULL==p)
    {
        return -1;
    }

    //头插逻辑
    p->next = L->next;
    L->next = p;
    
    
    //表的变化
    L->len ++;
    printf("头插成功\n");
    return 0;
}

2.6 链表遍历 

        需要使用一个遍历指针,将每一个节点进行遍历一遍,如果该指针指向的节点不为空,就访问其数据域,向后偏移

//链表遍历函数
int list_show(NodePtr L)
{
    //判断逻辑
    if(NULL==L || list_empty(L))
    {
        printf("遍历失败\n");
        return -1;
    }
    
    printf("链表中的元素分别是:");
    //遍历逻辑
    NodePtr q = L->next;   //定义遍历指针从第一个结点出发
    while(q != NULL)
    {
        //输出数据域
        printf("%d\t", q->data);

        q = q->next;    //指针向后偏移一个
    }
}

2.7 通过位置查找节点 

        1> 参数:链表、位置

        2> 返回值:对应节点的地址

//通过位置查找结点
NodePtr list_search_pos(NodePtr L, int pos)
{
    //判断逻辑
    if(NULL==L || list_empty(L) || pos<0 || pos>L->len)
    {
        printf("查找失败\n");
        return NULL;
    }

    //查找逻辑
    //定义遍历指针从头结点出发
    NodePtr q = L;
    for(int i=0; i<pos; i++)
    {
        q = q->next;
    }

    return q;     //将找到的结点地址返回
}

2.8 任意位置插入元素

        1> 参数:链表、位置、要插入的元素

        2> 返回值:int

        3> 注意:必须找到要插入位置的节点的前驱节点,将前驱节点当作头节点,进行头插操作

//任意位置插入
int list_insert_pos(NodePtr L, int pos, datatype e)
{
    //判断逻辑
    if(NULL==L || pos<1 || pos>L->len+1)
    {
        printf("插入失败\n");
        return -1;
    }
    
    //申请结点封装数据
    NodePtr p = apply_node(e);
    if(NULL==p)
    {
        return -1;
    }
    
    //调用函数查找前驱结点
    NodePtr q = list_search_pos(L, pos-1);
    
    //插入逻辑
    p->next = q->next;
    q->next = p;
    
    //表的变化
    L->len++;
    printf("插入成功\n");
    return 0;
}

2.9 头删 

        1> 参数:链表

        2> 返回值:int

        3> 注意:需要将要删除的节点先标记一下,头节点的指针,指向第二个节点后,将标记的节点释放

//链表头删
int list_delete_head(NodePtr L)
{
    //判断逻辑
    if(NULL==L || list_empty(L))
    {
        printf("删除失败\n");
        return -1;
    }
    
    //删除三部曲
    NodePtr p = L->next;    //标记
    L->next = p->next;  //L->next->next  孤立
    free(p);            //释放
    p = NULL;

    //表长变化
    L->len--;

    printf("头删成功\n");
    return 0;
}

 2.10 任意位置删除函数

        1> 参数:链表、要删除的位置

        2> 返回值:int

        3> 注意:需要找到要删除的节点的前驱节点,将其当作头节点,进行头删逻辑

//链表任意位置删除
int list_delete_pos(NodePtr L, int pos)
{
    //判断逻辑
    if(NULL==L || list_empty(L) || pos<1 || pos>L->len)
    {
        printf("删除失败\n");
        return -1;
    }
    
    //找到前驱结点
    NodePtr q = list_search_pos(L, pos-1);
    
    //删除逻辑
    NodePtr p = q->next;           //标记
    q->next = q->next->next;   //p->next 孤立
    free(p);                   //释放
    p = NULL;

    
    //表的变化
    L->len--;
    printf("删除成功\n");
    return 0;
}

2.11 按值查找返回位置 

        1> 参数:链表、要查找的值

        2> 返回值:元素在链表中的位置

//链表按值查找返回位置
int list_search_value(NodePtr L, datatype e)
{
    //判断逻辑
    if(NULL==L || list_empty(L))
    {
        printf("查找失败\n");
        return -1;
    }
    
    //查找逻辑
    //定义遍历指针从第一个结点出发
    NodePtr q = L->next;
    for(int index=1; index<=L->len; index++)
    {
        //判断当前结点的值是否为要找的数据
        if(q->data == e)
        {
            return index;
        }

        q = q->next;     //继续向后遍历
    }

    //程序执行至此,表示没找到
    printf("没找到\n");
    return -1;
}

2.12 按位置修改 

        1> 参数:链表、要修改的元素位置、要被更新的值

        2> 返回值:int

        3> 注意:先通过位置,找到对应的元素,更改该元素中的内容即可

//链表按位置进行修改
int list_update_pos(NodePtr L, int pos, datatype e)
{
    //判断逻辑
    if(NULL==L || list_empty(L) || pos<1 || pos>L->len)
    {
        printf("修改失败\n");
        return -1;
    }
    
    //按位置查找逻辑
    NodePtr p = list_search_pos(L, pos);
    
    //修改逻辑
    p->data = e;

    printf("修改成功\n");
    return 0;
}

2.13 按值进行修改函数 

        1> 参数:链表、旧值、新值

        2> 返回值:int

        3> 思路:先通过旧值找到位置,通过位置进行修改

//按值进行修改
int list_update_value(NodePtr L, datatype old_e, datatype new_e)
{
    //判断逻辑
    if(NULL==L || list_empty(L))
    {
        printf("修改失败\n");
        return -1;
    }
    
    //按值查找位置
    int res = list_search_value(L, old_e);
    if(res == -1)
    {
        printf("没有要修改的值\n");
        return -1;
    }
    
    //按位置修改
    list_update_pos(L, res, new_e);
    
    printf("修改成功\n");
    return 0;
}

2.14 链表的反转 

        1> 参数:链表

        2> 返回值:int

        3> 注意:在该操作中,没有节点被删除,也没有节点被释放

//将链表进行翻转
void list_reverse(NodePtr L)
{
    //判断逻辑
    if(NULL==L || L->len<=1)
    {
        printf("翻转失败\n");
        return ;
    }
    
    //翻转逻辑
    NodePtr H = L->next;   //将链表元素进行托付

    L->next = NULL;        //自己白手起家

    NodePtr p = NULL;         //结点的搬运工

    while(H != NULL)
    {
        p = H;      //搬运第一个结点
        H = H->next;     //头指针后移

        //将p以头插的方式放入L中
        p->next = L->next;
        L->next = p;
    }

    printf("翻转成功\n");
    
}

2.15 链表的释放

         1> 参数:链表

        2> 返回值:无

        3> 注意:需要先将所有的节点内存全部释放后,再将头节点释放

//释放链表
void list_destroy(NodePtr L)
{
    //判断逻辑
    if(NULL == L)
    {
        return;
    }

    //将所有结点进行释放
    while(!list_empty(L))
    {
        //头删
        list_delete_head(L);
    }

    //释放头结点
    free(L);
    L = NULL;

    printf("释放成功\n");
}


课外作业:

1.链表的排序

解析:

//排序
void list_sort(NodePtr L)
{
	if(NULL == L || list_empty(L))
	{
		printf("排序失败\n");
		return ;
	}for (int i = 0; i < L->len; i++)
	{
	NodePtr q=L->next;
	NodePtr q1 = q->next;
	while(q1 != NULL){
		if(q->data > q1->data)
		{
			datatype temp = q->data;
			q->data = q1->data;
			q1->data  =temp;
		}
		q=q->next;            //指针向后偏移一个
		q1= q1->next;
		}
	}
	printf("排序成功\n");
	list_print(L);
}

2.链表的反转(递归实现)

解析:

3. 链表去重

解析:

void list_rep(NodePtr L)
{
	if(NULL == L || L->len<=1)
	{
		printf("去重失败\n");
		return ;
	}
		NodePtr q = L;
		while(q->next != NULL){
			if(q->data == q->next->data)
			{
				NodePtr temp = q->next;
				q->next = temp->next;
				free(temp);
			}else{
				q=q->next;
			}
		}
		printf("去重成功\n");
		list_print(L);
}

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

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

相关文章

C语言 ——— 浮点数类型 在 内存中 的 存储模式

目录 浮点数存储规则 单\双精度浮点数 存储 S、M、E 的布局 有效数字M 和 指数位E 的特殊规定 浮点数在内存中是否存储的S、M、E 浮点数存储规则 根据国际标准IEEE754&#xff08;电气和电子工程协会&#xff09;规定&#xff1a;任意一个 浮点数F的二进制 都可以表示成…

19集 两款ESP32开发板如何选择?-《MCU嵌入式AI开发笔记》

19集 两款ESP32开发板我们用哪款&#xff1f;-《MCU嵌入式AI开发笔记》 有两款ESP32的开发板分别是ESP32 S3 和C3的&#xff0c;我们该如何选择&#xff1f; 1、ESP32-S3-BOX-3 在乐鑫官网上&#xff0c;https://www.espressif.com.cn/zh-hans/products/devkits 有ESP32S3 B…

Unity UGUI 之 Canvas画布

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 1.UGUI是什么 首先官方手册导向了这两个连接&#xff0c;里面是ugui的基本教程 帆布 |U…

【全面升级!短视频运营利器揭秘】短视频矩阵系统

短视频矩阵通常是指一种内容分发策略&#xff0c;它通过构建一个包含多种不同类型、主题或风格短视频的内容平台架构。这个矩阵可能会包括搞笑、教育、音乐、美食、旅游等各种短视频形式&#xff0c;让用户可以根据兴趣选择观看。每个视频可以视为矩阵中的一个节点&#xff0c;…

JVM-垃圾回收与内存分配

目录 垃圾收集器与内存分配策略 引用 对象的访问方式有哪些?&#xff08;句柄和直接指针&#xff09; Java的引用有哪些类型? 如何判断对象是否是垃圾? 请列举一些可作为GC Roots的对象? 对象头了解吗? mark word&#xff08;hashcode、分代、锁标志位&#xff09;、…

Linux 12:多线程2

1. 生产者消费者模型 生产者消费者模型有三种关系&#xff0c;两个角色&#xff0c;一个交易场所。 三种关系&#xff1a; 生产者之间是什么关系?竞争 - 互斥 消费者和消费者之间?竞争 - 互斥 消费者和消费者之间?互斥和同步 两个角色&#xff1a; 生产者和消费者 一个交…

记录|C# winform布局学习

目录 前言一、自适应布局Step1. 添加AutoAdaptWindowsSize类Step2. Form中引用Step3. 创建SizeChanged事件函数Step4. 在Fram.Disiger中添加 更新时间 前言 参考视频&#xff1a; C#5分钟winform快速自适应布局 参考文章&#xff1a; 其他参考&#xff1a; 写这篇文章&#xff…

【python学习】python的知识点总结、特点和思考及解答(代码示例)

引言 python 是一种高级编程语言&#xff0c;具有简洁的语法和丰富的库&#xff0c;被广泛应用于Web开发、数据分析、人工智能、科学计算等领域 文章目录 引言一、python知识点总结1.1 基础语法1.1.1 变量和数据类型1.1.2 控制结构1.1.3 函数和模块 1.2 面向对象编程1.2.1 类和…

vue的三大核心知识点

响应式&#xff1a; 监听data属性getter setter(包括数组)模板编译&#xff1a; 模板到render函数再到vnodevdom&#xff1a; patch(elem, vnode)和patch(vnode, newVnode) vue组件初次渲染过程 解析模板为render函数&#xff08;或在开发环境已完成&#xff0c;vue-loader&a…

Vue 对接海康威视,实现摄像头画面展示

文章目录 需求分析1. 下载2. 安装3. new 一个WebControl 插件相关实例 需求 项目中集成海康威视&#xff0c;实现摄像头画面展示 分析 1. 下载 传送门&#xff1a;官方插件包和文档下载 2. 安装 &#xff08;1&#xff09;下载完成后打开 &#xff08;2&#xff09;在项…

三款知名的基于RAG技术的智能体平台分析

这篇文章是关于目前市面上三款知名的基于检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;技术的智能体平台的详细对比分析。这三款产品分别是FastGPT、Dify和Coze。文章从不同维度分析了这些产品的优势和劣势&#xff0c;以供读者参考。 什么是RAG&…

【Qt之·类QRandomGenerator】

系列文章目录 文章目录 前言一、概述1.2. 二、实例演示总结 前言 一、概述 1. 2. 二、实例演示 示例1&#xff1a; #include <QRandomGenerator> #include <QRandomGenerator64> #include <QDebug>int randomInt QRandomGenerator::global()->bound…

ESP8266模块(2)

实例1 查看附近的WiFi 步骤1&#xff1a;进入AT指令模式 使用USB转串口适配器将ESP8266模块连接到电脑。打开串口终端软件&#xff0c;并设置正确的串口和波特率&#xff08;通常为115200&#xff09;。输入以下命令并按回车确认&#xff1a; AT如果模块响应OK&#xff0c;…

TCP状态转换详解

1.什么是TCP的状态转换 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输层协议。在 TCP 连接的生命周期中&#xff0c;连接的状态会随着不同阶段的通信而发生变化&#xff0c;这些变化被称为状…

数据库最佳实践:优化爬虫管理的数据存储方案

摘要&#xff1a; 面对日益增长的数据抓取需求&#xff0c;如何高效管理和存储爬虫获取的海量信息成为一大挑战。本文将深入探讨数据库最佳实践&#xff0c;揭示如何通过优化策略提升爬虫数据存储效率&#xff0c;助您跨越数据管理的障碍&#xff0c;实现数据价值最大化。 一、…

虚拟试衣人像合成新SOTA!IMAGDressing-v1:ControlNet和IP-Adapter的最佳拍档

文章链接&#xff1a;https://arxiv.org/pdf/2407.12705 github链接&#xff1a;https://imagdressing.github.io/ Demo试用&#xff1a;https://sf.dictdoc.site/ 亮点直击 为商家引入了一项新的虚拟试衣&#xff08;VD&#xff09;任务&#xff0c;并设计了一个综合亲和力测量…

关闭 Linux 服务器上的 IPv6

虽然 IPv6 已经逐渐普及&#xff0c;但在某些 Linux 服务器上的业务系统仍然可能遇到一些奇怪的问题。特别是在集群场景中&#xff0c;因为集群各个节点之间需要互相通信&#xff0c;如果 IPv6 没有正确配置网络&#xff0c;可能导致一些未知问题&#xff0c;解决起来相当麻烦。…

acwing796-子矩阵的和-前缀和

s矩阵是全局变量&#xff0c;维度n*m,从1~n和 1~m存储元素【0】【0】~【0】【m】和【0】【0】~【n】【0】分别存储的都是0.s矩阵刚开始是存储输入的元素&#xff0c;后面用于存储前缀和。 s矩阵的意思是s【i】【j】表示从【0】【0】到【i】【j】为对角线的矩阵里面所有元素的和…

Pytorch的编译新特性TorchDynamo的工作原理和使用示例

在深度学习中&#xff0c;优化模型性能至关重要&#xff0c;特别是对于需要快速执行和实时推断的应用。而PyTorch在平衡动态图执行与高性能方面常常面临挑战。传统的PyTorch优化技术在处理动态计算图时效果有限&#xff0c;导致训练时间延长和模型性能不佳。TorchDynamo是一种为…

AI批量剪辑,批量发布大模型矩阵系统搭建开发

目录 前言 一、AI矩阵系统功能 二、AI批量剪辑可以解决什么问题&#xff1f; 总结&#xff1a; 前言 基于ai生成或剪辑视频的原理&#xff0c;利用ai将原视频进行混剪&#xff0c;生成新的视频素材。ai会将剪辑好的视频加上标题&#xff0c;批量发布到各个自媒体账号上。这…