数据结构:双向循环链表的全方位解构

听故事学语言 · 数据结构篇 · 第五章 藏在文件里的顺序与链 数据结构篇第五章,工业章一,前四章家当的实战回门。拖动进度条为什么秒跳,MP4 登记簿记位置供直达;FAT 文件分配表,链式存储的逻辑、数组实现的身体,1977 年马克·麦克唐纳为软盘设计它;进程档案链表轮转,一头进一头出,内核偏爱链表的连接自由;浏览器后退键,教科书的双向链表对工业界的数组加游标,同一个队形,两种站位法,谁赢谁的战场。 阅读详情

数据结构:双链表的全方位解构

双向循环链表像一群人手拉手围成圈:每个人既记得左边是谁、也记得右边是谁,从任意一人出发都能绕完一整圈,加人走人只需改两根手指。

核心思想

单链表最大的痛是找前驱 O(n)。双向链表给每个节点加一个 prev 指针,前驱后继都能 O(1) 拿到。再让链表首尾相连成环(尾节点的 next 指回头,头节点的 prev 指向尾),并加一个不存数据的哨兵头节点 phead,于是:

  • 空表也有结构(哨兵自环),不用特判空表。
  • 头插、尾插、头删、尾删全部统一为 O(1)——因为哨兵的 prev 就是尾节点,哨兵的 next 就是第一个有效节点,O(1) 可达。
  • 已知任意节点 pos,在它前后插入、删除它本身,都是 O(1)。

代价:每个节点多一个指针(空间换时间),且插入/删除时要改 4 个指针,顺序极易写错。

数据结构定义

typedef struct Listnode
{
    LTDataType data;
    struct Listnode *next;   // 后继
    struct Listnode *prev;   // 前驱
}LTNode;

内存模型(带哨兵头节点 phead,存了 3 个有效数据 1,2,3):

        ┌──────────────────────────────────────────┐
        ↓                                          │
   [phead] ⇄ [1] ⇄ [2] ⇄ [3] ⇄──────────────┘
        ↑                                  │
        └──────────────────────────────────┘
   哨兵不存数据,prev 指向尾节点(3),next 指向首节点(1)
   尾节点(3) 的 next 指回 phead → 形成环

关键约定:

  • phead 是哨兵位,phead->next 是第一个有效节点,phead->prev 是最后一个有效节点(尾)。
  • 空表判断:phead->next == phead(哨兵自环,没有有效节点)。
  • 遍历从 phead->next 开始,到再次遇到 phead 结束。

关键操作实现

创建节点 LTbuynode

LTNode * LTbuynode(LTDataType x)
{
    LTNode *node = (LTNode *)malloc(sizeof(LTNode));
    if (node == NULL)
    {
        perror("malloc fail!");
        exit(1);
    }
    node->data = x;
    node->next = node->prev = node;  // 新节点自环:next 和 prev 都指向自己
    return node;
}

逐行解释:新节点初始时 next = prev = self(自环)。这是双向循环链表的「空状态」原子结构——一个自环节点本身就是一条合法的空环。后续插入时只需把它接到链里,不用单独初始化指针。

初始化 LTInit

LTNode * LTInit()
{
    LTNode *phead = LTbuynode(-1);  // 哨兵位,-1 是占位数据(永不读取)
    return phead;
}

逐行解释:初始化就是建一个自环哨兵节点。返回值赋给调用者的头指针。哨兵的 data 用 -1 占位,约定永不读取哨兵的 data,所以放什么都行。

打印 LTprint

void LTprint(LTNode *phead)
{
    LTNode *pcur = phead->next;     // 从第一个有效节点开始
    while (pcur != phead)           // 转一圈回到哨兵就结束
    {
        printf("%d->", pcur->data);
        pcur = pcur->next;
    }
    printf("\n");
}

逐行解释:从 phead->next 出发,顺着 next 走,直到再次回到 phead。这就是循环链表遍历的终止条件——不是 NULL,而是「回到起点」。

尾插 LTPushBack

