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

408《数据结构》——第二章:线性表

文章目录

  • 第二章:线性表
    • 核心内容总结
      • 1. 线性表的定义与基本概念
      • 2. 线性表的顺序表示与实现(顺序表)
      • 3. 线性表的链式表示与实现(链表)
      • 4. 顺序表 vs. 链表的比较
      • 5. 线性表的应用
    • 考研备考重点与难点
    • 备考建议

考研408《数据结构》第二章“线性表”的详细总结,紧密结合考研要求,突出重点和难点。

第二章:线性表

线性表是整个数据结构的基础,也是考研中的绝对重点。本章内容逻辑清晰,但细节繁多,需要深入理解两种存储结构(顺序和链式)的实现原理、操作算法及其复杂度分析。

核心内容总结

1. 线性表的定义与基本概念

  • 定义: 线性表(Linear List)是具有相同数据类型n (n ≥ 0) 个数据元素的有限序列。记作:L = (a₁, a₂, ..., aᵢ, ..., aₙ)
  • 关键特性:
    • 有限性: 元素个数有限。
    • 相同类型: 所有元素属于同一数据对象。
    • 序列性: 元素之间存在严格的顺序关系
      • 存在唯一的“第一个”元素(表头元素,无直接前驱)。
      • 存在唯一的“最后一个”元素(表尾元素,无直接后继)。
      • 除表头和表尾元素外,每个元素 aᵢ (1 < i < n) 都有且仅有一个直接前驱 aᵢ₋₁一个直接后继 aᵢ₊₁
  • 逻辑结构: 一对一的线性关系。是线性结构的典型代表。
  • 基本操作(ADT定义的核心):
    • 初始化 InitList(&L): 构造一个空的线性表L。
    • 销毁 DestroyList(&L): 释放线性表L占用的内存空间。
    • 判空 ListEmpty(L): 若L为空表,则返回true,否则返回false。
    • 求长度 ListLength(L): 返回L中数据元素的个数。
    • 按位查找 GetElem(L, i, &e): 用e返回L中第 i (1 ≤ i ≤ n) 个元素的值。
    • 按值查找 LocateElem(L, e): 返回L中第一个其值与 e 相等的元素的位序。若不存在,则返回0(或特定值)。
    • 插入 ListInsert(&L, i, e): 在L的第 i (1 ≤ i ≤ n+1) 个位置之前插入新的元素 e。L的长度增1。
    • 删除 ListDelete(&L, i, &e): 删除L的第 i (1 ≤ i ≤ n) 个位置的元素,并用 e 返回其值。L的长度减1。
    • 遍历 ListTraverse(L, visit()): 依次对L的每个元素调用函数 visit() 进行操作(如打印)。
    • (其他操作:清空、求前驱、求后继等,视具体ADT定义而定)
  • ADT定义: ADT List { ... } (包含上述基本操作)

2. 线性表的顺序表示与实现(顺序表)

  • 存储原理: 用一组地址连续的存储单元依次存储线性表中的数据元素。逻辑上相邻的元素,其物理位置也相邻。
  • 实现方式:
    • 静态分配: 使用定长数组。#define MaxSize 50; ElemType data[MaxSize]; int length;
      • 优点: 简单。
      • 缺点: 空间大小固定,一旦 length > MaxSize 则发生上溢,无法动态扩展;容易造成空间浪费。
    • 动态分配: 使用指针和动态内存分配。ElemType *data; int MaxSize, length; 初始化时 data = (ElemType*)malloc(sizeof(ElemType)*InitSize);
      • 优点: 可以动态扩展空间(使用 realloc)。
      • 缺点: 扩展时需要移动大量元素,开销大;仍可能存在空间分配失败问题。
  • 特点:
    • 随机访问: 通过首地址元素序号(下标)可以在 O(1) 时间内访问任意元素。LOC(aᵢ) = LOC(a₁) + (i-1) * sizeof(ElemType)
    • 存储密度高: 只存储数据元素本身。
  • 基本操作实现与时间复杂度分析:
    操作算法描述最好情况最坏情况平均情况说明
    插入1. 检查i合法性 (1≤i≤n+1)
    2. 检查空间满?
    3. 将第i至第n个元素后移一位
    4. 在位置i放入e
    5. length++
    O(1) (插表尾)O(n) (插表头)O(n)移动次数 = n - i + 1
    删除1. 检查i合法性 (1≤i≤n)
    2. 取aᵢ到e
    3. 将第i+1至第n个元素前移一位
    4. length–
    O(1) (删表尾)O(n) (删表头)O(n)移动次数 = n - i
    按位查找直接通过下标访问 data[i-1]O(1)O(1)O(1)随机访问特性
    按值查找顺序扫描数组,比较元素值O(1) (在表头)O(n) (在表尾/不存在)O(n)
    求长度返回lengthO(1)O(1)O(1)
    判空判断length == 0O(1)O(1)O(1)
  • 优缺点总结:
    • 优点: 随机访问快(O(1));存储密度高。
    • 缺点: 插入/删除需要移动大量元素(O(n));需要预分配/连续空间,静态分配不灵活,动态分配扩展开销大。

