数据结构精讲:单链表从零到精通,一文搞定所有核心操作与高频考点

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

数据结构精讲:单链表从零到精通,一文搞定所有核心操作与高频考点

单链表像一列不知道总长的火车:每节车厢(节点)只记住「下一节是谁」,想找第 N 节只能从车头一节节数过去,但插一节新车厢只需改一根指针。

核心思想

单链表用分散的节点存储数据,每个节点 = 数据 + 一个 next 指针。节点在内存里不连续,靠指针串成一条链。和顺序表(数组)相比:

顺序表单链表
随机访问O(1)O(n),必须从头遍历
插入/删除O(n),要搬移O(1),只改指针(前提是已找到位置)
内存连续,cache 友好分散,有指针开销

单链表的所有难点都集中在三件事:二级指针(要不要改头指针)、找前驱(单链表只能往后走)、边界情况(空表 / 单节点 / 头节点 / 尾节点)。

数据结构定义

typedef int SLTDataType;

typedef struct SListNode
{
    SLTDataType data;            // 数据域
    struct SListNode *next;      // 指针域:指向下一个节点
}SLTNode;

内存模型(不带头节点,plist 是头指针):

plist -> [data|next] -> [data|next] -> [data|next] -> NULL
          节点1           节点2           节点3

关键约定:链表不带头节点(哨兵),plist == NULL 表示空链表。最后一个节点的 next == NULL 是链表的终止标志。

为什么需要二级指针(最高频考点)

链表头指针 plist 是 SLTNode* 类型。如果一个函数要修改 plist 本身(比如空表头插,要让 plist 指向新节点),传 plist(一级指针)只拷贝了指针值,函数内改的是副本,外部的 plist 不变。必须传 &plist(二级指针 SLTNode**)。

传一级指针 plist:               传二级指针 &plist:
函数内 *pphead 副本被改          函数内 *pphead 直接改外部 plist
外部 plist 不变 ❌               外部 plist 真的变了 ✅

plist ──┐                       &plist ──┐
        ↓                                ↓
      [节点]    <-- 副本指向它          plist ──→ [节点]
                                         ↑
                              *pphead 直接操作外部 plist

判断法则:函数内是否要改变头指针的指向?是 → 二级指针;否 → 一级指针即可。

关键操作实现

创建节点 SLTBuynode

SLTNode * SLTBuynode(SLTDataType x)
{
    SLTNode *pnewnode = (SLTNode *)malloc(sizeof(SLTNode));
    if (pnewnode == NULL)        // malloc 可能失败,必须检查
    {
        perror("malloc fail!");
        exit(1);                // 节点都建不出来,后续没法玩
    }
    pnewnode->data = x;
    pnewnode->next = NULL;       // 新节点 next 置空,防止野指针
    return pnewnode;
}

逐行解释:每个节点都从堆 malloc 来,必须检查返回值。next 初始化为 NULL 是好习惯——即使忘了接链,也不会指向随机内存。所有插入函数都应通过 SLTBuynode 创建节点,避免重复代码。

打印 SLTPrint

void SLTPrint(SLTNode *phead)    // 只读不改头指针,一级指针即可
{
    SLTNode *pcur = phead;       // 用游标遍历,不动原头指针
    while (pcur)                 // pcur != NULL,到尾节点终止
    {
        printf("%d->", pcur->data);
        pcur = pcur->next;       // 游标后移一格
    }
    printf("NULL");              // 链表以 NULL 收尾
    printf("\n");
}

逐行解释:用临时游标 pcur 而非直接动 phead,保护原头指针。循环条件 pcur(非 NULL)保证遍历到尾节点的 next==NULL 时停下。

头插 SLTPush_Front

void SLTPush_Front(SLTNode **pphead, SLTDataType x)
{
    assert(pphead);              // 防御二级指针本身为空
    SLTNode *pnewnode = SLTBuynode(x);
    pnewnode->next = *pphead;    // 新节点先指向原头
    *pphead = pnewnode;          // 再更新头指针指向新节点
}

