算法与数据结构

数据结构

数据结构是相互之间存在一种或多种特定关系的数据元素的集合.我们可以将数据结构分为逻辑结构和物理结构.

  • 逻辑结构是指数据对象中数据元素之间存在逻辑上的相互关系.如集合,线性结构,树形结构,图形结构等
  • 物理结构是指数据的在计算机中的存储形式.如顺序存储,链式存储.其中,顺序存储时把数据元素存放在地址连续的存储单元中,链式存储是把数据元素存放在任意存储单元中,再通过逻辑上的指针将各个元素串联起来

算法

算法是解决特定问题求解步骤的描述,具有如下 5 个特性:

  • 输入输出: 算法具有零个或多个输入,至少有一个或多个输出
  • 有穷性: 算法在执行有限步骤后,自动结束而不会出现无线循环,且每个步骤在可接收的时间内完成
  • 确定性: 算法的每个步骤都有确定的含义,不会出现分歧
  • 可行性: 算法的每一步都是可行的

算法的时间复杂度

算法的时间复杂度可使用大 O 阶进行表示,我们按照如下方式推导大 O 阶:

  1. 用常数 1 表示运行时间中所有加法常数
  2. 在修改后的运行次数函数中,只保留最高阶项
  3. 如果最高阶项存在且不是 1,则去除与这个项相乘的常数,得到的结果就是大 O 阶

常用时间复杂度所耗费的时间从小到大是:

1
O(1) < O(log n) < O(n) < O(nlog n) < O(n^2) < O(2^n)  < O(n!) < O(n^n)

线性表

线性表是一个序列,元素之间是有顺序的.若元素存在多个,则第一个元素无前驱,最后一个元素无后继,其它每个元素有且仅有一个前驱和后继.常见的表示形式是数组和链表

数组

数组具有如下性质

  • 具有连续的内存,有上界和下界
  • 数据是连续的,随机访问速度快(查找较快),时间复杂度为 O(1)
  • 增删元素较慢,需要重新分配内存空间并将元素进行平移,时间复杂度为 O(n)

链表

链表具有有如下性质:

  • 节点的链接方向是单向的
  • 相对于数组来说,单链表的的随机访问速度较慢,只能从链表开头进行依次查找)
  • 删除/添加数据的效率很高,只需要修改插入/删除位置的 next 指针即可

栈具有有如下性质:

  • 栈中数据是按照”后进先出(LIFO, Last In First Out)”方式进出栈的
  • 向栈中添加/删除数据时,只能从栈顶进行操作

栈通常包括的三种操作: push(向栈中添加元素), peek(返回栈顶元素), pop(返回并弹出栈顶元素)

队列

队列具有有如下性质:

  • 队列中数据是按照”先进先出(FIFO, First-In-First-Out)”方式进出队列的
  • 队列只允许在”队首”进行删除操作,而在”队尾”进行插入操作

队列通常包括的两种操作: 入队列和出队列

树形结构

二叉树

二叉树是每个结点最多有两个子树的树形结构,通常子树被称作”左子树(left subtree)”和”右子树(right subtree)”.二叉树常被用于实现二叉查找树和二叉堆

满二叉树: 所有层都是满的.即深度为 k,且有 2^k - 1 个节点的二叉树
完全二叉树: 除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点

先序遍历 = 根->左子树->右子树

中序遍历 = 左子树->根->右子树

后序遍历 = 左子树->右子树->根

二叉查找树

二叉查找树具有有如下性质:

  • 左子树上所有结点的值均小于它的根结点的值,右子树上所有结点的值均大于它的根结点的值
  • 任意节点的左,右子树也分别为二叉查找树
  • 没有键值相等的节点

局限: 如果我们的根节点选择是最小或者最大的数,那么二叉查找树就完全退化成了线性结构(链表)

平衡二叉树 AVL