3. 线性表的链式表示与实现(链表)

链式存储通过指针表示元素间的逻辑关系。不需要连续存储空间。核心概念是结点(Node)

  • 单链表 (Singly Linked List):

    • 结点结构: 数据域 (data) + 指针域 (next)
      typedef struct LNode {ElemType data;          // 数据域struct LNode *next;     // 指针域,指向下一个结点
      } LNode, *LinkList;
      
    • 头指针: 指向链表中第一个结点(首元结点)的指针。是链表的标识。LinkList L;
    • 头结点:
      • 概念:在单链表的第一个结点之前附加的一个结点。
      • 数据域:通常不存储信息(或存储表长等元信息)。
      • 指针域:指向首元结点。
      • 引入头结点的优点:
        1. 统一操作: 对首元结点的插入/删除操作与其他位置结点的操作逻辑完全一致(无需特殊处理空表或在表头插入/删除)。
        2. 简化判空: 空表时,头结点的指针域为 NULL (L->next == NULL)。
        3. 头指针不变: 头指针始终指向头结点,无论链表如何变化(插入/删除首元结点)。
      • 引入头结点的缺点: 多占用一个结点的空间(存储密度略微降低)。
    • 基本操作实现与时间复杂度分析:
      操作算法描述时间复杂度说明
      建立头插法: 新结点始终插入头结点之后。生成链表顺序与输入顺序相反
      尾插法: 需维护尾指针,新结点插入尾部。生成链表顺序与输入顺序相同
      O(n)头插法常用于逆序。尾插法需要记住尾指针位置。
      插入1. 找到第 i-1 个结点(前驱结点p)
      2. 创建新结点s,s->data = e
      3. s->next = p->next
      4. p->next = s
      O(1) (已知p)
      O(n) (需查找p)
      核心:修改指针顺序(先连后断)。在p后插入只需O(1)。按位插入需查找O(n)。
      删除1. 找到第 i-1 个结点(前驱结点p)
      2. q = p->next (指向要删除的结点)
      3. p->next = q->next
      4. e = q->data; free(q)
      O(1) (已知p)
      O(n) (需查找p)
      核心:修改指针并释放空间。删除p后的结点只需O(1)。按位删除需查找O(n)。
      按位查找从首元结点开始,顺指针next域逐个向后搜索,直到第i个结点。O(n)
      按值查找从首元结点开始,顺指针next域逐个向后比较data域,直到找到值等于e的结点。O(n)
      求长度设置计数器,从头结点(或首元结点)开始遍历整个链表计数。O(n)顺序表O(1),链表O(n)是重要区别。
      判空 (带头结点)判断 L->next == NULLO(1)
    • 优缺点总结:
      • 优点: 插入/删除操作方便快速(在已知位置插入/删除只需修改指针O(1));不需要预分配连续空间,空间分配灵活。
      • 缺点: 不能随机访问,查找元素需要顺序扫描(O(n));存储密度较低(需要额外空间存储指针);访问元素需要从头指针开始遍历。
  • 双链表 (Doubly Linked List):

    • 结点结构: prior指针域 + 数据域 (data) + next指针域
      typedef struct DNode {ElemType data;struct DNode *prior, *next; // 指向前驱和后继
      } DNode, *DLinklist;
      
    • 优点: 可以双向遍历链表。插入和删除操作更灵活(尤其当需要定位前驱结点时,单链表需要O(n)时间查找前驱)。
    • 插入操作 (在p结点后插入s):
      s->next = p->next;
      if (p->next != NULL) p->next->prior = s; // 如果p不是最后一个结点
      s->prior = p;
      p->next = s;
      
    • 删除操作 (删除p结点):
      p->prior->next = p->next;
      if (p->next != NULL) p->next->prior = p->prior;
      free(p);
      
    • 时间复杂度: 在已知结点p位置进行插入或删除操作的时间复杂度为 O(1)(无需查找前驱)。
  • 循环链表 (Circular Linked List):

    • 单循环链表: 表中最后一个结点的指针域指向头结点(带头结点时)或首元结点(不带头结点时)。整个链表形成一个环。
      • 判空:L->next == L (带头结点)
      • 优点:从表中任意结点出发均可访问到表中所有结点。常用于需要循环处理的场景(如约瑟夫问题)。
    • 双循环链表: 在双链表的基础上,头结点的prior指向尾结点,尾结点的next指向头结点。
      • 判空:L->next == L && L->prior == L (带头结点)
  • 静态链表 (Static Linked List):

    • 原理: 借助数组来描述链式结构。数组元素包含 data游标 cur (相当于指针,存储下一个元素在数组中的下标)。
    • 实现:
      #define MaxSize 50
      typedef struct {ElemType data;int cur; // 游标,0号单元cur指向第一个备用结点(空闲结点),最后一个空闲结点cur=0
      } SLinkList[MaxSize];
      
    • 特点:
      • 需要预先分配一个较大的连续数组空间。
      • 插入/删除操作不需要移动元素,只需要修改游标(类似链表修改指针)。
      • 失去了顺序表随机存取的特性。
      • 解决了在不支持指针的高级语言(如早期Basic、Fortran)中实现链表的问题。现代应用较少,但考研中可能涉及原理理解。
    • 操作: 分配空闲结点(从备用链表中取)、回收空闲结点(放回备用链表)、插入、删除等操作需维护游标。

