几种常用数据结构介绍

191 阅读10分钟

1. 链表(Linked List)


1.1 定义

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

1.2 特点

  • 链表由一系列的结点组成,结点在运行时动态生成;

  • 结点由数据域和指针域组成,数据域存储当前结点的数据值,指针域存储下一个结点的地址;

  • 由于不用顺序存储,插入时间复杂度可以达到O(1);已知删除结点的指针的前提下删除结点时间复杂度也是O(1);

  • 查询修改时间复杂度O(n)。

2. 栈(Stack)


2.1 定义

栈是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表,这一端被称为栈顶,相对地,把另一端称为栈底。

2.2 特点

  • 栈可以由数组或链表实现(顺序栈或链式栈);

  • 先进后出,后进先出;

  • 向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;

  • 从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

3. 队列(Queue)


3.1 定义

队列是一种操作受限制的线性表。限定仅在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。

3.2 特点

  • 队列可以由数组或链表实现,在实际应用中可分为顺序队列和循环队列;

  • 先进先出,后进后出。

4. 数组(Array)


4.1 定义

数组是同类数据元素的集合。

4.2 特点

  • 存储空间连续,中间不间断;

  • 存储同一种数据类型的元素;

  • 数组创建时的存储空间即已根据类型及长度计算后固定,后期无法修改;

  • 查询修改时间复杂度O(1),根据索引位置可以直接获取;

  • 插入删除时间复杂度O(n),需要重新创建数组逐个赋值。

5. 树(Tree)


5.1 定义

由有限个结点组成的具有层次性的集合。根朝上叶朝下。

5.2 特点

  • 每个结点有0个或多个子结点;

  • 没有父结点的结点称为根结点;

  • 每个非根结点有且只有一个父结点(不会形成环);

  • 除根结点外,每个子结点可以分为多个不相交的子树。

5.3 相关术语

  • 结点: 树中的每个元素;

  • 结点的度: 一个结点拥有的子结点个数;

  • 叶子结点: 也称为终端结点,度为0的结点;

  • 分支结点: 也称为非终端结点,度大于0;

  • 树的度: 树中结点的度的最大值;

  • 结点的层次: 从根结点开始,根结点为第1层,其子结点为第2层,以此类推;

  • 树的深度: 也称为树的高度,树中结点的层次的最大值;

  • 有序树: 树中任意结点的子结点之间有顺序关系;

  • 无序树: 也称为自由树,树中任意结点的子结点之间无顺序关系;

  • 堂兄弟结点: 结点的层次相同的结点互为堂兄弟;

  • 子孙结点: 以该结点为根结点的子树中任一结点都称为该结点的子孙结点;

  • 结点的祖先: 从根结点到该结点所经分支的所有结点;

  • 森林: 由m(m≥0)棵互不相交的树集合。

5.4 遍历方式

  • 前序遍历: 按照根左右的顺序沿一定路径经过路径上所有的结点;

  • 中序遍历: 先左子树,后根结点,最后右子树(仅二叉树有中序遍历);

  • 后序遍历: 按照左右根的顺序沿一定路径经过路径上所有的结点;

  • 层次遍历: 宽度优先搜索算法(BFS),按照结点的层次,从小到大从左往右依次遍历。

5.5 几种常见的树

5.5.1 二叉树

每个结点最多有两个子树的树结构。可以由数组或链表实现。

5.5.2. 完全二叉树

一棵深度为k且有 2^k-1 个结点的二叉树称为满二叉树。一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。完全二叉树的叶子结点只出现在最后两层。

其倾向于数组存储,能够有效利用存储空间。对于存储索引为k的结点,其存储特征有以下几点:

  • 索引位置 2k+1 的结点是其左子结点;
  • 索引位置 2k+2 的结点是其右子结点;
  • 索引位置 (k-1)/2 的结点是其父结点。

5.5.3. 二叉排序树/二叉查找树

一颗空树,或者具有以下性质的树:

  • 左子树不空,则左子树上的所有结点的值均小于其根结点的值;
  • 右子树不空,则右子树上的所有结点的值均大于其根结点的值;
  • 其左右子树也分别为二叉排序树。

6. 堆(Heap)


堆具有以下性质:

  • 堆是完全二叉树;
  • 堆中某个结点的值总是不大于或是不小于其父节点的值;
  • 根结点最大的堆称为大根堆/最大堆,根结点最小的堆称为小根堆/最小堆。

7. 图(Graph)


7.1 定义

