线性表

线性表是数据结构中最基本、最常用、最简单的一种结构。

线性,是说数据在逻辑结构上具有线性关系。线性关系指的是数据一个挨着一个,总体呈线性分布。就好比“老鹰捉小鸡”游戏中,小鸡们全部手拉着手,它们之间的关系就可以称为线性关系。

线性表只对数据的逻辑结构有要求,根据实际存储的物理结构的不同(分散存储或者集中存储),线性表还可以进行更详细的分类。

对于线性表中的数据来说,位于当前数据之前的数据统称为“前驱元素”,前边紧挨着的数据称为“直接前驱”;同样,后边的数据统称为“后继元素”,后边紧挨着的数据称为“直接后继”。除非在线性表中插入或者删除数据元素,否则数据之间的关系不会改变。

线性表的定义

线性表(List):零个或多个具有相同特性的数据元素的有限序列。

首先它是一个序列。也就是说,元素之间是有顺序的,若元素存在多个,则第一个元素无前驱,最后一个元素无后继,其他每个元素都有且只有一个“直接前驱”和“直接后继”。

然后,线性表强调是有限的,即元素的个数是有限的。事实上,在计算机中处理的对象都是有限的,那种无限的数列,只存在于数学的概念中。

如果用数学语言来进行定义。可如下:

若将线性表记为(A[1], ..., A[i-1], A[i], A[i+1], ..., A[n]),则表中 A[i-1] 领先于 A[i],A[i] 领先于 A[i+1],称 A[i-1] 是 A[i] 的直接前驱元素,A[i+1] 是 A[i] 的直接后继元素。当 i=1, 2, ..., n-1 时,A[i] 有且仅有一个直接后继,当 i=2, 3, ..., n 时,A[i] 有且仅有一个直接前驱。

所以线性表元素的个数 n(n>=0) 定义为线性表的长度,当 n=0 时,称为空表。

在非空表中的每个数据元素都有一个确定的位置,如 A[1] 是第一个数据元素,A[n] 是最后一个数据元素,称 i 为数据元素 A[i] 在线性表中的位序。

在较复杂的线性表中,一个数据元素可以由若干个数据项组成。

虽然不同线性表的数据元素可以是各种各样的,但对于同一线性表的各数据元素必定具有相同的数据类型和长度。

线性表的抽象数据类型

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

对于不同的应用,线性表的基本操作是不同的,上述操作是最基本的,对于实际问题中涉及的关于线性表的更复杂的操作,完全可以用这些基本操作的组合来实现。

线性表的顺序存储结构

顺序存储结构是线性表的两种物理结构之一,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

线性表的每个数据元素的类型都相同,因此可以用一维数组来实现顺序存储结构。顺序存储结构需要三个属性:

  • 存储空间的起始位置:数组 data,它的存储位置就是存储空间的存储位置
  • 线性表的最大存储容量:数组长度 MaxSize
  • 线性表的当前长度:length

线性表的长度是线性表中数据元素的个数,随着线性表插入和删除的进行,这个量是变化的。在任意时刻,线性表的长度应该小于等于数组的长度。

数组用一种连续的内存空间,来存储一组具有相同类型的数据。因此数组支持随机访问,根据下标随机访问的时间复杂度为 O(1),对数组进行插入或删除时,时间复杂度都是 O(n)。

线性表的顺序存储结构的优点:

  • 无须为表示表中元素之间的逻辑关系而增加额外的存储空间
  • 可以快速地存取表中任一位置的元素

线性表顺序存储结构的缺点:

  • 插入和删除操作需要移动大量元素
  • 当线性表长度变化较大时,难以确定存储空间的容量
  • 造成存储空间的“碎片”

线性表的链式存储结构

线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。这就意味着,这些数据元素可以存在内存未被占用的任意位置。

在顺序存储结构中,每个数据元素只需要存数据元素信息就可以了。现在链式存储结构中,除了要存储数据元素信息外,还要存储它的后继元素的存储地址。

因此,为了表示每个数据元素 A[i] 与其直接后继数据元素 A[i+1] 之间的逻辑关系,对数据元素 A[i] 来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数据元素的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称作指针或链。这两部分信息组成数据元素 A[i] 的存储映像,称为结点(Node)。

n 个结点链接成一个链表,即为线性表(a1, a2, ..., an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。单链表正是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起。

我们把链表中第一个结点的存储位置叫做头指针,那么整个链表的存取就必须是从头指针开始进行了。之后的每一个结点,其实就是上一个后继指针指向的位置。最后一个结点不存在直接后继,所以我们规定,线性链表的最后一个结点指针为“空”(通常用 NULL 或 "^" 符号表示)。 有时,我们为了更加方便地对链表进行操作,会在单链表的第一个结点前附设一个结点,称为头结点。头结点的数据域可以不存储任何信息,也可以存储如线性表长度等附加信息,头结点的指针域存储指向第一个结点的指针。

头指针与头结点:

  • 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
  • 头指针具有标识作用,所以常用头指针冠以链表的名字
  • 无论链表是否为空,头指针均不为空。头指针是链表的必要元素
  • 头结点是为了操作的统一和方便而设立的,放在第一元素的节点之前,其数据域一般无意义(也可存放链表的长度)
  • 有了头结点,对在第一元素结点前插入节点和删除节点,其操作与其它节点的操作就统一了
  • 头结点不一定是链表必须要素