逐行解释:头插 O(1),两步搞定。顺序很重要:必须先 pnewnode->next = *pphead(让新节点抓住原链表),再 *pphead = pnewnode(头指针改指向新节点)。如果反过来先改 *pphead,原链表的头就丢了。

插入前:  plist -> A -> B -> NULL
步骤1:   plist -> A -> B -> NULL
         pnewnode -> A   (新节点.next = plist)
步骤2:   plist -> pnewnode -> A -> B -> NULL   (plist = pnewnode)

尾插 SLTPush_Back(已修复)

void SLTPush_Back(SLTNode **pphead, SLTDataType x)
{
    assert(pphead);
    SLTNode *pnewnode = SLTBuynode(x);
    if (*pphead == NULL)         // 空链表:新节点直接当头
    {
        *pphead = pnewnode;      // 改头指针 → 必须二级指针
    }
    else
    {
        SLTNode *pptail = *pphead;
        while (pptail->next)     // 遍历找尾节点(next==NULL 的)
        {
            pptail = pptail->next;
        }
        // 把新节点接到尾节点后面;赋值方向不能写反
        pptail->next = pnewnode; // ✅ 尾节点.next 指向新节点
    }
}

逐行解释:分两种情况。空表时新节点就是头,要改 *pphead,所以必须二级指针。非空表要遍历到尾节点(next==NULL 的节点),然后把尾节点的 next 指向新节点。

修复前的 bug:原来写成 pnewnode = pptail->next;,这是把新节点指针指向了「尾节点的 next(即 NULL)」,新节点根本没接进链表,数据丢失。正确方向是 pptail->next = pnewnode;——赋值方向 = 数据流方向,让旧尾指向新节点。

为什么尾插 O(n):每次都要从头遍历到尾。如果频繁尾插,应改用带头尾指针的结构,或用双向链表。

尾删 SLTPop_Back

void SLTPop_Back(SLTNode **pphead)
{
    assert(pphead && *pphead);   // 二级指针和链表都不能空
    if ((*pphead)->next == NULL) // 只有一个节点
    {
        free(*pphead);
        *pphead = NULL;          // 删完变空表,改头指针
    }
    else
    {
        SLTNode *ptail = *pphead;
        SLTNode *prev = *pphead; // 前驱指针,紧跟 ptail
        while (ptail->next)     // 找尾节点
        {
            prev = ptail;       // prev 先停在 ptail 当前位置
            ptail = ptail->next; // ptail 往后走
        }
        free(ptail);
        ptail = NULL;
        prev->next = NULL;      // 新尾节点的 next 置空
    }
}

逐行解释:尾删的难点在于单链表无法直接找前驱,删尾节点后要把倒数第二个节点的 next 置空,否则它变成悬空指针。所以用快慢双指针:ptail 跑在前面找尾,prev 紧跟其后始终落后一格,循环结束时 prev 恰好是尾节点的前驱。

边界情况:

  • 空表:assert 拦下。
  • 单节点:删完要改 *pphead = NULL,必须二级指针。
  • 多节点:用双指针找前驱。

头删 SLTPop_Front

void SLTPop_Front(SLTNode **pphead)
{
    assert(*pphead && pphead);
    SLTNode *next = (*pphead)->next; // 先存第二个节点,防丢
    free(*pphead);
    *pphead = next;                  // 头指针指向原第二节点
}

逐行解释:头删 O(1)。必须先用 next 存住原头节点的下一个,否则 free(*pphead) 后原头内存释放,(*pphead)->next 就读不到了。这是「先存后删」的经典套路。

查找 SLTFind

SLTNode * SLTFind(SLTNode *phead, SLTDataType x)
{
    SLTNode *pcur = phead;
    assert(pcur);             // ⚠️ 问题:空链表 phead==NULL 会断言失败
    while (pcur)
    {
        if (pcur->data == x)
            return pcur;       // 找到返回节点指针
        pcur = pcur->next;
    }
    return NULL;              // 没找到
}

