目录

引言

学习目标:

1.什么是链表

2.链表的分类

2.1 单向链表和双向链表

(1)单向链表

(2)双向链表

2.2 带头结点链表和不带头结点链表

(1)带头结点链表

(2)不带头结点链表

2.3 循环链表和不循环链表

(1)循环链表

(2)非循环链表

3.链表的实现

3.1 单链表的功能

3.2单链表的定义

3.3单链表功能实现

1.打印单链表数据

2.单链表头插

(1)创建节点

(2)头插代码

3.单链表头删

4.单链表尾插

代码实现

5.单链表尾删

6.单链表数据查找

7.单链表数据修改

代码实现

8.指定位置数据插入

(1)指定位置之前插入数据

(2)指定位置之后插入数据

9.指定位置数据删除

(1)删除pos节点

(2)删除pos之后的节点

10.销毁单链表

完整代码SList.h


引言

在上一篇文章中 [数据结构——lesson2.顺序表]我们学习了数据结构中的顺序表,我们知道了顺序表的空间是连续存储的,这与数组非常类似。但是我们也遗留了一些关于顺序表的问题

当我们同时当插入数据时可能存在移动数据与扩容的情况,这大大增加我们的时间与空间成本。因此我们接下来学习新的数据结构——链表

学习目标:

  • 什么是链表?
  • 链表的分类
  • 链表的实现

1.什么是链表

链表(Linked List)是一种常见的数据结构,它通过一系列的节点(Node)来存储数据元素。这些节点之间通过指针或引用相互连接,从而形成一个链状结构。每个节点通常包含两部分:一部分用于存储数据元素,另一部分用于存储指向下一个节点的指针或引用。

注意:

1.从上图可以看出,链式结构在逻辑上是连续的,但在物理上不一定连续;

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表中的每个节点通过指针指向下一个节点,从第一个节点开始,依次沿着指针可以访问到后续节点,形成一个逻辑上连续的序列。但在物理内存中,这些节点可能分布在不同的位置,并不要求存储单元是连续的。

2.链表的结点一般都是从堆上申请的;

链表中的结点是独立申请的空间,通常是在需要插入数据时才去申请一块节点的空间。操作系统的内存管理中,堆是一块可供程序动态分配的内存区域,它允许程序在运行时根据需要申请和释放内存,适合链表这种动态数据结构,因为链表的长度不固定,需要随时根据数据的插入和删除来分配和释放节点空间。

3.从堆上申请的空间,是按照一定的策略来分配的,两次省请的空间可能连续,可能不连续。

堆内存的分配由操作系统的内存分配器负责,它会根据一定的算法和策略来管理和分配内存,这些算法会根据堆内存的当前使用情况来寻找合适的空闲块分配给程序。由于堆内存的使用是动态变化的,每次申请内存时,内存分配器找到的空闲块位置是不确定的,所以两次申请的空间可能连续,也可能不连续。

2.链表的分类

链表可以分为几种不同的类型,其中最常见的有八种,它们大致可以分为三类:

2.1 单向链表和双向链表

(1)单向链表

在单向链表中,每个节点只包含一个指向下一个节点的指针。

(2)双向链表

在双向链表中,每个节点除了包含数据元素外,还包含两个指针:一个指向下一个节点(称为后继节点),另一个指向前一个节点(称为前驱节点)。

2.2 带头结点链表和不带头结点链表

(1)带头结点链表

带头结点的单链表是在链表的第一个数据元素结点之前附加一个结点,称为头结点。

(2)不带头结点链表

非带头结点的单链表不包含头结点,链表的第一个结点即为数据元素结点,头指针直接指向链表的第一个数据元素结点。

2.3 循环链表和不循环链表

(1)循环链表

循环链表是一种特殊类型的链表,其中最后一个节点指向第一个节点,形成一个循环。这种结构使得链表在逻辑上成为一个环状结构。

(2)非循环链表

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
 
无头单向非循环链表

 带头双向循环链

本篇博客将详细讲解一下无头单向非循环链表。

无头单向非循环链表是一种链表结构,其特点是没有头结点,且链表中的节点单向连接,不形成循环。这种链表结构相对简单,一般不会单独用来存储数据,而是作为其他数据结构的子结构,如哈希桶、图的邻接表等。

