2.5 数据结构(Data Structures)
数据结构是用于表示算法所处理的数据在内存中存储的格式,当数据是结构化的,会更便于读取。
不同的数据结构有不同的特性,适用于不同的场景,正确选择数据结构会让工作更为简单。
大多数编程语言自带了预先做好的数据结构,比如 C++ 中的「标准模板库」(Standard Template Library)和 Java 中的「Java 类库」(Java Class Library)。
2.5.1 数组(Array)
数组
数组又名列表(list)或是向量(Vector),其连续存储在内存中,可存储多个值。
数组中不同值使用下标(index)区分,下标从 0 开始,使用方括号 [ ] 表示所访问数组的数值。e.g 数组 j 的第一个元素为 j[0]
,第三个元素为 j[2]
。
本质上是通过计算第一个元素地址的偏移量,来得到数组中其他元素的地址。注意区分数组中第 5 个数字和数组中下标为 5 的数,前者下标为 4,后者实际为第六个数。
数组用途广泛,几乎所有的编程语言都自带了很多函数来处理数据,比如数组排序函数。
字符串
字符串(string)是由字母、数字、标点等组成的数组,以二进制值 \0
(null)结尾。处理字符串的常见函数有 strcat
,其接受两个数组,将第二个放在第一个结尾处。
矩阵
数组仅能操作一维数据,二维及以上数据需要使用矩阵(Matrix)。以二维矩阵为例,取值需要 2 个下标,如 j[2][1]
:
2.5.2 结构体(struct)
结构体是指将几个逻辑上有关系的变量打包在一起的结构,比如将银行卡和余额打包在一个称为一个结构体放入数组中。
但数组创建时则大小固定,无法动态增加,同时其按序排列,在中间插入某值需移动后续诸多值,着实不便。因而可以使用结构体建立新的数据结构消除相关限制。
2.5.3 链表
通常采用名为 node 的结构体构建,节点(node)是变量(variable)和指针(pointer)构成的结构体,指针是指向一个内存地址的特殊变量。
单链表(linked list)
多个节点可以组成链表(linked list),通过链表中的每个节点指向下一个节点来实现灵活性。下图所示为「循环链表」(circular linked),若最后一个指针为 0(null),则为非循环列表。
链表的大小可以动态增减,插入也比数组更加容易,只需改变指针即可(如下图)。在链表中,重新排序、两端缩减、分割以及倒序等操作也很容易进行。
队列(queue)
一种可以使用链表构建的复杂数据结构,采用「先进先出」(First-In First-Out, FIFO)的原则。
处理完一个数据则可以其「出队」(dequeue),将数据加入队列则称为「入队」(enqueue)。入队需要遍历整个链表直至结尾,然后将原先结尾节点的指针指向要加入的节点。
栈(stacks)
栈是一种「后进先出」(Last-In First-Out, LIFO)的数据结构,类似于放羽毛球进筒里。栈中数据的出入称为「进栈」(push)和「出栈」(pop)。
2.5.4 树(tree)
在 node 的基础上再新增一个指针可以构造出树:
抽象来看,可以将最高的节点称为「根节点」(root),节点下的所有节点称为「子节点」(children),任何子节点的直属上层节点称为「父节点」(parent node),没有任何子节点的节点(i.e. 树结束的地方)叫做「叶节点」(leaf)。
上图这种每个节点最多只有两个节点的树称为「二叉树」(binary tree),这是一种特殊的树。树的节点个数是任意的,甚至我们可以在 node 中使用链表来存储所有的子节点。
树的特性是从根到叶是单向进行,而非循环。若数据随意连接,则构成 2.4.4 节中提过的图(graph),图的指向任意,可以使用多个指针的 node 来表示。