逐行解释:遍历比较,找到返回节点指针(供后续 Insert/Erase 用),找不到返回 NULL。

潜在 bug:assert(pcur) 在空链表(phead==NULL)时会直接断言退出,但「空链表里找不到元素」本应正常返回 NULL,不该崩溃。正确做法是删掉这行 assert,让空表正常返回 NULL。

指定位置前插 SLTInsert

void SLTInsert(SLTNode **pphead, SLTNode *pos, SLTDataType x)
{
    assert(*pphead && pphead);
    assert(pos);
    if (*pphead == pos)        // pos 是头节点,退化为头插
    {
        SLTPush_Front(pphead, x);
        return;
    }
    SLTNode *pnewnode = SLTBuynode(x);
    SLTNode *prev = *pphead;
    while (prev->next != pos)   // 找 pos 的前驱
    {
        prev = prev->next;
    }
    pnewnode->next = pos;       // 先连新节点→pos
    prev->next = pnewnode;      // 再连 prev→新节点
}

逐行解释:在 pos 前面插,难点又是找前驱(单链表的痛)。pos 是头节点时没有前驱,单独走头插逻辑。否则遍历找 prev,然后两步接线。

接线顺序(关键):必须先 pnewnode->next = pos(新节点抓住后继),再 prev->next = pnewnode(前驱抓住新节点)。如果先 prev->next = pnewnode,则 prev 原来指向 pos 的链断了,pos 后面整条链丢失。

prev -> pos         插入 pnewnode 到中间
步骤1: pnewnode -> pos   (新节点先抓 pos)
步骤2: prev -> pnewnode   (prev 再抓新节点)
结果:  prev -> pnewnode -> pos

指定位置后插 SLTInsertAfter

void SLTInsertAfter(SLTNode *pos, SLTDataType x)  // 一级指针!
{
    assert(pos);
    SLTNode *pnewnode = SLTBuynode(x);
    pnewnode->next = pos->next;  // 新节点先抓原后继
    pos->next = pnewnode;        // pos 再抓新节点
}

逐行解释:在 pos 后面插,不需要找前驱,O(1)。而且不改头指针,所以只传一级指针 pos。接线顺序同样是「先抓后继,再改前驱」。

规范问题:头文件声明是 SLTInsertAfter(SLTNode *phead, SLTDataType x),形参叫 phead,但实现里叫 pos。虽然类型一致能编译,但名字误导(让人以为要传头指针)。声明和实现的形参名应一致。

删除 pos 节点 SLTErase

void SLTErase(SLTNode **pphead, SLTNode *pos)
{
    assert(pphead && *pphead);
    assert(pos);
    if (pos == *pphead)        // 删的是头节点
    {
        SLTNode *next = (*pphead)->next;
        free(*pphead);
        *pphead = next;         // 改头指针
    }
    else
    {
        SLTNode *prev = *pphead;
        while (prev->next != pos)  // 找前驱
            prev = prev->next;
        prev->next = pos->next;    // 前驱跨过 pos 连后继
        free(pos);
        pos = NULL;                // 置空形参(但对外部指针无效,见下)
    }
}

逐行解释:删头节点单独处理(改头指针)。否则找前驱,让前驱的 next 跨过 pos 直接连 pos->next,再 free(pos)。

陷阱:函数内 pos = NULL 只置空了形参,调用者手里的 pos 指针仍指向已释放内存(悬空指针)。所以 SLTErase 后调用者必须自己把持有的指针置空,否则再用 pos 会 UAF(use after free)。

删除 pos 之后的节点 SLTEraseAfter

void SLTEraseAfter(SLTNode *pos)
{
    assert(pos && pos->next);   // pos 和它的后继都不能空
    SLTNode *del = pos->next;
    pos->next = del->next;      // pos 跨过 del 连 del 的后继
    free(del);
}

逐行解释:删 pos 后面的节点,不用找前驱,O(1)(前驱就是 pos 本身)。让 pos->next 跨过 del 连到 del->next,再 free(del)。

