【数据结构与算法】2 - 1 线性表
§2-1 线性表
部分内容参考、引用自:
《数据结构教程(第 6 版)》李春葆 著
2-1.1 线性表定义
定义:线性表(linear list)是具有相同特性的数据元素的一个有限序列。
序列中元素个数成为线性表长度,表示为 \(n(n \geq 0)\)。当 \(n = 0\) 时,线性表为一张空表,表中不含任何元素。线性表中的每个数据元素由逻辑序号 \(i (1 \leq i \leq n)\) 唯一确定。那么,一张线性表中的所有数据元素即可表示为:
其中,除了第一个元素和最后一个元素外,所有元素有且只有一个直接前驱元素和直接后继元素。
线性表应当支持以下运算操作:
- 初始化线性表:构造一张空表;
- 销毁线性表:释放为线性表所分配的内存空间;
- 判断空表:判断线性表是否为空;
- 计算表长:求得线性表表长,即线性表中元素个数;
- 输出线性表:当表不为空时,顺序输出线性表中的所有元素;
- 获取元素:按(逻辑)序号求得线性表中的元素;
- 查找元素:在线性表中找到首次出现指定关键字的元素的(逻辑)序号;
- 插入元素:在线性表的指定位置插入元素;
- 删除元素:删除线性表中指定位置或指定关键字的元素,并返回元素值;
线性表可以设计为顺序存储结构或链式存储结构。二者的优劣在于:
- 顺序存储:允许随机访问(访问效率高),但空间受限;
- 链式存储:空间不受限,但不允许随机访问(访问效率低)。
下面简单介绍常见的线性表。
2-1.2 栈(Stack)
栈的特点:栈的一端封闭,数据后进先出,先进后出。
数据进出过程:数据进入栈模型的过程称为压栈/进栈;数据离开栈模型的过程称为弹栈/出栈。
栈的示意图:
应用实例:
- Java 的线程栈使用的就是栈的数据结构,方法执行时进栈,执行完毕出栈,遵循后进先出,先进后出的规则;
- 将中缀表达式转化为后缀表达式以及求值的过程也使用了栈;
- 可以使用栈完成对树、图的深度优先搜索。
2-1.3 队列(Queue)
队列特点:先进先出,后进后出。
数据进出过程:数据从队列后端进入队列,这一过程称为入队列;数据从队列前端离开队列,这一过程称为出队列。
队列示意图:
应用实例:
- 在多线程和并发编程中,常常通过队列协调线程的执行顺序;
- 使用队列可实现对树和图的广度优先搜索(求得不带权的最短路径)。
2-1.4 双端队列(Deque)
特点:先进先出,后进后出。但是双端队列两端都可以进行入队和出队操作。
数据进出过程:数据可以从前端进出,也可以从后端进出。
双端队列示意图:
实际应用:
在实际中,还可以有输出受限的双端队列(允许两端入队,但只有一端出队)和输入受限的双端队列(允许两端出队,但只有一端入队)。若入队元素仅能从入队端出队,这样的双端队列退化成两个栈底相连的栈了。
2-1.5 数组(Array)
数组特点:数组是个长度固定,查询快,增删慢的模型。
- 查询速度快。通过地址和索引快速定位,查询任意数据耗时相等;
- 元素在内存中是连续的。数组在内存中开辟了一片连续的存储空间,数组中的数据在内存当中是相邻的;
- 添加和删除效率低。若要在某一位置添加元素,其后面的所有元素需要同时后移;若要将原始数据删除,其后面的所有元素需要同时前移;
数组示意图:
注意:
- 绝大多数的编程语言,数组的索引(物理序号)与逻辑序号范围并不相同。在 Java 和 C/C++ 中,数组的索引由 0 开始。此外,数组已经由编程语言实现,是一种顺序存储结构,支持随机访问,但只能存储同种类型的数据,且一旦创建,数组容量即确定且不可变;
- C/C++ 的支持多维数组,且整个数组存储在同一片连续的存储空间中。Java 的数组实际上都是一维数组,多维数组也只是 ”数组的数组“,因此,Java 的多维数组中的所有元素通常并不会存储在同一片连续的存储空间中。
2-1.6 链表(Linked list)
链表特点:链表是一种查询慢,增删快(相较于数组),首尾操作极快的模型。
- 内存游离。链表中的每一个元素称为结点(node),每个结点作为一个独立的对象,拥有其自己独立的地址,在内存中不连续。每个结点中存储数据和后继结点的地址;
- 链表查询慢。每一次查询都得从头结点开始查询;
- 链表增删相对快。增删元素时,只需要改动相邻结点中下一结点的地址即可;
链表示意图:
单向链表:上图所示的链表,每一个结点中的数据具有两部分:一个是真实存储的数据,一个是指向后继结点的地址。这样的链表称为单向链表,查询时只能从头结点开始,以一个方向查询。
双向链表:只需要在单向链表的基础上,为每一个结点额外添加一个指向前驱结点的地址数据即可。这样的链表查询时可以双向查询,且可以提高查找效率:只需要判断所查询的结点与头结点或末结点的距离,选择最近的结点开始查找即可。
循环链表:循环链表分为单向循环链表和双向循环链表。将单链表改为单向循环链表的过程是将它的尾结点 next
指针域由空改为指向头结点,整个单链表形成一个环。而将双链表改为双向循环链表的过程是将它的尾结点的 next
指针域由空指向头结点,头结点的 prev
指针域由空指向尾结点,整个双链表构成两个环。