单向链表

news2024/12/23 22:20:29

目录

思维导图:

学习内容:

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/1945558.html

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

相关文章

员工网络监控软件:把控员工网络活动的标尺

在竞争激烈的漩涡之中&#xff0c;企业如同一只不断旋转的陀螺&#xff0c;努力保持着自身的平衡和稳定&#xff0c;而员工的网络活动则是那无形却强大的力量&#xff0c;时刻影响着企业的运转。员工网络监控软件仿佛一根坚固无比的轴心&#xff0c;以其精准的标尺帮助企业实现…

分类模型-逻辑回归和Fisher线性判别分析★★★★

该博客为个人学习清风建模的学习笔记&#xff0c;部分课程可以在B站&#xff1a;【强烈推荐】清风&#xff1a;数学建模算法、编程和写作培训的视频课程以及Matlab等软件教学_哔哩哔哩_bilibili 目录 1理论 1.1逻辑回归模型 1.2线性概率模型 1.3线性判别分析 1.4两点分布…

基于区块链的算力交易平台

目录 基于区块链的算力交易平台 核心技术 创新点 算力交易流程和拍卖算法 关键技术 创新点 基于区块链的算力交易平台 核心技术 智能合约: 定义:智能合约是一组情景应对型的程序化规则和逻辑,通过部署在区块链上的去中心化、可信共享的脚本代码实现。作用:智能合…

leetcode10 -- 正则表达式匹配

题目描述&#xff1a; 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 . 和 * 的正则表达式匹配。 . 匹配任意单个字符* 匹配零个或多个前面的那一个元素 所谓匹配&#xff0c;是要涵盖 整个 字符串 s的&#xff0c;而不是部分字符串。 示例 1&#xff1…

【工具】轻松转换JSON与Markdown表格——自制Obsidian插件

文章目录 一、插件简介二、功能详解三、使用教程四、插件代码五、总结 一、插件简介 JsonMdTableConverter是一款用于Obsidian的插件&#xff0c;它可以帮助用户在JSON格式和Markdown表格之间进行快速转换。这款插件具有以下特点&#xff1a; 轻松识别并转换JSON与Markdown表格…

解锁PCIe8516高速数据采集卡应用——超声波无损检测

超声波无损检测是无损检测技术的重要手段之一&#xff0c;由于其信号的高频特性&#xff0c;需要采用高速数据采集设备来采集、记录、分析和处理。 某客户需要使用超声波对钢材进行无损检测&#xff0c;由于声波在钢材中的传播速度很高&#xff0c;(纵波CL的传播速度为5900米/秒…

分布式训练并行策略

1.分布式训练的概念 分布式训练&#xff08;Distributed Training&#xff09;是指将机器学习或深度学习模型训练任务分解成多个子任 务&#xff0c;并在多个计算设备上并行地进行训练。 一个模型训练任务往往会有大量的训练样本作为输入&#xff0c;可以利用一个计算设备完成…

【C语言】链式队列的实现

队列基本概念 首先我们要了解什么是队列&#xff0c;队列里面包含什么。 队列是线性表的一种是一种先进先出&#xff08;First In Fi Out&#xff09;的数据结构。在需要排队的场景下有很强的应用性。有数组队列也有链式队列&#xff0c;数组实现的队列时间复杂度太大&#x…

PySide(PyQt),自定义图标按钮

1、在Qt Designer中新建画面&#xff0c;并放置3个按钮&#xff08;QPushButton&#xff09;和一个分组框&#xff08;QGroupBox&#xff09;小部件&#xff0c;分别命名为btn_1&#xff0c; btn_2&#xff0c;btn_3和btnStation。 2、将所有小部件的显示文字内容删除。 3、将…

前端面试宝典【Javascript篇】【1】

欢迎来到《前端面试宝典》&#xff0c;这里是你通往互联网大厂的专属通道&#xff0c;专为渴望在前端领域大放异彩的你量身定制。通过本专栏的学习&#xff0c;无论是一线大厂还是初创企业的面试&#xff0c;都能自信满满地展现你的实力。 核心特色&#xff1a; 独家实战案例…

畅销款超声波眼镜清洗器该怎么选?2024年最强超声波清洗机推荐指南