3.链表的实现

3.1 单链表的功能

单链表一般要实现如下几个功能:

  1. 打印单链表中的数据。
  2. 对单链表进行头插(开头插入数据)。
  3. 对单链表进行头删(开头删除数据)。
  4. 对单链表进行尾插(末尾插入数据)。
  5. 对单链表进行尾删(末尾删除数据)。
  6. 对单链表进行查找数据。
  7. 对单链表数据进行修改。
  8. 对指定位置的数据删除。
  9. 对指定位置的数据插入。
  10. 销毁单链表。

3.2单链表的定义

单链表的结点定义方式与我们以往定义的方式都不同,它是一个结构体中包含两个成员。一个是存储数值,一个存放下一个结点的地址。

//定义节点的结构
//数据+指向下一节点的指针
typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;

3.3单链表功能实现

1.打印单链表数据

遍历链表并打印每个节点的数据来展示链表的内容,直到遇到链表的末尾。

代码实现

void SLTPrint(SLTNode* phead)
{// 初始化一个指针pcur,指向链表的头节点SLTNode* pcur = phead;// 使用while循环遍历链表,直到pcur指向NULL(即链表末尾)  while (pcur != NULL){printf("%d->", pcur->data);pcur = pcur->next;}// 循环结束后,打印"NULL"以明确表示链表的结束 printf("NULL\n");
}

2.单链表头插

(1)创建节点

由于单链表每次插入都需要插入一个节点,因此我们可以写一个创建节点的函数方便后续调用。

//申请内存
SLTNode* SLTCreateNode(SLTDataType x)
{// 为新的单链表节点分配内存空间 SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail:");exit(1);}// 初始化新节点的数据字段  newnode->data = x;newnode->next = NULL;return newnode;
}
(2)头插代码

在这里我们需要注意的是:单链表传参需要二级指针。因为头插数据需要改变一级指针plist的指向,而形参的改变无法影响实参,所以需要二级指针才能改变一级指针plist的指向。

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{// 断言确保传入的头节点指针的地址非空,避免解引用空指针assert(pphead);// 创建一个新的节点,并初始化其数据为x  SLTNode* newnode = SLTCreateNode(x);// 将新节点的next指针指向当前头节点newnode->next = *pphead;// 更新头节点指针,使其指向新节点*pphead = newnode;
}

一、对 assert(pphead) 的理解

核心作用:确保二级指针 pphead 不为空,避免解引用空指针导致程序崩溃。

  • 逻辑背景
    代码中存在 *pphead(解引用二级指针),而对空指针执行解引用操作(如 *NULL)会触发未定义行为(通常导致程序崩溃)。
    assert(pphead) 会在程序运行时检查 pphead 是否为 NULL
    • 若 pphead 为 NULL,程序会报错并终止,提示开发者问题所在;
    • 若 pphead 非空,则继续执行后续代码。
  • 本质目的:通过断言提前暴露潜在的空指针风险,提高代码健壮性。

二、为什么需要传递二级指针?

核心原因:需要通过函数修改原始指针(如链表头节点指针 head)的地址本身。

  • 指针操作的本质
    • 若仅传递一级指针 head(即结构体指针),函数内部只能修改 head 指向的结构体内容,无法改变 head 本身存储的地址值(类似 “传值” 效果)。
    • 若需要修改 head 本身的地址(例如头节点变更、链表初始化等场景),必须传递 head 的地址,即二级指针 pphead(类似 “传址” 效果)。

3.单链表头删

头删与头插类似,都可能需要改变plist的指向,所以也需要二级指针。并且也需要防止链表为空的情况发生

代码实现

//头删
void SLTPopFront(SLTNode** pphead)
{// 链表不能为空,确保pphead不是空指针// 且*pphead(即头节点)也不是空指针assert(pphead && *pphead);// 保存头节点的下一个节点的地址,// 以便删除头节点后能够更新头指针SLTNode* next = (*pphead)->next;free(*pphead);// 更新头节点*pphead = next;
}

4.单链表尾插