void LTPushBack(LTNode *phead, LTDataType x)
{
    assert(phead);
    LTNode *newnode = LTbuynode(x);
    // phead  phead->prev(原尾)  newnode
    newnode->prev = phead->prev;   // 新节点的前驱 = 原尾
    newnode->next = phead;          // 新节点的后继 = 哨兵(成环)

    phead->prev->next = newnode;    // 原尾的后继 = 新节点
    phead->prev = newnode;          // 哨兵的前驱 = 新节点(更新尾)
}

逐行解释:尾插 O(1),因为哨兵的 prev 直接给出原尾节点。要改 4 个指针:newnode->prev、newnode->next、原尾->next、phead->prev。

修改顺序的核心原则:先改新节点的两个指针(它还没接入,随便改不影响别人),再改旧节点的指针。具体说:

  1. newnode->prev = phead->prev —— 此时 phead->prev 还指向原尾,安全。
  2. newnode->next = phead。
  3. phead->prev->next = newnode —— 让原尾指向新节点。必须在这步之前还没动 phead->prev,否则原尾就找不到了。所以这步在 phead->prev = newnode 之前。
  4. phead->prev = newnode —— 最后更新哨兵的前驱为新尾。

陷阱:如果先执行 phead->prev = newnode,那 phead->prev->next(第3步)就变成了 newnode->next,原尾节点彻底丢失。所以「先改新、后改旧」「先连原尾、再更新哨兵」是铁律。

头插 LTPushFront

void LTPushFront(LTNode *phead, LTDataType x)
{
    assert(phead);
    // phead  newnode  phead->next(原首节点)
    LTNode *newnode = LTbuynode(x);
    newnode->prev = phead;          // 新节点前驱 = 哨兵
    newnode->next = phead->next;   // 新节点后继 = 原首节点

    phead->next->prev = newnode;   // 原首节点的前驱 = 新节点
    phead->next = newnode;          // 哨兵的后继 = 新节点(更新头)
}

逐行解释:头插 O(1),哨兵的 next 直接给原首节点。同样 4 个指针,同样「先改新节点、再改旧节点」「先连原首、再更新哨兵」。

尾删 LTPopBack / 头删 LTPopFront

void LTPopBack(LTNode *phead)
{
    assert(phead && phead->next != phead);  // 不能删空表,也不能删哨兵
    LTNode *del = phead->prev;       // 待删的尾节点
    // phead  del->prev(新尾)  del
    del->prev->next = phead;        // 新尾的后继 = 哨兵
    phead->prev = del->prev;         // 哨兵的前驱 = 新尾
    free(del);
    del = NULL;
}

void LTPopFront(LTNode *phead)
{
    assert(phead && phead->next != phead);
    LTNode *del = phead->next;       // 待删的首节点
    // phead  del  del->next(新首)
    del->next->prev = phead;        // 新首的前驱 = 哨兵
    phead->next = del->next;         // 哨兵的后继 = 新首
    free(del);
    del = NULL;
}

逐行解释:删除也是 O(1)。assert(phead->next != phead) 保证不删空表(空表时哨兵自环,next==phead,删了等于删哨兵,链表结构就毁了)。删的过程:让待删节点的前驱和后继直接相连(跨过 del),再 free(del)。

先连后断原则:先让 del 的邻居互连(del->prev->next = del->next 等),再 free(del)。如果先 free,就读不到 del->prev 和 del->next 了。

查找 LTFind

LTNode * LTFind(LTNode *phead, LTDataType x)
{
    LTNode *pcur = phead->next;
    while (pcur != phead)            // 转一圈
    {
        if (pcur->data == x)
            return pcur;
        pcur = pcur->next;
    }
    return NULL;
}

逐行解释:遍历有效节点比较,找到返回节点指针(供 LTInsert/LTErase 用),找不到返回 NULL。

在 pos 之后插入 LTInsert