销毁 SLDestroy

void SLDestroy(SLTNode **pphead)
{
    assert(pphead && *pphead);
    SLTNode *pcur = *pphead;
    while (pcur)
    {
        SLTNode *next = pcur->next;  // 先存下一个,否则 free 后读不到
        free(pcur);
        pcur = next;
    }
    *pphead = NULL;  // 头指针置空,防悬空
}

逐行解释:逐个 free 节点。必须先用 next 存住当前节点的下一个,因为 free(pcur) 后 pcur->next 这块内存已释放,再读就是 UAF。最后 *pphead = NULL 让外部头指针置空(要二级指针)。

复杂度分析

操作时间复杂度说明
头插 / 头删O(1)只改头指针和一两个 next
尾插 / 尾删O(n)必须遍历找尾 / 找前驱
指定位置后插 / 后删O(1)已知 pos,不用找前驱
指定位置前插 / 删 posO(n)要遍历找前驱
按值查找O(n)顺序遍历
销毁O(n)逐个释放

核心结论:单链表在头部操作 O(1),在尾部和中间操作要找前驱,O(n)。这正是双向链表要解决的痛点。

常见陷阱清单

  1. 该用二级指针却用一级 → 改了头指针但外部不生效,空表插入/头删失效。
  2. free 前没存 next → UAF,读到已释放内存。
  3. 接线顺序错 → 断链,丢失整段链表。
  4. 尾删不找前驱 → 倒数第二个节点的 next 变悬空指针。
  5. SLTErase 后不置空外部 pos → 悬空指针 UAF。
  6. SLTFind 的 assert(pcur) → 空链表查找直接崩溃。
  7. SLTInsertAfter 声明实现形参名不一致 → 误导调用者。
  8. 赋值方向写反(如修复前的尾插)→ 数据没入链。

我的实现 vs 教科书实现

  • 已修复尾插 bug:pptail->next = pnewnode。
  • 残留问题:SLTFind 的 assert(pcur) 在空表会崩;SLTInsertAfter 声明用 phead 实现用 pos,命名不一致。
  • 缺失接口:没有 SLTSize、反转链表、判环等常用函数。
  • 风格:用二级指针管理头指针,是 C 语言单链表的经典写法(C++ 会用引用 SLTNode*&)。

拓展知识点

变体与进阶

  • 带头单链表:加一个不存数据的哨兵头节点,空表也有头节点,头插/头删逻辑统一,不用特判空表。
  • 双向链表:见项目 List 模块,每个节点多一个 prev 指针,找前驱 O(1)。
  • 循环链表:尾节点 next 指回头节点,适合解决约瑟夫环问题。

常见考点(必刷题)

  • 反转链表:三指针法(prev/cur/next),迭代或递归。
  • 找中间节点:快慢指针,快走两步慢走一步。
  • 判断有环 / 找环入口:快慢指针相遇判环,再从相遇点和头同步走找入口。
  • 合并两个有序链表:dummy 头 + 双指针归并。
  • 删除倒数第 K 个节点:快指针先走 K 步,再快慢同走。

与其他结构的关系

单链表是许多结构的基础:哈希表用链地址法处理冲突时每个桶是链表;图的邻接表用链表存边;LRU 缓存用双向链表(要 O(1) 删任意节点)。栈和队列也可以用单链表实现(头插头删做栈,头删尾插做队列)。

一句话记忆法

单链表:头指针是指南针,next 是绳结;改头用二级指针,删节点先存 next,接线先抓后继再改前驱,找前驱只能从头数。

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

相关推荐

开源项目 ruoyi-sso-oauth2(一)环境配置

Ruoyi若依实现单点登录SSO、oauth2认证

Xiaoweidumpb 9683

网易校招CV算法工程师笔试题复盘:从KMP到Sobel算子

