https://mp.weixin.qq.com/s/jXU36r9gV6nGVLPPvfnBVgmysql
在计算机中,树随处可在,可说是图论和计算机科学中的重中之重,理解树的结构、树的思想和树的优异性质对于程序设计大有裨益!算法
咱们都知道,数组的特色是查询快,直接能够经过下标获取元素,时间复杂度为O(1)
;可是当咱们在指定的位置插入元素或者删除元素的时候,数组下标和所对应的元素是须要从新排列的,所须要的时间复杂度为O(n)
!sql
因此对于频繁的插入、删除的场景,不建议采用有序数组!数据库
可能有的朋友会想到,对于须要频繁的插入、删除的场景,可使用链表结构,由于对于链表结构来讲,在进行插入或者删除的时候,只须要改变元素的前驱或者后继节点的引用就能够了,所须要的时间复杂度为O(1)
;可是若是咱们想查询指定的内容时候,须要遍历链表元素并逐步判断,直到查找到目标元素为止,所须要的时间复杂度为O(n)
!数组
因此对于查找频繁的数据,不建议使用链表!缓存
哪有没有一种查询速度快、插入删除也很快的一种数据结构呢?数据结构
树
就是其中一个!树
这种数据结构,在计算机领域中有着不少的实际应用!好比说:ide
说到树
这种数据结构,相信不少人首先想到的就是二叉树
!函数
不错,二叉树做为一个很重要的数据结构,在某些状况下既能够知足咱们要求查询快的特色同时也能够知足插入删除也快的要求。编码
固然,在生活中咱们能够看到树,实际上是分不少种类的,咱们刚刚也说了在某些状况下,假如一个树是任意自由的结构,那么它可能既达不到查询快也达不到插入删除快的要求,所以咱们须要给树做出一些定义。
在现实中,树是一个根朝下、叶朝上的结构,而在计算机科学中,树是由n
个有限节点组成一个具备层次关系的集合,看起来像一颗倒挂的树,根朝上、叶朝下,特色以下:
计算机科学中,树的定义:
以下图,看起来像一颗树,但不是树结构:
虽然树作出了一些基本定义,可是不足以知足咱们的需求,在计算机科学中,树能够被分为如下几种类型:
对于无序树,也就是那种自由树结构,没有任何规律,因此无从查找,这种结构通常不考虑;而有序树,由于各个子节点存在一个的顺序关系,那么在查询的时候,就能够以此为基础进行查找!
固然,有序树又能够进行种类细分,内容以下:
上文说到的二叉树其实就是有序树的一种,在程序开发中,用的也是最多的一种树形结构!
而对于B 树,主要在文件系统和数据库领域中有所应用,像 Linux 操做系统的文件系统就是使用 B+ 树进行文件的存储。
在计算机科学中,二叉树(英文名:Binary Tree)是每一个结点最多有两个子树的树结构,一般子树被称做“左子树”(left subtree)和“右子树”(right subtree)。
按照这个定义,在逻辑上二叉树能够进行五种基本形态的分类:
对于 1~4 种形态的二叉树,形状比较简单,对于第 5 种既有左子树又有右子树的二叉树,在形态上比较复杂,咱们也能够进行特殊类型细分,内容以下:
彻底二叉树是一种特殊的二叉树,特性以下:
在实际的开发中也有所应用,彻底二叉树会使用二叉查找树算法(会在下文介绍),来保证查找的数据是有序的,叶子节点能够按从上到下、从左到右的顺序依次添加到数组中。知道一个节点的位置,就能够轻松地算出它的父节点、孩子节点的位置。
当咱们用数组存储一个彻底二叉树时,以上面图中彻底二叉树为例,标号为 2 的节点,它在数组中的位置也是 2,它的父节点就是 (k/2 = 1)
,它的孩子节点分别是(2k=4)
和(2k+1=5)
,别的节点也是相似。
由于叶子节点的位置比较规律,全部查询排序效率比较高,好比堆排序就使用了它。
除最后一层结点均无任何子节点外,每一层的全部结点都有两个子结点的树,称为满二叉树!
也就是说,若是一个二叉树的层数为K
,且结点总数是(2^k) -1
,满二叉树形状:
满二叉树,特性彻底同彻底二叉树,可是比彻底二叉树更严格,每一个叶节点到达根路径所需的长度都相同!而彻底二叉树的k-1
层能够为叶节点!
平衡二叉树的提出就是为了保证树不至于太倾斜,尽可能保证两边平衡,特性以下:
平衡二叉树的经常使用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。
像 JDK1.8 中 HashMap、TreeMap 等就使用到了红黑树实现。
上面介绍了彻底二叉树、满二叉树、平衡二叉树都属于特殊类型的二叉树,须要咱们从逻辑上去控制才能够知足要求!
上文中咱们说到,二叉树的出现就是为了解决查询效率问题,按照二分进行查找,每次查询只须要选择其中一个子树就进行查找,从而减小查找次数,提高查询效率!
那么咱们如何进行二分查找呢?
这就要求查找的数据必须是有序的,每次查找、插入删除时都要维护一个有序的数据集,因而就有了二叉查找树这个概念,英文全称 Binary Search Tree,简称 BST。
二叉查找树,也被称为二叉排序树,能够说是从算法层面来定义二叉树结构,这种算法思路适用于全部的二叉树结构,特性以下:
二叉查找树,在最好的状况下,按照折半查找,查询效率获得提高,时间复杂度为O(logn);可是,若是构成的二叉排序树蜕变为单支树,树的深度为 n,其查找时间复杂度与顺序查找同样为O(n)
。
若是二叉查找树变成了单支树,查询效率就大大折扣了,因而就有平衡二叉查找树的出现!
平衡二叉查找树,又称 AVL 树,由于算法的发明者为Adel'son-Vel'skii
和 Landis
,被称为 AVL 树来自于大神的姓名缩写组合。
它除了具有二叉查找树的基本特征以外,还具备一个很是重要的特色:
也就是说 AVL 树每一个节点的平衡因子只多是-一、0和1(平衡因子算法:左子树高度减去右子树高度)。
那么如何保证二叉查找树在添加元素的同时保证节点平衡呢?
基本思想就是:当在二叉排序树中插入一个节点时,首先检查是否因插入而破坏了平衡,若破坏,则找出其中的最小不平衡二叉树,在保持二叉查找树特性的状况下,调整最小不平衡子树中节点之间的关系,以达到新的平衡。
所谓最小不平衡子树是指:离插入节点最近且以平衡因子的绝对值大于1的节点做为根的子树。
当新插入的节点致使树结构发生失衡就会进行调整,主要操做有左旋转、右旋转操做!
从图中能够看出,在插入数据 100 以前,左图 BST 树只有 80 节点的平衡因子是 -1(左子树高度减去右子树高度),但整棵树仍是平衡的。
插入 100 以后,80节点的平衡因子就成为了-2,此时平衡被破坏,须要进行调整,绕节点 90 进行左旋转,最终树型结构变成右图。
左旋转场景:当树中节点 X 的右孩子的右孩子上插入新元素,且平衡因子从 -1 变成 -2 后,就须要绕节点 X 进行左旋转!
从图中能够看出,在插入数据 30 以前,左图 BST 树只有 80 节点的平衡因子是 1(左子树高度减去右子树高度),但整棵树仍是平衡的。
插入 30 以后,80节点的平衡因子就成为了 2,此时平衡被破坏,须要进行调整,绕节点 50 进行右旋转,最终树型结构变成右图。
右旋转场景:当树中节点 X 的左孩子的左孩子上插入新元素,且平衡因子从 1 变成 2 后,就须要绕节点 X 进行右旋转。
不少时候,插入元素一次调整知足不了要求,以下图就是左旋与右旋的结合,具体操做时能够分解成这两种操做,只是围绕点不同而已。
与之对应的,也有右旋与左旋的结合,以下图:
因而可知,经过左旋转、右旋转操做,平衡二叉树不会出现普通二叉查找树的最差状况,其查找的时间复杂度为O(logN)!
在查询的时候,操做与普通二叉查找树上的查找操做相同;插入的时候,每一次插入结点操做最多只须要单旋转或双旋转,整体上插入操做的代价仍然在O(logN)级别;若是是动态删除,删除以后必须检查从删除结点开始到根结点路径上的全部结点的平衡因子,最多可能须要O(logN)次旋转。
为了解决尽量少的旋转调整,红黑树出现了!
红黑树,英文名称:red-black tree,简称 RBT!红黑树也是基于平衡二叉树结构的一种实现,可是它的平衡指标没有像 AVL 算法那样要求很严格,并非高度平衡但基本平衡,特性以下:
红黑树,在查找方面,与普通二叉查找树上的查找操做相同;在插入、删除方面,调整方式和平衡二叉查找树相似,同样是左旋转、右旋转,其中红黑树还增长一个调整操做:节点颜色转换。
从上面的特性能够看出,从每一个叶子到根的全部路径上不能有两个连续的红色结点,对于不知足特性的节点颜色,只须要转换颜色一些便可,比较简单!
红黑树,在插入的时候,与 AVL 同样,结点最多只须要2次旋转;在删除的时候,由于没有像 AVL 那样高度平衡的要求,删除一个结点最多只须要3次旋转操,可见红黑树的删除操做代价要比 AVL 要好的多;由于不是高度平衡,在查询方面,红黑树在查询效率方面稍逊于 AVL,可是比二叉查找树强不少!
在 JDK 中就有不少红黑树的具体实现,最典型的就是 JDK1.8 中的 HashMap,当冲突链表长度大于 8 时,链表就会以红黑树结构存储。
哈夫曼树是一种特殊结构的二叉树,主要由哈夫曼编码实现,内容定义以下:
“给定N个权值做为N个叶子结点,构造一棵二叉树,若这棵二叉树的带权路径长度达到最小,则称这样的二叉树为最优二叉树,也称为Huffman树。
哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
哈夫曼编码的由来!
1951年,哈夫曼在麻省理工学院(MIT)攻读博士学位,他和修读信息论课程的同窗正在想选择是完成学期报告仍是期末考试。此时的导师罗伯特·法诺(Robert Fano)出的学期报告题目是:查找最有效的二进制编码。
因为没法证实哪一个已有编码是最有效的,哈夫曼放弃对已有编码的研究,转向新的探索,最终发现了基于有序频率二叉树编码的想法,并很快证实了这个方法是最有效的。
哈夫曼使用自底向上的方法构建二叉树,避免了次优算法香农-范诺编码(Shannon–Fano coding)的最大弊端──自顶向下构建树。
并于1952年,在论文《一种构建极小多余编码的方法》(A Method for the Construction of Minimum-Redundancy Codes)中发表了这个编码方法。
在上文中讲的 BST、AVL、RBT 都是典型的二叉查找树结构,其查找的时间复杂度与树高相关。
下降树的高度能够提升查找效率,另外还有一个比较实际的问题:就是大量数据存储中,实现查询这样一个实际背景下,平衡二叉树因为树深度过大而形成磁盘IO读写过于频繁,进而致使效率低下。
那么如何减小树的高度,一个基本的想法就是:
这样咱们就提出来了一个新的查找树结构 ——多路查找树,根据 AVL 给咱们的启发,一颗平衡多路查找树可使得数据的查找效率保证在O(logN)这样的对数级别上。
在计算机科学中,平衡多路查找树,简称为B树,每一个节点能够拥有2个以上的子节点,可以保持数据有序。
这种数据结构可以让查找数据、顺序访问、插入数据及删除的动做,都在对数时间内完成。
与自平衡二叉查找树不一样,B 树适用于读写相对大的数据块的存储系统,例如磁盘。
B树减小定位记录时所经历的中间过程,从而加快存取速度。这种数据结构常被应用在数据库和文件系统的实现上。
B~树,也就是咱们常说的 B 树,其实 B~树和 B 树是同一种树,咱们知道 B 树是多叉结构,假如给定一个变量 m 来指定多叉,一棵 m 阶的 B~树(m叉树)的特性以下:
例如:下面就是一棵3阶B~树,如图所示:
B树相对于平衡二叉树的不一样是,每一个节点包含的关键字增多了,特别是在 B 树应用到数据库中的时候,数据库充分利用了磁盘块的原理,把节点大小限制和充分使用在磁盘快大小范围;把树的节点关键字增多后树的层级比原来的二叉树少了,减小数据查找的次数和复杂度。
B+树是B树的一个升级版,相对于B树来讲B+树更充分的利用了节点的空间,让查询速度更加稳定,其速度彻底接近于二分法查找。
一棵m阶的B+树和m阶的B-树的差别,内容以下:
例如:下面就是一棵3阶B+树,咱们能够和B~树作一个明显的对比,如图所示:
B+ 树相比B树的优点以下:
但B树也不是彻底没有优点,B树相对于B+树的优势是:若是常常访问的数据离根节点很近,而B树的非叶子节点自己存有关键字其数据的地址,因此这种数据检索的时候会要比B+树快。
B*
树,是 B+树的变体,在 B+树的非根和非叶子结点上增长了指向兄弟的指针,不一样之处以下:
B*
树的初始化个数为(cei(2/3*m)
);B*
树节点满时会检查兄弟节点是否满(由于每一个节点都有指向兄弟的指针),若是兄弟节点未满则向兄弟节点转移关键字,若是兄弟节点已满,则从当前节点和兄弟节点各拿出1/3的数据建立一个新的节点出来;B*
树相比B+树的优点:在B+树的基础上因其初始化的容量变大,使得节点空间使用率更高,而在非根和非叶子结点上增长指向兄弟的指针,能够向兄弟节点转移关键字的特性使得B*
树的分解次数变得更少!
关于树的故事,基本介绍完了,内容比较多,尤为B树模型,比较深奥复杂,有兴趣的朋友能够本身研究一些,若是有理解不当的地方,欢迎网友指出!
一、百度百科 - 树
二、维基百科 - 树
三、掘金 - 西召 - 树结构与Java实现
四、掘金 - 张拭心 - 二叉树、平衡二叉树、二叉查找树
五、iteye - Heart.X.Raid - 动态查找树比较
六、知乎 - 勤劳的小手 - 平衡二叉树、B树、B+树、B*