void LTInsert(LTNode *pos, LTDataType x)
{
    assert(pos);
    LTNode *newnode = LTbuynode(x);
    // pos  newnode  pos->next(原后继)
    newnode->prev = pos;             // 新节点前驱 = pos
    newnode->next = pos->next;       // 新节点后继 = 原后继

    pos->next = newnode;             // pos 的后继 = 新节点
    newnode->next->prev = newnode;   // 原后继的前驱 = 新节点
}

逐行解释:在 pos 之后插,O(1)(双向链表前驱后继都在手)。注意第 4 步 newnode->next->prev = newnode:此时 newnode->next 已指向原后继,所以这步是让原后继的 prev 回指新节点。

顺序陷阱:第3步 pos->next = newnode 必须在第4步之后吗?不——第4步用的是 newnode->next(已存了原后继),不是 pos->next,所以即使第3步先改了 pos->next,第4步仍正确。但更稳的写法是先存 pos->next 到临时变量,避免依赖顺序记忆。

删除 pos 节点 LTErase

void LTErase(LTNode *pos)
{
    assert(pos);
    // pos->prev  pos  pos->next
    pos->next->prev = pos->prev;    // 后继的前驱 = pos 的前驱
    pos->prev->next = pos->next;    // 前驱的后继 = pos 的后继
    free(pos);                       // 邻居互连后再删
    pos = NULL;                      // 只置空形参(见下)
}

逐行解释:删除 O(1)。让 pos 的前驱和后继直接互连(跨过 pos),再 free(pos)。先连后断:两步互连必须都在 free 之前。

重要约定:pos 不能是哨兵 phead,否则删了哨兵整个链表结构就废了。调用者必须保证不传哨兵。函数内 pos = NULL 只置空形参,外部持有的指针仍悬空,需调用者自行置空。

销毁 LTDestroy

void LTDestroy(LTNode *phead)
{
    assert(phead);
    LTNode *pcur = phead->next;
    while (pcur != phead)            // 遍历有效节点
    {
        LTNode *next = pcur->next;   // 先存下一个
        free(pcur);
        pcur = next;
    }
    free(phead);                     // 最后释放哨兵本身
    phead = NULL;                    // ⚠️ 只置空形参
}

逐行解释:遍历释放所有有效节点,最后释放哨兵 phead。同样「先存 next 再 free」防 UAF。

陷阱:phead = NULL 只改了形参,调用者手里的头指针仍指向已释放的哨兵内存(悬空指针)。和 LTErase 一样,调用者必须自己 plist = NULL。这是「传一级指针无法改外部指针」的老问题——如果要彻底解决,LTDestroy 应改成传二级指针 LTNode**。

复杂度分析

操作时间复杂度说明
头插 / 尾插O(1)哨兵 prev/next 直接给位置
头删 / 尾删O(1)同上
在 pos 处插入 / 删除 posO(1)双向,前驱后继都在手
按值查找O(n)仍需遍历
销毁O(n)逐个释放

对比单链表:单链表尾插尾删 O(n)(找前驱),双向循环链表全 O(1)。代价是每个节点多 8 字节(一个 prev 指针)。

常见陷阱清单

  1. 4 个指针修改顺序错 → 断链,最常见也最难调。口诀:「先改新节点两指针,再连旧邻居,最后更新哨兵」。
  2. 先 free 后连邻居 → UAF,读不到前驱后继。
  3. LTErase 传了哨兵 phead → 删了哨兵,结构崩塌。
  4. LTDestroy/LTErase 后不置空外部指针 → 悬空指针。
  5. 空表判断写错 → 应是 phead->next == phead,写成 phead == NULL(哨兵永不为空,永远判非空)。
  6. 哨兵 data 被误读 → 占位值无意义,遍历应跳过哨兵。

我的实现 vs 教科书实现

  • 设计正确:哨兵 + 双向 + 循环,结构标准,头尾插删全 O(1)。
  • 代码复用不足:LTPushBack/LTPushFront 完全可以调用 LTInsert(phead, x) / LTInsert(phead->prev... ) 复用,当前各写一遍,有重复。
  • LTDestroy 置空无效:传一级指针无法改外部头指针,应改二级指针或要求调用者手动置空。
  • 缺接口:没有 LTSize、LTEmpty(判空)、LTClear(清空不销毁)。
  • vs std::list:C++ 标准库的 list 就是这种结构,额外提供迭代器、splice(O(1) 拼接)、size 维护。