带有平衡条件的二叉查找树,所有节点的左右子树高度差不超过 1.只要不满足这个条件,就要通过旋转来保持平衡,而旋转是非常耗时的.因此 AVL 树适合用于插入删除次数比较少,但查找多的情况.

维护这种高度平衡所付出的代价比从中获得的效率收益还大,实际的应用不多

红黑树

一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,非黑即红

  • 每个节点非红即黑
  • 根节点是黑的
  • 每个叶子节点(叶子节点即树尾端 NULL 指针或 NULL 节点)都是黑的
  • 如果一个节点是红的,那么它的两儿子都是黑的
  • 对于任意节点而言,其到叶子节点的 NULL 指针的每条路径都包含相同数目的黑节点
  • 每条路径都包含相同的黑节点

红黑树应用

  • 广泛用于 C++ 的 STL 中,Map 和 Set 都是用红黑树实现的
  • 著名的 Linux 进程调度 Completely Fair Scheduler,用红黑树管理进程控制块,进程的虚拟内存区域都存储在一颗红黑树上,每个虚拟地址区域都对应红黑树的一个节点,左指针指向相邻的地址虚拟存储区域,右指针指向相邻的高地址虚拟地址空间
  • IO 多路复用 epoll 的实现采用红黑树组织管理 sockfd,以支持快速的增删改查
  • Nginx 中用红黑树管理 timer,因为红黑树是有序的,可以很快的得到距离当前最小的定时器
  • Java 中 TreeMap 的实现

二叉堆

二叉堆是完全二元树或者是近似完全二元树,按照数据的排列方式可以分为两种: 最大堆和最小堆

  • 最大堆: 父结点的键值总是大于或等于任何一个子节点的键值
  • 最小堆: 父结点的键值总是小于或等于任何一个子节点的键值

B 树

B 树是一种多路搜索树,它的每个节点可以拥有多于两个孩子节点,M 路的 B 树最多能拥有 M 个孩子节点.

一个 m 阶的 B 树具有如下几个特征:

  • 根结点的孩子节点数为 [2, m]
  • 每个中间节点包含 k-1 个元素和 k 孩子.其中 m/2 <= k <= m
  • 每一个叶子节点都包含k - 1个元素,其中 m/2 <= k <= m
  • 所有的叶子节点都位于同一层
  • 每个节点中的元素从小到大排列,节点当中 k-1 个元素正好是 k 个孩子包含的元素的值域分划

多路是为了降低树的高度,但是无线多路会退化成有序数组

B 树应用

B 树多用于文件系统索引.

文件系统和数据库的索引都是存在硬盘上的,并且如果数据量大的话,不一定能一次性加载到内存中.这时候,B 树的多路存储威力就出来了,可以每次加载 B 数的一个节点,然后一步一步往下找

B+ 树

什么是B+树

  • 有 k 个子树的中间节点包含有 k 个元素(B 树中是 k-1 个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点
  • 所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接
  • B+ 树中带有重复元素,每一个父节点的元素都同时存在于叶子节点,在叶子节点元素中是最大(或最小)元素

B+ 树应用

  • MySQL 数据库索引

MySQL 索引为什么使用 B+ 树

  • B+ 树中父节点元素都出现在子节点,所有叶子节点包含了全量元素信息,每个叶子节点都带有指向下一个节点的指针,形成了一个有序链表.相比于 B 树,更易于范围查找
  • B 树中的每个节点带有卫星数据(所谓卫星数据,指的是索引元素指向的数据记录,比如数据库中的一行).而 B+ 树中只有叶子节点带有卫星数据(中间节点仅仅是索引),因此相同的磁盘页可以容纳更多的节点元素.这就意味着,相同数据量的情况下,B+ 树比 B 树更加”矮胖”,查询 IO 次数更少.

需要补充的是,数据库的聚簇索引中,叶子节点直接包含卫星数据,非聚簇索引中,叶子节点带有指向卫星数据的指针.

实现

参见 Github

Buy me a cup of coffee.