算法工程师岗位的笔试往往不止考查深度学习,数据结构、机器学习与图像处理基础同样占据核心地位。KMP算法作为字符串匹配的经典方法,通过next数组避免主串回溯,是代码题中的高频考点;堆排序与TopK问题则考验对海量数据处理的工程思维。传统优化算法如粒子群算法,模拟鸟群觅食更新速度与位置,体现了启发式搜索在参数调优中的价值。图像处理中的Sobel算子通过卷积核提取边缘梯度,是计算机视觉特征提取的基石。这些看似分散的知识点,其实共同构成了算法岗笔试的底层逻辑。从基础算法原理到工程应用场景,逐一掌握才能真正应对大厂

weixin_34117211的博客 374

AlphaFold-multimer 复合物结构预测

是DeepMind开发的AlphaFold项目的一个扩展,旨在预测蛋白质多聚体的三维结构。蛋白质多聚体是由多个蛋白质亚单位相互组装而成的结构,如酶、膜蛋白复合物和病毒颗粒。理解多聚体的结构对于揭示蛋白质的功能和相互作用非常重要。

MurphyStar的博客 5647

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

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

weixin_44218929的博客 187

Android 导出aar 用于Unity 百度语音唤醒

AndroidStudio版本下载地址Android Studio Arctic Fox Unity版本2021.3.26f1c1 新建AndroidStudio工程 新建module 这里注意一下,网上很多教程说要注意包名要和unity一样,但是测试下来,不一样也可以的 在新建的module里添加这两个jar包,并点击右键add as library 导入jniLibs 右键创建这几个package 代码就安装这个大佬的写参考代码 需要注意的的是在WakeupListener脚本里U

weixin_38777563的博客 633

rk3588 rkaiq_3A_server 无法解析json文件记录

rk3588 rkaiq_3A_server 无法解析json文件记录

neardi_dev的博客 1万+

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

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

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

HRNet 【demo 复现过程】

Deep High-Resolution Representation Learning for Human Pose Estimation

qq_45257495的博客 5556

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

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

ACH_YYX的博客 188

【QGIS入门实战精品教程】1.2:QGIS与ArcGIS的区别和联系

以下是GISGeography官方给出的两者之间的27点比较(译): 官方网址:https://gisgeography.com/qgis-arcgis-differences/ 1.QGIS容纳更多的数据格式 ArcGIS没有支持所有的数据格式,毫无疑问,QGIS在处理各种类型的数据时非常顺手,QGIS在数据格式方面是个多面手,在使用数据方面是它的长处。 QGIS使用GDAL/OGR库来读取和写入地理信息数据格式,支持超过70种矢量格式。QGIS一开始就是为了与PostGIS协作而诞生的,而支持的名单.

智慧空间实验室 2040

R语言广义加型模型(GAM)的运用例子及实现教程

一旦拟合了广义加型模型,我们可以查看模型摘要和诊断,以评估模型的质量和拟合情况。# 查看模型摘要响应变量:mpg(汽车的燃油效率)拟合函数:mpg ~ s(hp) + s(wt)

Mrrunsen的博客 3446

数据结构:9.Map和Set

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

2602_95646088的博客 201

基于Python的上市公司年报PDF文本抓取与财务风险分析系统

随着大数据时代的到来,金融数据分析已成为投资决策和风险管理的重要工具。上市公司年报包含了丰富的财务信息和经营数据,通过对这些数据的提取和分析,可以评估公司的财务状况和潜在风险。本文将详细介绍如何使用Python构建一个完整的上市公司年报文本抓取与财务风险分析系统,涵盖PDF文本提取、数据清洗、财务指标计算和风险分析等全流程。

2201_76125261的博客 312

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

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

NanChen的博客 343

深度学习工具包 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")

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

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

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

2401_87069538的博客 611

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

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

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

笔记-论数据结构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

【数据结构-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的博客 342
上一篇: 数据结构:双向循环链表的全方位解构
下一篇: 从地址到自由:C语言指针核心概念深度复盘与实战指南
DDXYcoder
博客等级 码龄1年 140粉丝 29原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值