拓展知识点

变体与进阶

  • Linux 内核链表:反向设计——把链表节点(struct list_head)嵌入到业务结构体里,而非把业务数据放进链表节点。这样一套链表代码能挂任意类型,极致复用。
  • LRU Cache:HashMap + 双向链表,O(1) 访问 + O(1) 淘汰。访问一个节点就移到链表头,满了删尾。
  • 不带头双向链表:去掉哨兵,但头插删要特判空表,代码稍繁。

常见考点

  • 双向链表插入/删除 4 个指针的顺序(口诀见上)。
  • 哨兵位的作用:统一边界,免特判。
  • 为何循环 + 哨兵能让头尾操作 O(1):哨兵 prev/next 直接定位首尾。

与其他结构的关系

双向链表是单链表的升级版(加 prev),常用来实现队列、栈、LRU、调度队列。std::deque(双端队列)底层用分块的双向链表/数组组合。和单链表、顺序表同属线性表家族。

一句话记忆法

双向循环链表:哨兵是圆心,首尾在隔壁,插删都 O(1),改 4 指针先新后旧、先连后断,空表看 next==phead。

听故事学语言 · 数据结构篇 · 第六章 栈,后进先出的盒子 数据结构篇第六章,栈。二进制短除法余数为什么要倒着读,谜底是栈;括号匹配先让计数器上场,([)] 一串让它当场折戟,混括号必须栈;逆波兰记法,1924 年卢卡谢维奇发明波兰记法,1957 年汉布林把它倒过来;一件学生时代的四则运算器文物,带着一颗时发时不发的雷,当年调了很久怎么也跑不对,病没变,变的是它发不发作;双栈直接求值与随机表达式对数器一千组零不合;最小栈一步报最小,接雨水单调栈一层一层结算;汉诺塔的调用栈真身,V 字图退役;火车进出站十四种可能,卡塔兰数;链栈与共享栈收章。 阅读详情

相关推荐

千兆以太网的接收和crc校验

解析该以太网帧,包括统计该以太网帧长度+过滤功能(端口号对不对,是否为udp协议)+CRC校验,将解析结果存入状态fifo,一旦状态fifo开始有数,就开始开启读过程,读过程开始时,首先读状态fifo,根据从状态fifo里面读出的以太网帧长度,来读数据fifo(这样能够防止跨帧读),然后在根据状态fifo里面读出的crc校验正确标志和过滤标志,来确定是否将数据fifo读出来的数据送给下一个模块。改掉之后在抓一下,可以看到抓出来的数据完全正确,从最后开始看,除去4个crc校验的尾巴,数据是到255结束的。

weixin_45284871的博客 2739

【数据结构-5】单链表的实现

单链表,具体来说是单向的、不带头的、不循环的链表。链表由一个一个节点组成,每个节点分为数据域和指针域,数据域存放数据,指针域存放下一节点的地址。这样,就可以根据指针域中的地址找到下一个节点,节点就串起来了构成了单链表。

weixin_44218929的博客 187

centos7安装db2 version11.1

centos7安装db2 version11.1

AKA石头 1062

pandas 深度入门:从数据结构到一份真实销售报表

本文系统讲解Pandas核心应用,从三分钟安装到完整月度报表生成,涵盖数据读取、清洗、筛选、分组聚合、表连接、透视表与时间序列等13个关键环节。强调“先看数据再动手”,掌握Series/DataFrame/Index结构本质,熟练运用loc/iloc、merge校验、agg/transform区别,通过真实案例实现高效报表自动化,告别手动Excel操作。最后提供性能优化与高频报错解决方案,助力读者快速上手,真正实现“写二十行代码,跑三秒出结果”。

信息安全领域优质创作者|操作系统领域专家 282

浙政钉H5应用上架实战:从环境配置到发布避坑指南

