当前位置: 首页 > news >正文

数据结构入门:线性表(Day 1)——从原理到代码实战

📚 数据结构入门:线性表(Day 1)——从原理到代码实战

day 1的内容只是一个大概,初学者看不太懂的话也没关系,具体详细的每一部分精讲都会在后续的内容当中体现

自由的前提是自律,加油冲起来


🌟 一、线性表:数据世界的“排队规则”

线性表是数据的“一维队列”,核心特性是元素首尾相接、类型统一,就像地铁🚇的乘客队列:

  • 前驱与后继:除首元素无前驱、尾元素无后继,其他元素均有唯一“邻居”
  • 逻辑与物理结构的对比
    • 逻辑结构:线性关系(1对1)
    • 物理结构:顺序存储(数组) vs 链式存储(指针)
🔍 两大物理实现对比
特性顺序表(数组)链表
内存分配连续内存块动态分散存储
查询速度O(1)(直接下标访问✨)O(n)(需遍历🚶♂️)
插入/删除效率O(n)(需移动元素📦)O(1)(修改指针即可🔗)
空间灵活性固定容量,扩容成本高💸按需分配,无内存浪费🌱

🏗️ 二、顺序表:内存中的“连续公寓”

顺序表动态扩容流程

在这里插入图片描述

🔧 C语言代码实现(动态扩容版)
typedef struct {int *data;      // 动态数组指针int length;     // 当前长度int capacity;   // 总容量
} SeqList;// 初始化(默认容量10)
void InitList(SeqList *L) {L->data = (int*)malloc(10 * sizeof(int));L->length = 0;L->capacity = 10;
}// 扩容操作(翻倍策略)
void Expand(SeqList *L) {int *new_data = (int*)malloc(2 * L->capacity * sizeof(int)); // 容量翻倍for(int i=0; i<L->length; i++) new_data[i] = L->data[i];      // 数据迁移free(L->data);                 // 释放旧内存L->data = new_data;            // 指向新数组L->capacity *= 2;              // 更新容量
}

💡 高频面试题:为什么顺序表扩容常采用翻倍策略?
(答案:均摊时间复杂度优化,避免频繁扩容)


⛓️ 三、链表:指针串起的“数据珍珠”

双向链表插入操作可视化

在这里插入图片描述

🔧 双向链表实现(支持反向遍历)
typedef struct DNode {int data;struct DNode *prior;  // 指向前驱的指针struct DNode *next;   // 指向后继的指针
} DNode;void InsertAfter(DNode *p, DNode *s) {s->next = p->next;    // ① 新节点后继指向p的后继if(p->next) p->next->prior = s; // ② p原后继的前驱指向sp->next = s;          // ③ p的后继改为ss->prior = p;         // ④ s的前驱指向p
}

🔍 链表变种大全

  • 单链表(➡️单向指针)
  • 双向链表(↔️双向指针)
  • 循环链表(🔁首尾相连)
  • 静态链表(用数组模拟📇)

🚀 四、线性表实战:从理论到工程

LRU缓存算法流程图解

在这里插入图片描述

案例:用链表实现LRU缓存淘汰算法
// 链表节点结构
typedef struct CacheNode {int key;int value;struct CacheNode *prev;struct CacheNode *next;
} CacheNode;// 访问数据时的链表调整
void UpdateLRU(CacheNode *node, CacheNode **head) {// 1. 断开当前节点node->prev->next = node->next;node->next->prev = node->prev;// 2. 插入到链表头部node->next = (*head)->next;node->prev = *head;(*head)->next->prev = node;(*head)->next = node;
}

📈 应用场景:Redis内存管理、浏览器缓存、CPU缓存体系


📝 五、考研真题精选

📌 考研真题大全解(线性表篇)
精选15年高频考题+深度解析,建议收藏反复练习!

🔥 真题1:顺序表删除重复元素(2023年408真题)