4. 顺序表 vs. 链表的比较

比较项目顺序表链表适用场景
存储空间连续存储单元离散存储单元(通过指针链接)
存储密度高(=1,只存数据)较低(<1,需额外存储指针)空间紧张优先顺序表
随机访问支持,O(1)不支持,O(n)需要频繁按序号访问用顺序表
插入/删除操作平均需移动约一半元素,O(n)修改指针,O(1) (已知位置) / O(n) (需查找)需要频繁插入/删除用链表
空间分配静态分配:固定大小,易溢出/浪费
动态分配:可扩展但效率低
动态分配,按需申请,灵活高效表长变化大,难以预估用链表
缓存友好性好(空间局部性)
实现难度简单较复杂(指针操作需谨慎)
典型应用数组、堆栈、队列(顺序实现)堆栈、队列(链式实现)、树、图的邻接表

5. 线性表的应用

  • 基于线性表的操作实现更复杂的结构或算法(如多项式相加、集合运算)。
  • 作为其他数据结构(栈、队列、字符串)的基础实现方式(顺序存储或链式存储)。

考研备考重点与难点

  1. 线性表ADT的理解: 熟练掌握线性表的定义、特性(序列性、前驱后继关系)和基本操作的含义。
  2. 顺序表的实现与操作:
    • 深刻理解顺序存储的原理(逻辑相邻=物理相邻)。
    • 重点掌握插入、删除操作的算法步骤和代码实现,及其时间复杂度分析(移动元素的计算)。
    • 理解静态分配和动态分配的区别与限制。
  3. 单链表的实现与操作 (重中之重!):
    • 透彻理解带头结点和不带头结点单链表的区别(特别是对表头操作的影响)。 考研中绝大多数链表操作基于带头结点链表。
    • 熟练掌握单链表的建立(头插法、尾插法)、插入、删除操作的算法步骤、代码实现和指针修改顺序。 “先连后断”是核心口诀。
    • 熟练掌握按位查找、按值查找、求长度、判空等操作的实现和时间复杂度。
    • 理解头插法产生逆序的原因。
  4. 双链表与循环链表:
    • 掌握双链表结点的结构和特点(prior指针)。
    • 熟练掌握在已知结点p位置进行插入和删除操作的算法步骤和指针修改(注意修改prior和next两个方向)。
    • 理解循环单链表/双链表的判空条件。
    • 了解循环链表在解决特定问题(如约瑟夫环)时的优势。
  5. 静态链表: 理解其工作原理(用数组+游标模拟指针)、优缺点和适用场景。能看懂静态链表的基本操作。
  6. 顺序表 vs. 链表的综合比较: 能根据应用场景(访问模式、插入删除频率、空间要求)选择合适的存储结构。表格对比中的各项内容是选择题和简答题高频考点。
  7. 算法设计与分析:
    • 链表操作边界条件: 空表、表头/表尾插入删除、查找不到元素等情况的处理必须严谨。
    • 双指针技巧: 链表问题中常用技巧(如快慢指针找中点、判断环、倒数第k个结点;前后指针删除结点)。
    • 递归: 理解链表遍历、反转等操作的递归实现(虽然空间复杂度O(n),但有助于理解递归思想)。
    • 复杂度分析: 能准确分析给定链表算法(特别是涉及遍历、查找的操作)的时间复杂度和空间复杂度。