本文详细解析浙政钉H5应用上架全流程,从环境配置到发布避坑指南。重点介绍Vue 2.6 + Node 14.21.3的稳定开发环境搭建、项目结构规范及常见报错解决方案,帮助开发者高效完成浙政钉H5应用上架流程。

weixin_27943395的博客 286

【C++PTA】6-1 Point类的运算 (10 分)

定义Point类,有坐标x,y两个私有成员变量;对Point类重载“+”(相加)、“-”(相减)和“==”(相等)运算符,实现对坐标的改变,要求用友元函数和成员函数两种方法实现。对Point类重载<<运算符,以使得代码 Point p; cout<<p<<endl;可以输出该点对象的坐标。 函数接口定义: 实现Point类。 裁判测试程序样例: /* 请在这里填写答案 */ int main(int argc, char const *argv[]) { P

libertye的博客 2367

数据结构的基本概念和算法的初步认识

本文系统阐述数据结构与算法的基础概念,涵盖数据、数据元素、数据对象的层次关系,数据结构的三要素(逻辑结构、存储结构、数据运算),以及抽象数据类型。重点解析算法的特性与设计要求,并深入讲解时间复杂度分析的核心——大O表示法,通过“只保留最高阶项、忽略常数系数、取最坏情况”三大原则,结合实例剖析O(1)、O(n)等常见时间复杂度,揭示算法效率的本质评估方法。

ACH_YYX的博客 188

一步一步详解LSTM网络【从RNN到LSTM到GRU等,直至attention】

本文主要译至并加上了部分笔者的见解,对于全面理解LSTM有一定的帮助。

zik的博客 3078

数据结构:9.Map和Set

有的讲解会说这里key是标识作用,这种说法其实没有太大意义,就算知道了他是标识value的作用又如何,在实际的使用过程中,我们只会根据具体的效果选择使用的方法等,不会执着于“标识”这种说法。2.由于哈希函数的设计原则太过理论,它仅仅是在认为:这些通过关键码算出来的index不同 的情况下设计的,然而这种理论化的设计原则在遇到冲突时,恰恰就出现了问题,而我们能做的,就是降低冲突率。虽然哈希表的使用过程中存在冲突,但是在实际的使用过程中,哈希表的冲突率是不高的,冲突的个数是可控的。

2602_95646088的博客 201

单片机应用案例:ESP32 搭配 BH1750 光强传感器和 Web 服务器

在该应用案例中,我们将使用 ESP32 开发板作为主控制器,BH1750 光强传感器用于采集环境光强度数据,并通过 Web 服务器展示采集到的数据。步骤一:购买材料步骤二:连接硬件步骤三:编写代码我们将使用 Arduino IDE 编写代码,以下是主要代码:// WiFi 设置const char* ssid = “你的 WiFi 名称”;const char* password = “密码”;// Web 服务器实例// BH1750 实例// 获取当前光强度数据。

RlDart的博客 813

101、LLC谐振变换器的传递函数推导

文章摘要 LLC谐振变换器的传递函数推导是电源设计的关键环节。作者通过一次炸管事故案例,揭示了忽视传递函数计算的严重后果。LLC传递函数复杂的根源在于其三个谐振元件(Lr、Cr、Lm)的变频工作特性,导致增益曲线非单调变化。文章详细介绍了采用扩展描述函数法的建模思路,指出Lm对增益曲线的重要影响,并给出了传递函数的数学表达式及频率响应特性。通过实际调试案例,作者对比了错误补偿器设计(Type II)与优化方案(Type III)的效果差异,强调了实测验证的重要性。最后总结了5条实用经验,包括留足裕度、关注启

Jack010169的博客 152

Vue3 低代码移动表单设计器实战(二):缩放画布下的拖拽系统与树状级联数据结构

本文深入剖析Vue3低代码表单设计器在缩放画布下的拖拽失效问题,揭示SortableJS在CSS Transform下坐标崩溃的数学根源。通过自研独立视觉克隆壳层与逆向几何补偿算法,实现精准拖拽;采用扁平数据存储+运行时树状计算结构,解决级联依赖与深克隆难题,确保逻辑一致性与性能优化,为复杂低代码系统提供可复用的工程范式。

NanChen的博客 343

揭秘 C++ 性能优化的终极秘籍,让你面试时应答如流

VTune Amplifier 是英特尔开发的一款强大的性能分析工具,它可以深入分析应用程序在英特尔架构上的性能瓶颈。功能特点全面的分析视角:提供了多种分析模式,如热点分析、线程分析、内存分析等。热点分析模式能快速定位程序中最耗时的代码段;线程分析模式可帮助开发者了解多线程程序中线程的执行情况、同步开销等;内存分析模式则专注于内存访问的性能,包括缓存命中率、内存带宽使用等。硬件层面的洞察:能够深入到硬件层面,分析 CPU 的微架构指标,如指令级并行度、流水线停顿等。

大模型大数据攻城狮的专栏 1151

排序算法深度笔记(上):比较类排序的思想演进与代码实现

与。本文聚焦前者 —— 也就是通过元素间两两比较来确定相对顺序的排序算法,从最朴素的 O (n²) 基础排序,到工业级主流的 O (nlogn) 进阶排序,逐层拆解设计思想、带注释的代码实现与边界细节。当我们理解了比较排序的性能边界,也就自然引出了非比较排序的存在意义。所有代码均遵循统一的实现规范:入口函数前置空数组与单元素边界校验,基于数组原地修改,主函数附带可直接运行的测试用例,与练习代码风格完全对齐。

2401_87069538的博客 611

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

CF2283F[数据结构]

首先考虑一种暴力解法,当我们建好i之前的图后,当我们添加i的时候,区间[li,ri]中的点都会向i建边,但是这些点中,如果有某两个顶点l,r,满足l<r,那么l就可以通过r间接到达i,因此可以删除l到i的边;初始时我们令所有点放入set中,然后对于每一个i的处理,我们将集合中仍在[l,r]的范围的点弹出,并且将f设置为i,这样我们就得到了所有点的最小跳板;对于每个区间[l,r] 我们要计算这下下标中的f(v)中大于r的数目有多少个;我们令f(v)表示顶点v可到达的最小的顶点;

蒟蒻 138

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

笔记-论数据结构2

typedef 数据类型 别名return 0;typedef也经常用在结构体上:每次声明结构体变量都要写struct关键字,很麻烦,而且逻辑上也很难受,typedef可以解决这个问题。int x;int y;p.x = 5;p.y = 10;return 0;而利用typedef解决有两种解法:typedef struct 结构体名数据类型 变量名;数据类型 变量名;……}别名;数据类型 变量名;数据类型 变量名;……}别名;实际的运用int x;