题目
设计算法删除递增顺序表中所有重复元素,使每个元素只出现一次。要求时间复杂度O(n),空间复杂度O(1)。
示例
原表:[1,2,2,3,3,3,4] → 新表:[1,2,3,4]

答案

void DeleteDuplicates(SeqList *L) {if (L->length == 0) return;int k = 0; // 新表指针for (int i=1; i<L->length; i++) {if (L->data[i] != L->data[k]) {L->data[++k] = L->data[i];}}L->length = k + 1;
}

解析

  • 双指针法k指向已处理部分的末尾,i扫描未处理部分
  • 核心逻辑:当发现新元素时,k先自增再赋值(类似“快慢指针”)
  • 易错点:忘记处理空表或长度为1的特殊情况❗

🔥 真题2:链表合并(2020年真题)

题目
将两个非递减有序单链表合并为一个非递减有序单链表,要求用原链表节点,不得开辟新内存。

答案

Node* MergeList(Node *La, Node *Lb) {Node *dummy = (Node*)malloc(sizeof(Node)); // 虚拟头节点Node *tail = dummy;while (La && Lb) {if (La->data <= Lb->data) {tail->next = La;La = La->next;} else {tail->next = Lb;Lb = Lb->next;}tail = tail->next;}tail->next = La ? La : Lb; // 拼接剩余部分return dummy->next;
}

解析

  • 虚拟头节点技巧:避免处理空链表的边界条件
  • 尾插法:始终维护 tail指针指向合并后的链表末尾
  • 断链风险:修改指针前必须保存后继节点(如 La = La->next)⚠️

🔥 真题3:循环链表判环(2016年真题)

题目
设计算法判断单链表是否有环,若有环返回环的入口节点。要求空间复杂度O(1)。

答案