代码实现
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//	*pphead就是指向第一个节点的指针//	处理空链表SLTNode* newnode = SLTCreateNode(x);// 判断链表是否为空if (*pphead == NULL){// 如果链表为空,则新节点即为头节点*pphead = newnode;}else{// 找尾SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}// 循环结束,ptail指向尾节点ptail->next = newnode;}
}

5.单链表尾删

与单链表尾插类似,当链表只有一个头结点时需要将plist置为空所以也需要二级指针。并且也需要防止链表为空的情况发生。

//尾删
void SLTPopBack(SLTNode** pphead)
{// 链表不能为空,确保pphead不是空指针,// 且*pphead(即头节点)也不是空指针assert(pphead && *pphead);// 如果链表只有一个节点if ((*pphead)->next == NULL)	//->优先级高于*{free(*pphead);*pphead = NULL;}else{// 链表有多个节点SLTNode* prev = *pphead;SLTNode* ptail = *pphead;// 遍历链表直到找到尾节点while (ptail->next){prev = ptail;			// 更新prev节点ptail = ptail->next;	// 移动到下一个节点}free(ptail);	// 释放尾节点prev->next = NULL;}
}

6.单链表数据查找

如果找到了就返回这个节点的地址,否则就返回空指针NULL。

代码实现

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead;// 遍历链表,直到当前节点为空(即到达链表末尾)while (pcur){if (pcur->data == x){return pcur;	// 找到当前节点的指针}pcur = pcur->next;}return NULL;
}

7.单链表数据修改

代码实现
//数据修改 
void SLTModifity(SLTNode* pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos); SLTNode* cur = pphead;while (cur){if (cur == pos){cur->data = x;break;}cur = cur->next;}
}

8.指定位置数据插入

(1)指定位置之前插入数据
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && *pphead);assert(pos);SLTNode* newnode = SLTCreateNode(x);// 如果插入位置是链表的第一个位置(即pos等于头节点)// 则调用头插函数if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}newnode->next = pos;prev->next = newnode;}
}
(2)指定位置之后插入数据
//在指定位置之后插入数据
void SLTnsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTCreateNode(x);// 将新节点的next指针指向pos的下一个节点newnode->next = pos->next;// 将pos的next指针指向新节点,完成插入操作pos->next = newnode;
}

9.指定位置数据删除

(1)删除pos节点
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pos);assert(pphead && *pphead);// 如果删除的节点是头节点if (pos == *pphead){SLTPopFront(pphead);	// 头删}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}// 将prev的next指针指向pos的下一个节点// 从而绕过pos节点,实现删除prev->next = pos->next;free(pos);pos = NULL;}
}
(2)删除pos之后的节点
//删除pos之后的节点
void SLTraseAfter(SLTNode* pos)
{assert(pos && pos->next);// 创建一个临时指针del,指向pos之后的节点,即要删除的节点SLTNode* del = pos->next;// 将pos的next指针指向del的下一个节点,从而绕过del节点,实现删除pos->next = del->next;free(del);del = NULL;
}

10.销毁单链表

销毁链表需要依次遍历释放节点。