fish_zzzz的博客 111

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

【数据结构-3】算法美学之降复杂度

在while循环的循环体内,首先用临时变量保存最后一个位置的数据,再通过for循环把所有元素往后移动一位,最后把tmp中保存的值放到下标为0的位置上去。方法二的思路是:创建新数组,让原数组中后k个数据移动到新数组中前面的位置,让原数组中前numsSize-k个数据移动到新数组中后面的位置,这。这段代码的思路是:三次逆置,第一次逆置前numsSize-k个数据,第二次逆置后k个数据,最后整体逆置。这篇博文分享一道解法不一的算法题,求解过程的目的在于降低算法的时间复杂度,题源力扣。,属于空间换时间的操作。

weixin_44218929的博客 85

【数据结构-2】顺序表的实现

线性表是一种数据结构,它分为逻辑结构和物理结构,在逻辑结构上线性表是呈线性的,数据是是连续存储的;在物理结构(物理空间)上,线性表不一定是连续存储的。线性表的实现,主要分为数组和链式结构。顺序表是线性表的一种实现方式,顺序表在逻辑结构和物理结构上都是连续的。顺序表的底层实现是数组。

weixin_44218929的博客 208

数据结构:排序

将一组无序的记录(数据元素),按照某个关键字(key)递增或递减的顺序重新排列。

2502_92937908的博客 467

Python 开发的实践:从数据结构选型到框架决策

