线性表

定义

由**零个**或多个数据元素组成的有限序列
(a1,a2,a3,..., an), 其中 a1 为 a2 的前驱元素, a3 为 a2 的后继元素
n 为线性表的长度, n = 0,时为空表

数据类型

定义: 是指一组性质相同的值得集合及定义在此集合上的一些操作的总称
例如: 整形、浮点型、字符型、双浮点型等等
分类:  - 原子类型: 不可再分解的基本类型, 例如:整形、浮点型等
       - 结构类型: 由若干基本类型组合而成, 可再分解, 例如: 整形数组等等
有利于计算机合理分配空间 

抽象数据类型

是指一个数学模型及定义在该模型上的一组操作,即一个把属性和方法捆绑在一起,形成一个类
“抽象” 的意义是对数据类型进行数据抽象
伪代码:
    ADT 抽象数据类型名
    Data
        数据元素之间的逻辑关系的定义
    Operation
        操作
    endADT

线性表的抽象数据类型

ADT 线性表(List)
Data
    线性表的数据对象集合为{a1, a2, ..., an}, 
    每个元素的类型均为 dataType。
    其中,除了第一个元素 a1 外,每个元素都有且仅有一个前驱元素,
    除了最后一个元素 a2 外, 每个元素都有且仅有一个后继元素。
Operation
    InitList(*L): 初始化操作, 建立一个空的线性表 L。
    ListEmpty(L): 判断线性表是否为空表, 是否为空表,若是返回 true,否则返回 false
    ClearList(*L): 清空线性表
    GetElement(L, i, *e): 将线性表 L 中的第 i 个元素值返回给e
    LocateElement(L, e): 在线性表 L 中查找与给定值 e 相等的元素, 如果查找成功, 则返回该元素的在表中的序号, 否则返回0
    ListInsert(*L, i, e): 在线性表 L 中序号为 i 的位置插入新元素 e 
    ListDelete(*L, i, *e): 删除线性表 L 中的第 i 个位置的元素, 并用 e 返回其值
    ListLength(*L): 返回线性表 L 的长度
 endADT  

线性表的顺序存储结构

  1. 结构代码
    #define MAXSIZE 20
    typedef int ElemType
    typedef struct
    {
        ElemType data[MAXSIZE];
        int length; // 线性表当前的长度
    } SqList;
  1. 线性表结构封装的三个属性:
  • 起始位置
  • 最大长度
  • 当前长度
  1. 地址计算方法
    设 ElemType 占用 c 个存储单元(字节),
    那么第 i+n 个位置的地址为:
     LOC(a[i+n]) = LOC(ai) + nc;
    第 i 个位置的地址为:
     LOC(ai) = LOC(a1) + (i-1)*c
  1. 线性表删除和插入操作的时间复杂度
    最好情况为: O(1)
    最坏情况为: O(n)
    平均情况为: O( (n+1)/2 ) 约等于O(n)
  1. 优点:
    • 无须为表示表中的元素之间的逻辑关系而增加额外的存储空间
    • 可以快速地存取表中任意位置的元素
  2. 缺点:
    • 插入和删除操作需要移动到大量的元素
    • 当线性表的长度变化较大是, 难以确定存储空间的容量
    • 容易造成存储空间的“碎片”

线性表的链式存储结构

1. 概念:

  1. 数据域: 存储元素信息的域
  2. 指针域: 存储后继位置的域,所存储的信息为指针或链
  3. 存储映像: 数据域 + 指针域, 又称为结点(Node)
  4. 链表: n 个结点链接成的线性表

2. 单链表

  1. 头指针:
    1. 指向第一个结点的指针
    2. 具有标识作用, 长用链表名字命名
    3. 无论链表是否为空,头指针都不为空
    4. 头指针是链表的必要元素
  2. 结构代码
      typedef struct Node
      {
          ElemType data; // 数据域
          struct Node *Next; // 指针域
      } Node;
      typedef struct Node* LinkList;
    
  3. 单链表读取:
    核心思想: **工作指针后移 **
  4. 时间复杂度:
    1. 第一次查询、插入、删除操作的时间复杂度都是 O(n)
    2. 接下来每次操作都只通过需要移动指针就可以了, 时间复杂度为 O(1)
    3. 因此对于操作越频繁的数据来说, 优势就越明显
  5. 整表创建:
    1. 头插法创建单链表
      1. 创建一个头指针,生成一个悬浮的数据结点 p
      2. 将 p 的地址存到头指针中
      3. 再生成一个 悬浮数据结点 p, 在 p->Next 中存储头指针的地址
      4. 将 p 的地址存放到头指针中
      5. 以此类推
    2. 尾插法创建单链表
      1. 创建一个头指针, 生成一个尾指针 r (最初尾与头指针相同),生成一个悬浮数据结点 p
      2. 将 p 的地址存到 r->Next 中
      3. 再让 r 指向 p;
      4. 再生成一个悬浮数据结点 p
      5. 将新的 p 结点的地址存到 r->Next 中
      6. 再让 r 指向 p;
      7. 以此类推
  6. 单链表与顺序存储结构的优缺点
    1. 分配方式
      1. 顺序存储结构用一段连续的存储单元以此存储线性表的数据元素
      2. 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素及连接地址
    2. 时间性能
      1. 查找
        1. 顺序存储结构 O(1)
        2. 单链表 O(n)
      2. 插入和删除
        1. 顺序表 O(n)
        2. 单链表 第一次 O(n), 后面为 O(1)
    3. 空间性能
      1. 顺序表的预分配一定的存储空间,分配小了会溢出,分配大了会浪费空间,灵活性低
      2. 单链表不用预分配空间,在需要是即时分配,可以存储的元素不受限制,灵活性高
    4. 结论:
      1. 若线性表需要经常性的查找,无经常插入和删除的话采用顺序存储结构,否则,采用链式存储结构
      2. 若不清楚所需存储空间时,建议使用单链表

3. 静态链表

  1. 概念: 用数组描述的链表叫静态链表 (游标实现法)
  2. 结构代码
     #define MAXSIZE 1000
     typedef struct 
     {
         ElemType data; // 数据
         int cur;       // 游标(cursor)
     } Component, StaticLinkList[MAXSIZE];
    
  3. 初始化
         initStaticLinkList(StaticLinkList space)
         {
             int i;
             for ( i = 0; i < MAXSIZE -1; i++) {
                 space[i].cur = i + 1;
             }
             space[MAXSIZE - 1].cur = 0;
             return OK;
         }
    
  4. 静态链表的插入
    1. 将插入的元素放到备用链表的头部 headCur
    2. 将 headCur 的游标指向 i+1 位置的下标(即为原来 i-1 的游标)
    3. 在 i-1 位置的游标存入 i 位置的下标
    4. 如果为空表,则默认插在下标为 1 的位置

4. 循环链表

  1. 将单链表的尾结点指针指向头结点,就形成了首尾闭合的循环链表
  2. 空表:head->next 指向 head 或 为 null
  3. 尾部结点: rear

5. 双向链表

  1. 结构代码
       typedef struct DualNode
       {
          ElemType data; // 数据域
          struct DualNode *Next; // 后驱结点指针
          struct DualNode *prior; // 前驱结点指针
       } DualNode;
       typedef struct DualNode *DuLinkList ;