//销毁链表
void SListDesTory(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){// 保存当前节点的下一个节点的指针// 以便在释放当前节点后能够继续遍历SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

完整代码
SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>//定义节点的结构
//数据+指向下一节点的指针
typedef int SLTDataType;typedef struct SListNode
{SLTDataType data;struct SListNode* next;
}SLTNode;//打印
void SLTPrint(SLTNode* phead);
//尾插
void SLTPushBack(SLTNode** phead, SLTDataType);
//头插
void SLTPushFront(SLTNode** phead, SLTDataType);
//尾删
void SLTPopBack(SLTNode** pphead);
//头删
void SLTPopFront(SLTNode** pphead);//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在制定位置之后插入数据
void SLTnsertAfter(SLTNode* pos, SLTDataType x);//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的节点
void SLTraseAfter(SLTNode* pos);//销毁链表
void SListDesTory(SLTNode** pphead);//数据修改
void SLTModifity(SLTNode* pphead, SLTNode* pos, SLTDataType x);

SList.c

#include"SList.h"void SLTPrint(SLTNode* phead)
{// 初始化一个指针pcur,指向链表的头节点SLTNode* pcur = phead;// 使用while循环遍历链表,直到pcur指向NULL(即链表末尾)  while (pcur != NULL){printf("%d->", pcur->data);pcur = pcur->next;}// 循环结束后,打印"NULL"以明确表示链表的结束 printf("NULL\n");
}//申请内存
SLTNode* SLTCreateNode(SLTDataType x)
{// 为新的单链表节点分配内存空间 SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));if (newnode == NULL){perror("malloc fail:");exit(1);}// 初始化新节点的数据字段  newnode->data = x;newnode->next = NULL;return newnode;
}//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{assert(pphead);//	*pphead就是指向第一个节点的指针//	处理空链表SLTNode* newnode = SLTCreateNode(x);// 判断链表是否为空if (*pphead == NULL){// 如果链表为空,则新节点即为头节点*pphead = newnode;}else{// 找尾SLTNode* ptail = *pphead;while (ptail->next){ptail = ptail->next;}// 循环结束,ptail指向尾节点ptail->next = newnode;}
}//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{// 断言确保传入的头节点指针的地址非空,避免解引用空指针assert(pphead);// 创建一个新的节点,并初始化其数据为x  SLTNode* newnode = SLTCreateNode(x);// 将新节点的next指针指向当前头节点newnode->next = *pphead;// 更新头节点指针,使其指向新节点*pphead = newnode;
}//尾删
void SLTPopBack(SLTNode** pphead)
{// 链表不能为空,确保pphead不是空指针,// 且*pphead(即头节点)也不是空指针assert(pphead && *pphead);// 如果链表只有一个节点if ((*pphead)->next == NULL)	//->优先级高于*{free(*pphead);*pphead = NULL;}else{// 链表有多个节点SLTNode* prev = *pphead;SLTNode* ptail = *pphead;// 遍历链表直到找到尾节点while (ptail->next){prev = ptail;			// 更新prev节点ptail = ptail->next;	// 移动到下一个节点}free(ptail);	// 释放尾节点prev->next = NULL;}
}//头删
void SLTPopFront(SLTNode** pphead)
{// 链表不能为空,确保pphead不是空指针// 且*pphead(即头节点)也不是空指针assert(pphead && *pphead);// 保存头节点的下一个节点的地址,// 以便删除头节点后能够更新头指针SLTNode* next = (*pphead)->next;free(*pphead);// 更新头节点*pphead = next;
}//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{SLTNode* pcur = phead;// 遍历链表,直到当前节点为空(即到达链表末尾)while (pcur){if (pcur->data == x){return pcur;	// 找到当前节点的指针}pcur = pcur->next;}return NULL;
}//数据修改 
void SLTModifity(SLTNode* pphead, SLTNode* pos, SLTDataType x)
{assert(pphead);assert(pos); SLTNode* cur = pphead;while (cur){if (cur == pos){cur->data = x;break;}cur = cur->next;}
}//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{assert(pphead && *pphead);assert(pos);SLTNode* newnode = SLTCreateNode(x);// 如果插入位置是链表的第一个位置(即pos等于头节点)// 则调用头插函数if (pos == *pphead){SLTPushFront(pphead, x);}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}newnode->next = pos;prev->next = newnode;}
}//在指定位置之后插入数据
void SLTnsertAfter(SLTNode* pos, SLTDataType x)
{assert(pos);SLTNode* newnode = SLTCreateNode(x);// 将新节点的next指针指向pos的下一个节点newnode->next = pos->next;// 将pos的next指针指向新节点,完成插入操作pos->next = newnode;
}//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{assert(pos);assert(pphead && *pphead);// 如果删除的节点是头节点if (pos == *pphead){SLTPopFront(pphead);	// 头删}else{SLTNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}// 将prev的next指针指向pos的下一个节点// 从而绕过pos节点,实现删除prev->next = pos->next;free(pos);pos = NULL;}
}//删除pos之后的节点
void SLTraseAfter(SLTNode* pos)
{assert(pos && pos->next);//创建临时变量delSLTNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}//销毁链表
void SListDesTory(SLTNode** pphead)
{assert(pphead && *pphead);SLTNode* pcur = *pphead;while (pcur){// 保存当前节点的下一个节点的指针// 以便在释放当前节点后能够继续遍历SLTNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}

结束语

本节内容学习了链表的种类和单链表的实现。

感谢大家三连支持!!!

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

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

相关文章

从零深入理解嵌入式OTA升级:Bootloader、IAP与升级流程全解析

引言&#xff08;Opening&#xff09;想象一下&#xff0c;你开发的一款智能水杯、一个环境监测设备或者一台共享充电宝&#xff0c;已经部署到了成千上万的用户手中。突然&#xff0c;你发现了一个软件bug&#xff0c;或者需要增加一个酷炫的新功能。你不可能派人跑到每个设备…

【Ansible】实施 Ansible Playbook知识点

1.清单概念与静态清单文件是什么&#xff1f;答&#xff1a;Ansible 清单是被管理主机的列表&#xff0c;用于明确Ansible的管理范围&#xff0c;分为静态清单和动态清单。静态清单是通过手动编辑的文本文件来定义被管主机&#xff0c;文件格式可以是INI格式或YAML格式。在INI格…

【Linux】vim工具篇

目录一、vim的多模式1.1 命令模式1.1.1 光标移动1.1.2 复制及撤销1.1.3 剪切及删除1.1.4 替换1.1.5 批量化注释/去注释1.2 底行模式二、vim的配置个人主页<—请点击 Linux专栏<—请点击 一、vim的多模式 vim是一款功能强大的文本编辑器&#xff0c;它编辑代码主要围绕命…

Spark 核心原理:RDD, DataFrame, DataSet 的深度解析

Apache Spark 是一个强大的分布式计算系统&#xff0c;以其内存计算、速度快、易用性强等特点&#xff0c;在大数据处理领域占据重要地位。理解 Spark 的核心原理&#xff0c;特别是其三种核心抽象——RDD, DataFrame, DataSet——对于高效地使用 Spark 至关重要。本文将深入解…

Docker 命令行的使用

1.Docker 命令列表[roothost1 ~]# docker Usage: docker [OPTIONS] COMMANDA self-sufficient runtime for containersCommon Commands:run Create and run a new container from an imageexec Execute a command in a running containerps List cont…

Redis Stream:轻量级消息队列深度解析

&#x1f4e8; Redis Stream&#xff1a;轻量级消息队列深度解析 文章目录&#x1f4e8; Redis Stream&#xff1a;轻量级消息队列深度解析&#x1f9e0; 一、Stream 数据结构解析&#x1f4a1; Stream 核心概念&#x1f4cb; Stream 底层结构⚡ 二、消息生产与消费&#x1f68…

Android studio的adb和终端的adb互相抢占端口

在Android Studio调试时&#xff0c;有时候也需要借助终端的adb命令&#xff0c;他们互相抢占端 口&#xff0c;导致调试麻烦解决如下&#xff1a;① 终端adb的版本是&#xff1a;1.0.39路径是:/usr/lib/android-sdk/platform-tools/adb② Android Studio使用的adb来源于Androi…

GEO服务商推荐:移山科技以划时代高精尖技术引领AI搜索优化新纪元

引言&#xff1a;AI搜索生态重塑与GEO优化战略地位跃升AI技术对信息检索范式的颠覆GEO优化在企业增长中的核心作用第一章&#xff1a;AI搜索新纪元的企业营销挑战与机遇生成式AI成为用户主要信息入口的行业趋势企业在AI搜索中的“答案主权”争夺战GEO优化服务商的核心能力模型&…

Android SystemServer 系列专题【AttentionManagerService】

AttentionManagerService是framework中用来实现屏幕感知的一个系统级服务&#xff0c;他继承于systemserver。我们可以通过dumpsys attention来获取他的一些信息。如下针对屏幕感知的功能的引入来针对这个服务进行一个介绍。1、屏幕感知Settings UI实现屏幕感知的功能在A14上面…

nginx 反向代理使用变量的坑

nginx采用反向代理的时候使用变量的坑 正常情况&#xff1a; location ~ ^/prod-api(?<rest>/.*)?$ {# 假设 $mes_backend 形如: http://127.0.0.1:16889proxy_pass $mes_backend$rest$is_args$args;proxy_http_version 1.1;proxy_set_header Host $host;…

Origin绘制径向条形图|科研论文图表教程

数据排列格式截图&#xff0c;请查看每张图↘右下角水印 目录 数据排列格式截图&#xff0c;请查看每张图↘右下角水印 本 期 导 读 No.1 理解图形 1 定义 2 特点 3 适用场景 No.2 画图教程 1 导入数据&#xff0c;绘制图形 2 设置绘图细节 本 期 导 读 径…

MySQL InnoDB 的 MVCC 机制

前言 多版本并发控制&#xff08;MVCC&#xff09;是 MySQL InnoDB 存储引擎实现高性能事务的核心机制。它通过创建数据快照&#xff0c;使得读写操作可以无锁并发&#xff0c;极大地提升了数据库的并发性能。本文将深入探讨 MVCC 的工作原理、实现细节以及它与事务隔离级别的紧…

景区负氧离子气象站:引领绿色旅游,畅吸清新每一刻

在绿色旅游成为消费主流的今天&#xff0c;游客对 “清新空气” 的需求不再是模糊的期待&#xff0c;而是可感知、可选择的具体体验。景区负氧离子气象站的出现&#xff0c;正以科技之力重塑绿色旅游格局&#xff0c;让 “畅吸清新每一刻” 从口号变为触手可及的现实&#xff0…

Pytorch笔记一之 cpu模型保存、加载与推理

Pytorch笔记一之 cpu模型保存、加载与推理 1.保存模型 首先&#xff0c;在加载模型之前&#xff0c;我们需要了解如何保存模型。PyTorch 提供了两种保存模型的方法&#xff1a;保存整个模型和仅保存模型的状态字典&#xff08;state dict&#xff09;。推荐使用第二种方式&…

当AI在代码车间组装模块:初级开发者的创意反成「核心算法」

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录当AI在…

技术视界 | 跨域机器人通信与智能系统:打破壁垒的开源探索

8 月 16 日&#xff0c;在 OpenLoong 社区举办的第九期线下分享会上&#xff0c;国家地方共建人形机器人创新中心的软件开发负责人 Amadeus 博士带来了一场主题为“跨域机器人通信与智能系统&#xff1a;打破行业壁垒的创新方案”的演讲。深入探讨了当前机器人领域的一个关键痛…

Android入门到实战(八):从发现页到详情页——跳转、传值与RecyclerView多类型布局

一. 引言在上一篇文章里&#xff0c;我们从零开始实现了 App 的 发现页面&#xff0c;通过网络请求获取数据&#xff0c;并使用 RecyclerView 展示了剧集列表。但光有发现页还不够&#xff0c;用户在点击一部剧时&#xff0c;自然希望进入到一个更详细的页面&#xff0c;去查看…

【工具】41K star!网页一键变桌面应用

项目中遇到了一个需要将现有的 web 页面打包成一个 桌面应用 的需求。 最一开始想到的是 Electron&#xff0c;但是它还需要一些开发工作并且打包后的应用体积比较大&#xff0c;调研后发现了开源工具 Pake。 它能让你用最轻量的方式&#xff0c;把任何网页一键打包成跨平台桌…

浪潮CD1000-移动云电脑-RK3528芯片-2+32G-安卓9-2种开启ADB ROOT刷机教程方法

浪潮CD1000-移动云电脑-RK3528芯片-232G-安卓9-2种开启ADB ROOT刷机教程方法 往期文章&#xff1a; 浪潮CD1000-移动云电脑-RK3528芯片-232G-安卓9-开启ADB ROOT破解教程 地址1&#xff1a;浪潮CD1000-移动云电脑-RK3528芯片-232G-开启ADB ROOT破解教程-CSDN博客 中国移动浪潮…

Day23_【机器学习—聚类算法—K-Means聚类 及评估指标SSE、SC、CH】

一、聚类算法概念属于无监督学习算法&#xff0c;即有特征无标签&#xff0c;根据样本之间的相似性&#xff0c;将样本划分到不同的类别中。所谓相似性可以理解为欧氏距离、曼哈顿距离、切比雪夫距离... 。分类按颗粒度分为&#xff1a;粗聚类、细聚类。按实现方法分为&#xf…