备考建议

  1. 动手写代码: 链表操作极易出错(指针丢失、内存泄漏、边界条件)。务必亲自编写、调试本章所有核心操作的代码(建立、插入、删除、查找、反转、合并等),理解每一步指针的变化。画图辅助理解指针操作过程。
  2. 深入理解指针: 链表的核心在于指针操作。确保对C语言的指针概念(指向、解引用、指针运算)有扎实掌握。理解 LinkList L (头指针) 和 LNode *p (结点指针) 的区别与联系。
  3. 重视时间复杂度: 时刻牢记顺序表和链表在不同操作上的时间复杂度差异,这是选择题和分析题的核心考点。
  4. 对比记忆: 将顺序表和链表的关键特性、操作复杂度做成对比表格,反复记忆。
  5. 研究真题: 历年考研真题中关于线性表(尤其是链表)的题目非常多,题型涵盖选择题(概念、复杂度、简单操作结果)、应用题(设计算法解决特定问题)、算法题(实现指定操作)。认真分析真题,掌握命题规律和答题技巧。
  6. 理解“带头结点”的优越性: 考研题目和标准实现几乎都使用带头结点的链表,务必习惯并深刻理解其带来的操作统一性。
  7. 注意边界和异常: 在编写和阅读算法时,特别注意处理非法位置(i<1或i>n+1)、空表、空间分配失败等异常情况。健壮性是算法设计的重要要求。

总结: 第二章“线性表”是数据结构大厦的第一块坚实基石,其重要性不言而喻。核心在于透彻理解顺序存储和链式存储(特别是带头结点的单链表)的实现原理、操作算法(插入、删除、建立)及其时间复杂度分析,并熟练掌握两者的优缺点对比和应用场景选择。 大量的代码实践和真题演练是攻克本章的关键。

相关文章:

  • 【金融基础学习】债券回购方式
  • 【金融基础学习】债券市场与债券价值分析
  • Maven(黑马)
  • 数论——质数和合数及求质数
  • Flask中关于app.url_map属性的用法
  • 力扣HOT100之动态规划:416. 分割等和子集
  • 2025年目前最新版本Android Studio自定义xml预览的屏幕分辨率
  • flutter 构建报错Unsupported class file major version 65
  • Scratch节日 | 六一儿童节射击游戏
  • 深度学习---负样本训练
  • 深度学习篇---人脸识别中的face-recognition库和深度学习
  • 科研学习|科研软件——激活后的Origin导出图时突然出现了demo水印
  • Python数学可视化——坐标系与变换
  • ssm 学习笔记day03
  • 如何利用自动生成文档工具打造出色的技术文档
  • Vue 核心技术与实战智慧商城项目Day08-10
  • 打打基础 | 从翻转链表到寄存器、汇编与内存
  • 2025.6.1总结
  • (面试)获取View宽高的几种方式
  • 从模式到架构:Java 工厂模式的设计哲学与工程化实践
  • 徐州建站网站模板/外贸推广平台排名
  • 哪里可以做产品购物网站/营销和销售的区别
  • 亳州有做网站的吗/seo应该如何做
  • wordpress英文博客主题/seo技术员
  • adsence wordpress/seo网站推广的主要目的不包括
  • pc手机模板网站建设/守游网络推广平台