规模多大?决定用list还是set,Pandas 还是 Polars。瓶颈在哪?I/O 密集用异步,CPU 密集用进程/自由线程,内存受限用列式与惰性执行。代价是什么?消除手动维护窗口的出错面,TaskGroup消除失败任务不被取消的泄漏,消除 N+1,Protocol消除为类型约束而引入的继承层级——每个选择都在消除某类代价,同时也引入新的约束。能回答这三个问题,比记住更多 API 更有用。愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!

weixin_45809780的博客 379

头歌 数据结构——C&C++线性表实训

本系列任务围绕线性表的链式实现,依次完成顺序构建、逆序构建、排序插入、查找、删除、求长度及栈、队列、集合等应用。通过动态内存分配与指针操作,掌握单链表的基本操作与实际应用,涵盖插入、删除、遍历、搜索等核心功能,最终实现数据结构在典型场景中的灵活运用。

一名敲代码的苦13程序员 238

CDGA DMBOK 第5章——数据建模和设计:如何将业务需求转化为规范的数据结构

本章系统阐述数据建模与设计的核心方法,强调将业务需求转化为规范数据结构的全过程。重点涵盖数据模型三层次(概念、逻辑、物理)、实体关系模型(ER)及主键、外键等核心要素,深入解析第一至第三范式,阐明规范化在消除冗余、保障数据一致性的关键作用,并介绍反规范化在性能优化中的合理应用。通过“概念看业务、逻辑看结构、物理看实现”的逻辑框架,实现从需求到数据库落地的完整闭环。

积跬步,慕至千里的博客 307

数据结构第三课:单链表与双向链表

单链表是基于结点的非连续存储结构,由数据域和指针域组成,逻辑上连续但物理上可不连续。其核心优势在于动态增删高效,适合频繁插入删除场景;相较顺序表,虽有指针开销且访问需遍历,但灵活性更高。常见算法包括反转、中间节点查找、合并有序链表及环检测等。双向链表通过前后指针提升操作效率,实际应用中常采用带头循环双向链表结构。

2503_93549121的博客 206

数据结构——图的基础

图是由顶点集和边集构成的非线性数据结构,用于描述多对多关系。根据边的方向分为有向图与无向图,按连通性分为连通图与非连通图,按边是否带权分为有权图(网)与无权图。关键术语包括:简单图(无重边、自环)、完全图(边数最多)、稀疏/稠密图、路径、回路、子图、连通分量(极大连通子图)、强连通分量及生成树(包含所有顶点的极小连通子图,不唯一)。

2401_89246663的博客 170

数据结构:栈和队列

实现,栈顶设在链表头部(头插法),这样 push/pop 都是 O(1)。指向的是"下一个可插入位置",不是最后一个元素。:中缀转后缀(逆波兰表达式),用两个栈(操作数栈 + 运算符栈)。在队尾(rear)插入,在队头(front)删除。:链栈不会栈满(除非内存耗尽),适合元素数量不确定的场景。进行插入(入栈 push)和删除(出栈 pop)操作。:遇左括号入栈,遇右括号弹栈比对。:为了区分"队空"和"队满",通常。:只允许一端插入,两端删除。:只允许一端删除,两端插入。:排队买票,先来先服务。

2502_92937908的博客 343

数据结构之图论基础——从定义到常用术语

本文系统梳理图论核心知识点:从数据关系递进引出图的多对多特性,定义图为G=(V,E),强调顶点集非空;分类为有向/无向、有权/无权、连通/非连通;详解九大高频术语——完全图、稀疏/稠密图、权与网、邻接、度(含握手定理)、路径、子图、连通分量与生成树,并总结考研/面试必背公式与易错点,突出“边数计算”与“极大/极小”辨析,助力高效备考。

qq_34097230的博客 356
上一篇: Claude Code桌面版:CLI的GUI化革命
下一篇: 数据结构精讲:单链表从零到精通,一文搞定所有核心操作与高频考点
DDXYcoder
博客等级 码龄1年 140粉丝 29原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值