眼镜是现代生活中不可或缺的物品&#xff0c;但许多人可能不清楚如何正确清洁眼镜。传统的清洁方法可能会对眼镜造成损害&#xff0c;例如使用普通肥皂或清水清洗时容易划伤镜片。为了解决这个问题&#xff0c;家用超声眼镜波清洗机应运而生。超声波清洗机通过超声波振动原理进…

昇思MindSpore 应用学习-CycleGAN图像风格迁移互换

日期 心得 昇思MindSpore 应用学习-CycleGAN图像风格迁移互换&#xff08;AI代码学习&#xff09; CycleGAN图像风格迁移互换 模型介绍 模型简介 CycleGAN(Cycle Generative Adversarial Network) 即循环对抗生成网络&#xff0c;来自论文 Unpaired Image-to-Image Trans…

小白0基础怎么快速写一篇激光SLAM论文

大家好呀&#xff0c;我是一个SLAM方向的在读博士&#xff0c;深知SLAM学习过程一路走来的坎坷&#xff0c;也十分感谢各位大佬的优质文章和源码。如有不对的地方欢迎指出&#xff0c;欢迎各位大佬交流讨论&#xff0c;一起进步。博主创建了一个科研互助群Q&#xff1a;9510262…

视频翻译保留原音色pyvideotrans+clone-voice

剪映的视频翻译时长限制5分钟以内&#xff0c;需要积分2700首次有减免大概21.6元&#xff08;1秒9积分/1元100积分&#xff09; • 视频翻译配音工具pyvideotrans 将视频从一种语言翻译为另一种语言&#xff0c;并添加配音 打包链接&#xff1a;夸克网盘分享 升级补丁&#…

三、基础语法1(30小时精通C++和外挂实战)

三、基础语法1&#xff08;30小时精通C和外挂实战&#xff09; 1&#xff0c;开发环境的搭建2&#xff0c;cin和cout3、4&#xff0c;函数重载5&#xff0c;使用IDA分析exe6.1&#xff0c;默认参数6.2&#xff0c;默认参数的本质汇编7&#xff0c;externC1作用8&#xff0c;ext…

防御和进攻编程

防御性编程是许多程序员都听说过的一个术语&#xff0c;对于某些程序&#xff0c;防御性编程是必不可少的。对于其他程序&#xff0c;它可能偶尔使用一下。除此之外&#xff0c;还有攻击性编程。 在本文中&#xff0c;我们将首先研究“正常编程”。我们首先研究它&#xff0c;…

android(安卓)最简单明了解释版本控制之MinSdkVersion、CompileSdkVersion、TargetSdkVersion

1、先明白几个概念 &#xff08;1&#xff09;平台版本&#xff08;Android SDK版本号&#xff09; 平台版本也就是我们平时说的安卓8、安卓9、安卓10 &#xff08;2&#xff09;API级别&#xff08;API Level&#xff09; Android 平台提供的框架 API 被称作“API 级别” …

Mongodb的通配符索引

学习mongodb&#xff0c;体会mongodb的每一个使用细节&#xff0c;欢迎阅读威赞的文章。这是威赞发布的第95篇mongodb技术文章&#xff0c;欢迎浏览本专栏威赞发布的其他文章。如果您认为我的文章对您有帮助或者解决您的问题&#xff0c;欢迎在文章下面点个赞&#xff0c;或者关…

Blender 4.2 安装GIS插件步骤

Blender 4 更新以后插件安装变得复杂&#xff0c;插件界面的安装按钮不显示&#xff0c;界面布局改变&#xff0c;怎么安装插件&#xff1a; 1. 在线安装&#xff1a; “编辑”&#xff08;Edit&#xff09;>进入偏好设置&#xff08;Preferences setting&#xff09;>…

文件粉碎销毁 硬盘粉碎销毁 废弃的文件如何销毁

废弃的文件可以采用多种方法进行销毁&#xff0c;具体取决于文件的敏感性和数量。以下是一些常见的废弃文件销毁方法&#xff1a; 1. 机械粉碎&#xff1a;这是一种常见的方法&#xff0c;尤其适用于含有敏感信息的文件。可以使用碎纸机将文件切碎&#xff0c;对于小批量的资料…