Node* DetectCycle(Node *head) {Node *slow = head, *fast = head;while (fast && fast->next) {slow = slow->next;fast = fast->next->next;if (slow == fast) { // 相遇点Node *p1 = head, *p2 = slow;while (p1 != p2) { p1 = p1->next;p2 = p2->next;}return p1; // 入口点}}return NULL; // 无环
}

解析

  • 快慢指针法:快指针每次走2步,慢指针走1步
  • 数学原理:相遇时,从 头节点相遇点同速出发必在入口点相遇
  • 复杂度:时间复杂度O(n),空间O(1)(优于哈希表法🚀)

🔥 真题4:顺序表真题(2017年真题)

题目
已知顺序表L中元素按值递增排列,设计算法删除值在 [x,y]之间的所有元素,要求时间O(n),空间O(1)。

答案

void DeleteRange(SeqList *L, int x, int y) {int k = 0; // 新表指针for (int i=0; i<L->length; i++) {if (L->data[i] < x || L->data[i] > y) {L->data[k++] = L->data[i];}}L->length = k;
}

解析

  • 筛选保留法:只保留不在区间内的元素
  • 优化点:利用有序特性可二分查找边界,但遍历法更简单直接
  • 易错点:未处理区间边界相等的情况(如x=y)🔍

🧩 真题5:链表综合题(2024年新大纲样题)

题目
设计算法将单链表 L中所有奇数位置的节点移到偶数位置节点之后。
示例
输入:1→2→3→4→5→NULL
输出:2→4→1→3→5→NULL

答案

void Rearrange(Node *head) {if (!head || !head->next) return;Node *odd = head->next; // 偶数链表头Node *even = head;      // 奇数链表头Node *p = odd;while (p && p->next) {even->next = p->next;even = even->next;p->next = even->next;p = p->next;}even->next = odd; // 拼接奇偶链表
}

解析

  • 双链表分离法:将奇数节点和偶数节点拆分为两个链表再合并
  • 指针操作:注意在修改 next前保存原后继节点
  • 边界处理:链表长度为奇数/偶数的不同情况测试❗

🧩 课后
// 已知动态顺序表结构体定义
typedef struct {int *array;int size;int capacity;
} DynamicArray;// 请编写删除所有偶数的算法(要求时间复杂度O(n))
void RemoveEvenNumbers(DynamicArray *da) {// 你的代码写在这里...
}

💡 解题锦囊:双指针法+扩容逆用,评论区留下你的答案雏形!


📊 历年考点统计表

考点出现年份出现频次
顺序表删除操作2017,2023★★★★☆
链表合并/拆分2020,2024★★★★☆
链表环检测2016,2019★★★☆☆
链表逆置/重组2022,2021★★★★★
顺序表查找2018,2015★★☆☆☆

🎯 下期高能剧透!《顺序表:内存刺客 VS 性能王者の终极对决》

🔥 你将解锁这些硬核内容

💥 动态扩容の黑暗兵法

🔧 从「固定数组」到「翻倍策略」,揭秘顺序表如何用「空间换时间」统治数据结构江湖!

插入第11个元素
初始容量10
申请20空间
复制旧数据
旧内存销毁
新王者诞生!

💡 灵魂拷问:为什么Java的ArrayList默认扩容1.5倍?答案下期揭晓!(也可以去我的专栏Java 集合框架大师课:集合框架源码解剖室(五))

效率暴击全对比

🚀 手撕四大操作复杂度,用真实代码告诉你:

操作时间复杂度实战场景翻车案例
随机访问O(1)✨高频查询类API越界访问导致Segmentation Fault😱
尾部插入O(1)🎯实时日志采集系统未预分配空间引发频繁扩容💸
中间插入O(n)💣游戏技能队列插队百万级数据插入卡死界面🖥️
范围删除O(n)🌀数据库批量删除操作未重置length引发内存泄漏💦
🚀 三大工业级实战
  1. Redis字符串:SDS如何用预分配+惰性删除吊打C字符串?
  2. Tensor底层:NDArray如何用stride魔法实现超高速切片?
  3. MMORPG地图:游戏场景区块为何必须用顺序表存储?

🌌 下期更将放出

  • 顺序表在Linux内核中的魔鬼优化(slab分配器)
  • 用SIMD指令集暴力提升顺序表性能300%的骚操作
  • 10年408真题顺序表题型解题模板(附记忆口诀)

👉 点击关注不迷路,源码级解析即将空降!

相关文章:

  • STL c++ 详解——stack与queue模拟实现与deque的介绍
  • 【Sequelize】
  • 地理人工智能中位置编码的综述:方法与应用
  • VMware下Ubuntu空间扩容
  • 开展东南亚货运专线业务,有哪家系统提高管理效率?
  • flutter json解析增强
  • Android 9.0系统源码定制:实现开机启动特定App的全面指南
  • 《分布式软总线:不同频段Wi-Fi环境下设备发现兼容性难题》
  • leetcode面试经典算法题——2
  • 微店商品详情API接口:功能解析与数据应用实践
  • LLM-as-Judge真的更偏好AI输出?
  • 鸿蒙应用元服务开发-Account Kit配置登录权限
  • Prometheus架构组件
  • 国内开源医疗模型研究报告
  • 自动化测试工具playwright中文文档-------14.Chrome 插件
  • 如何在NS3中搭建窄带干扰和扫频干扰场景?
  • 844. 比较含退格的字符串
  • 安装SQLServer管理工具
  • 日语学习-日语知识点小记-构建基础-JLPT-N4阶段(4): 可能形(かのうけい)
  • Coze平台技术解析:零代码AI开发与智能体应用实践
  • 王宝强谈《棋士》:饰演这个灰度人物有一种被割裂的痛苦
  • 上海:全面建设重复使用火箭创新高地、低成本商业卫星规模制造高地
  • 著名文学评论家、清华大学中文系教授蓝棣之逝世
  • 习近平向气候和公正转型领导人峰会发表致辞
  • 宁夏回族自治区人大环境与资源保护委员会主任委员张柏森被查
  • “低头捡星光”,艺术创作直面三江源生态保护