图由顶点集合和顶点之间边的集合组成,通常表示为 G(V,E),其中G表示整个图,V表示顶点集合(Vertices Set),E表示边的集合(Edges Set)。

图可以使用二维数组来表示,该数组也被称为邻接矩阵;也可以使用链表存储,该链表也被称为邻接表,由顺序表和链表组合而成。

7.2 分类

  • 有向图、无向图: 根据各边是否有方向可以分为有向图和无向图;

  • 带权图、不带权图: 根据边长是否带权重可以分为带权图和不带权图,权可以是距离,代价,时间等等;其中带权有向图顶点之间不同方向的权重可以不同;

  • 稀疏图、稠密图: E << V² 为稀疏图,E 接近 V² 则为稠密图。边增加时邻接表存储需要增加节点,而领接矩阵则固定维护的 V² 的二维数组,所以稀疏图一般使用邻接表存储,稠密图则一般使用邻接矩阵,当然,具体也需要根据实际需求和数据结构特性综合选择。

7.3 相关术语

  • 阶(Order): 图G中点集V的大小称作图G的阶;

  • 子图(Sub-Graph): 当图G'=(V',E')其中V‘包含于V,E’包含于E,则G'称作图G=(V,E)的子图。每个图都是本身的子图;

  • 生成子图(Spanning Sub-Graph): 指满足条件V(G') = V(G)的G的子图G';

  • 导出子图(Induced Subgraph): 以图G的顶点集V的非空子集V1为顶点集,以两端点均在V1中的全体边为边集的G的子图,称为V1导出的导出子图;以图G的边集E的非空子集E1为边集,以E1中边关联的顶点的全体为顶点集的G的子图,称为E1导出的导出子图;

  • 度(Degree): 一个顶点的度是指与该顶点相关联的边的条数,顶点v的度记作d(v);

  • 入度(In-degree)和出度(Out-degree): 对于有向图来说,一个顶点的度可细分为入度和出度。一个顶点的入度是指与其关联的各边之中,以其为终点的边数;出度则是相对的概念,指以该顶点为起点的边数;

  • 自环(Loop): 若一条边的两个顶点为同一顶点,则此边称作自环;

  • 路径(Path): 从u到v的一条路径是指一个序列v0,e1,v1,e2,v2,...ek,vk,其中ei的顶点为vi及vi - 1,k称作路径的长度。如果它的起止顶点相同,该路径是“闭”的,反之,则称为“开”的。一条路径称为一简单路径(simple path),如果路径中除起始与终止顶点可以重合外,所有顶点两两不等;

  • 行迹(Trace): 如果路径P(u,v)中的边各不相同,则该路径称为u到v的一条行迹;

  • 轨道(Track): 如果路径P(u,v)中的顶点各不相同,则该路径称为u到v的一条轨道。闭的行迹称作回路(Circuit),闭的轨称作圈(Cycle);(另一种定义是:walk对应上述的path,path对应上述的track。Trail对应trace。)

  • 桥(Bridge): 若去掉一条边,便会使得整个图不连通,该边称为桥。

8. 散列表(Hash)


8.1 定义

根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

8.2 常用散列函数

  • 直接寻址法: 取关键字或关键字的某个线性函数作为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。若其中H(key)中已经有值了,就往下一个找,直到H(key)中没有值了,就放进去;

  • 数字分析法: 分析数字规律,尽可能利用这些数字来构造冲突率较低的散列地址;

  • 平方取中法: 当无法确定关键字中哪几位分布较均匀时,可以先求出关键字的平方值,然后按需要取平方值的中间几位作为哈希地址。这是因为:平方后中间几位和关键字中每一位都相关,故不同关键字会以较高的概率产生不同的哈希地址;

  • 折叠法: 将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。数位叠加可以有移位叠加和间界叠加两种方法。移位叠加是将分割后的每一部分的最低位对齐,然后相加;间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加;

  • 随机数法: 选择一随机函数,取关键字的随机值作为散列地址,即H(key)=random(key)其中random为随机函数,通常用于关键字长度不等的场合;

  • 除留余数法: 取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p≤m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。

8.3 哈希冲突处理

  • 开放寻址法: 发生哈希冲突时,就冲突位置按照一定规律探测到一个空闲位置存入;

  • 再散列法: 准备多种散列函数,发生冲突时使用另一个散列函数计算地址,直至不再冲突;

  • 链地址法: 发生哈希冲突时,就冲突位置使用链表连接元素;

  • 建域法: 另外设立存储空间用于存储冲突元素。