2026/5/19 2:02:35
网站建设
项目流程
如何设置网站的关键词,互联网信息服务业务经营许可证,网站建设格式,网页设计制作一个网站聊完算法#xff0c;就不得不说承载算法的 “容器”—— 数据结构。如果说算法是解决问题的 “工序”#xff0c;那数据结构就是存放 “原材料”#xff08;数据#xff09;的 “工具柜”。我年轻时第一次理解 “数据结构”#xff0c;是在给 Z80 汇编程序分配内存时…聊完算法就不得不说承载算法的 “容器”—— 数据结构。如果说算法是解决问题的 “工序”那数据结构就是存放 “原材料”数据的 “工具柜”。我年轻时第一次理解 “数据结构”是在给 Z80 汇编程序分配内存时当时想存 10 个温度数据一开始随手把数据存在 0100H、0105H、010AH 这些零散地址结果读取时要反复查地址表效率极低老师傅提醒我“把数据挨个儿存用一个起始地址就能找到所有数”—— 这就是我对 “数组” 的最初认知也让我明白数据怎么存直接决定算法怎么跑。一、数据结构基础先说说最基础的变量与内存的关系。很多新手觉得 “变量就是一个名字”却不知道它的本质是 “一段内存单元的别名”。在 Z80 汇编里没有高级语言的 “int a”“char b”我们要直接给变量分配内存地址比如用伪指令 “NUM EQU 0100H” 定义变量 NUM本质就是把内存地址 0100H 命名为 NUM往 NUM 里存数据就是往 0100H 这个 8 位内存单元里写二进制数如果要存 16 位的数就需要两个连续单元比如 “ADDR EQU 0200H”ADDR 存低 8 位ADDR1 存高 8 位。变量的类型8 位 / 16 位 / 32 位本质是告诉计算机 “这个变量占用几个连续的内存单元”。我年轻时调试程序曾犯过一个低级错误把 16 位的地址数据存进 8 位的变量内存单元结果数据被截断程序跑飞 —— 这也让我记住变量的核心是 “内存单元的映射”操作变量就是操作对应的内存类型错了内存操作就会出错。再说说数组的本质连续的内存块。数组不是 “多个变量的简单集合”而是 “一段连续、等长的内存空间”每个元素占用相同大小的内存单元通过 “基地址 偏移量” 就能快速访问任意元素。比如定义一个 5 元素的 8 位数组基地址是 0300H那么第 1 个元素0300H偏移量 0第 2 个元素0301H偏移量 1第 5 个元素0304H偏移量 4这种连续存储的优势是 “随机访问”—— 不管访问第几个元素计算地址的时间都一样这也是数组最核心的特性。我年轻时用数组存 100 以内的素数要找第 20 个素数只需计算 “基地址 19” 就能直接读取而如果用零散内存存储得逐个查找效率差了几十倍。对比零散内存数组的连续存储还有一个关键优势算法能利用 “偏移量” 快速遍历。比如遍历数组求和只需从基地址开始每次偏移量 1直到遍历完所有元素这是循环算法最适配的存储方式 —— 这也是 “数据结构匹配算法” 的第一个体现算法的遍历逻辑决定了数组这种连续存储的合理性。二、典型数据结构掌握了数组这个基础接下来要吃透几个典型数据结构 —— 栈、队列、链表、二叉树其中栈与队列的存取逻辑、链表的连接机制是教学的核心重点。我结合早年的汇编实践聊聊这些数据结构的本质和实现思路。1. 栈LIFO后进先出的 “叠盘子” 逻辑栈的核心逻辑是LIFOLast In First Out后进先出就像我们叠盘子最后放上去的盘子要先拿下来只能操作最顶端的盘子。在计算机里栈的实现很简单用一段连续数组作为栈空间再用一个 “栈顶指针”SPStack Pointer指向栈顶元素的位置。栈的存取逻辑栈只有两个核心操作压栈PUSH和出栈POP所有操作都只针对栈顶不能访问栈中间的元素压栈先把栈顶指针向 “栈底方向” 移动一个单元比如 Z80 里 SP 减 1再把数据存入指针指向的内存单元先移指针再存数据。出栈先读取栈顶指针指向的内存单元数据再把指针向 “栈顶方向” 移动一个单元先取数据再移指针。我用 Z80 汇编举个实际例子定义栈空间为 0400H-04FFH初始栈顶指针 SP0500H栈底 1; 压栈把累加器A里的数比如5存入栈 PUSH A ; 汇编指令等价于SPSP-1 → (SP)A ; 此时SP04FFH04FFH单元存5 ; 出栈把栈顶数据取到B寄存器 POP B ; 汇编指令等价于B(SP) → SPSP1 ; 此时B5SP0500H我年轻时用栈实现过 “中断现场保护”CPU 响应中断时自动把 PC、AF 等寄存器压栈中断处理完后再出栈恢复 —— 这正是栈的核心应用场景临时存储、嵌套操作比如函数调用、中断因为后进先出的逻辑能完美匹配 “最后执行的代码先返回” 的需求。2. 队列FIFO队列的核心逻辑是FIFOFirst In First Out先进先出就像排队买票先到的人先买只能从队尾进、队头出。队列的实现需要数组 两个指针队头指针FRONT指向队头元素队尾指针REAR指向队尾元素的下一个位置为了避免数组空间浪费通常用 “循环队列” 实现。队列的存取逻辑队列的核心操作是入队ENQUEUE和出队DEQUEUE操作分别针对队尾和队头入队先判断队列是否满若未满把数据存入队尾指针指向的单元再把队尾指针按数组长度取模循环移动一个单元比如数组长度 10REAR9 则移到 0。出队先判断队列是否空若不空读取队头指针指向的单元数据再把队头指针按数组长度取模移动一个单元。我早年用队列处理串口接收的数据串口每收到一个字节就入队CPU 空闲时从队头取数据处理。比如数组队列基地址 0600H长度 8初始 FRONT0、REAR0; 入队把A里的数存入队列 ENQUEUE: LD HL, FRONT LD DE, REAR CP HL, DE1 ; 判断是否满循环队列队尾1队头则满 JR Z, QUEUE_FULL LD (0600HDE), A ; 数据存入队尾 INC DE ; 队尾指针1 LD REAR, DE RET ; 出队把队头数据取到A DEQUEUE: LD HL, FRONT LD DE, REAR CP HL, DE ; 判断是否空队头队尾则空 JR Z, QUEUE_EMPTY LD A, (0600HHL) ; 读取队头数据 INC HL ; 队头指针1 LD FRONT, HL RET队列的核心应用场景是 “按顺序处理任务”比如打印机队列、任务调度队列先进先出的逻辑能保证任务的执行顺序这是栈无法替代的。3. 链表链表和数组是完全相反的存储逻辑数组是连续内存链表是非连续的零散内存块靠 “指针” 串联 —— 这也是教学的核心难点链表的连接机制。链表的连接机制链表的基本单元是 “节点”每个节点包含两部分数据域存储实际数据比如 8 位数字、16 位地址指针域存储下一个节点的内存地址指针—— 这就是 “自我引用” 的核心节点里存着指向同类型节点的指针。指针的作用是 “纽带”把原本零散的内存节点串联成一个整体访问链表时从 “头节点” 开始通过每个节点的指针域找到下一个节点直到指针域为 0尾节点。我年轻时用 Z80 汇编模拟链表当时没有高级语言的结构体直接用内存单元模拟节点; 节点10700H数据域5、0701H指针域0702H → 节点2地址 ; 节点20702H数据域8、0703H指针域0704H → 节点3地址 ; 节点30704H数据域3、0705H指针域0000H → 尾节点 ; 头节点地址0700H ; 遍历链表从头部开始逐个读取数据 TRAVERSE: LD HL, 0700H ; HL指向头节点 LOOP: LD A, (HL) ; 读取数据域5→8→3 ; 处理数据比如显示 INC HL LD DE, (HL) ; 读取指针域下一个节点地址 LD HL, DE CP HL, 0000H ; 判断是否尾节点 JR NZ, LOOP RET链表的核心优势是 “动态增删”插入 / 删除节点时只需修改指针域的地址不用像数组那样移动大量元素。比如在节点 1 和节点 2 之间插入新节点只需把节点 1 的指针域改为新节点地址新节点的指针域改为节点 2 地址全程只需修改两个内存单元 —— 这也是链表比数组灵活的关键。4. 二叉树二叉树是一种分层逻辑结构每个节点最多有两个子节点左子节点和右子节点核心逻辑是 “分类存储”—— 比如左子树的所有节点值都小于父节点右子树都大于父节点。我年轻时用二叉树实现过 “快速查找 100 以内的数”根节点存 50要找 30 就查左子树找 60 就查右子树只需几步就能定位比数组遍历快得多。二叉树不用像栈、队列那样依赖连续内存每个节点的指针域只需存左、右两个子节点的地址核心是 “分层决策” 的逻辑这也是后续复杂数据结构比如红黑树、B 树的基础。三、数据结构与算法算法和数据结构是 “鱼和水” 的关系再好的算法没有适配的数据结构效率也会大打折扣。我结合早年实践聊聊几种经典的 “数据结构 算法” 匹配场景1. 数组 冒泡排序冒泡排序的核心是 “相邻元素比较交换”需要快速访问任意位置的元素 —— 数组的 “基地址 偏移量” 正好满足这个需求。比如对数组 [9,3,7] 做冒泡排序访问 0300H9和 0301H3交换得 [3,9,7]访问 0301H9和 0302H7交换得 [3,7,9]。如果用链表存储这三个数每次交换需要修改指针效率比数组低得多 —— 这就是匹配的关键算法需要随机访问就选数组算法需要动态增删就选链表。2. 栈 括号匹配算判断 “((()))”“()[]” 这类括号是否匹配用栈最合适遇到左括号压栈遇到右括号出栈并对比是否匹配最后栈空则匹配成功。比如处理 “(())”左括号 “(” 压栈 → 栈[ (]左括号 “(” 压栈 → 栈[ (, (]右括号 “)” 出栈对比匹配 → 栈[ ( ]右括号 “)” 出栈对比匹配 → 栈空匹配成功。栈的后进先出逻辑完美适配括号的嵌套结构这是队列、数组无法替代的。3. 队列 任务调度算法早期微型机的打印机调度用队列存储打印任务用户提交的第一个任务入队头第二个入队尾打印机从队头依次取任务打印保证 “先提交先打印”—— 这正是队列 FIFO 逻辑的核心应用若用栈则会 “后提交先打印”不符合需求。4. 链表 插入排序插入排序的核心是 “找到位置后插入元素”用链表存储时只需修改指针就能完成插入而用数组则需要移动插入位置后的所有元素。比如给链表 [3,7,9] 插入 5找到 3 和 7 之间的位置把 3 的指针域改为 5 的节点地址5 的指针域改为 7 的地址全程只需修改两个指针而数组插入则需要把 7、9 后移一位效率差距随数据量增大而扩大。最后小结回顾和数据结构打交道的经历从早期用汇编手动分配数组内存到用指针模拟链表再到用栈处理中断、用队列调度任务深刻体会到数据结构的核心是 “数据的存储方式”而存储方式决定了算法的执行效率。从程序员角度核心要掌握两点栈与队列的存取逻辑栈是 “栈顶操作、后进先出”适配临时存储、嵌套操作队列是 “头尾操作、先进先出”适配顺序任务处理两者都依赖数组 指针实现操作仅限指定位置栈顶 / 队头 / 队尾。链表的连接机制指针内存地址是链表的 “纽带”节点通过指针域串联动态增删无需移动元素这是链表与数组最核心的区别也是理解 “非连续存储” 的关键。很多人学习数据结构最容易犯的错是 “不管场景选数组”—— 比如需要频繁增删数据时用数组导致算法效率低下。我的建议是先想清楚算法的核心操作遍历增删嵌套顺序执行再选适配的数据结构需要随机访问、遍历 → 数组需要临时存储、嵌套 → 栈需要顺序执行、排队 → 队列需要动态增删、非连续存储 → 链表需要分层查找、分类存储 → 二叉树。如今高级语言已经封装了现成的栈、队列、链表库但理解它们的底层逻辑内存布局、指针操作、存取规则依然重要 —— 因为不管封装多深数据结构的核心逻辑从未改变。现在用 Python 的 list 模拟栈逻辑都是 “后进先出”只是实现方式不同而已。掌握了数据结构再结合之前的算法知识才算真正理解了程序的核心用合适的方式存储数据用高效的步骤处理数据 —— 这也是编程的本质让计算机用最合理的方式解决问题。