二叉树
前情提要:
1、二叉树
每一个节点最多有2个子节点,有左右之分。深度为n的二叉树,最多有2^n^ -1个节点,第n层最多有2^k-1^ 个节点。
2、满二叉树
一棵深度为k,且有2^k^ -1个节点的树。
3、完全二叉树
完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h 层所有的结点都连续集中在最左边,这就是完全二叉树。
正文
一、二叉查找树(BST, binary search tree)
某一节点的键值,一定大于其左子树的键值,一定小于其右子树的键值。
为什么需要它?
我们知道数组的搜索效率是O(1),但是数组的插入、删除都需要移动元素,效率低下;而链表的插入、删除效率很高,但是其搜索效率是O(n),效率低下。这时候,二叉查找树就应运而生了。它具有插入、删除操作的高效和查找的高效。
实现:二叉查找树的实现简单,常用的操作就是插入、删除、查找节点。
缺点:二叉查找树在插入有序元素的时候,会退化成链表。
中序遍历:一颗二叉查找树的中序遍历结果就是排序的结果。
第一颗树的中序遍历结果是:3 4 7 10 12 13 17第二棵树的中序遍历结果是:17 11 8 7 3 1
二、平衡二叉树(AVL)
理论上,在插入元素的时候,动态调整二叉查找树,使其平衡(左右子树的最大深度不超过1)。平衡的二叉树就完美地实现了二分查找,同时其插入、删除的效率也是LogN。但是其实现时过于麻烦,仅仅作为理论来看待即可。
特点:平衡二叉树在节点插入的过程中,会动态地调整树的结构,使得其每个节点的左、右子树的最大深度之差不会超过1。这样就维持了二叉树的稳定,从而使得插入、删除、查找的效率都处在log~2~N。
实现:在对节点的操作中,使用左旋、右旋来调整树的结构。理解左旋和右旋的意义,事实上,旋转的结果就是使得较为中间的数处在了树的中间位置;换个角度,我们有一组排好序的数组,那么我们要把它拎成一颗平衡的二叉树,当然就是位于中间的数处于二叉树的中间,这样才不会使二叉树左右不协调。
三、2-3树
作为平衡二叉树的一种变形,2-3数不再是二叉树了,而是一颗多叉树。它引入了2-节点和3-节点。3-节点就是该节点有2个key和3个子节点。在插入的时候,是自下而上来构建2-3树,由于2-3树插入节点的特性(新节点插入到2-或3-节点中,然后自下而上分裂),使得2-3树是完美平衡的。
特点: 所有null节点到根节点的距离都是相同的。
实现: 2-3树的思想很好,同时也引出了2-3-4树甚至是n叉树。在MySQL的索引存储引擎中,使用的是B+Tree的结构,它就是多叉树的实现。
四、红黑树
2-3树在构建时,我们还是觉得麻烦,我们只想使用二叉查找树。这时候通过引入红黑节点及相关规定,我们可以构建一颗接近完美平衡的二叉查找树(因为2-3树是完美平衡的,而3-节点的拆分使得红黑树只能是接近完美平衡)。
意义: 最终红黑树的实现,实际上就是一颗2-3树,2-3树可以与红黑树互相转换。
延伸: jdk1.8之后,hashmap的底层,其原先存储值的链表结构改为了红黑树实现。
B-Tree与B+Tree
B-Tree
B-Tree是一种是磁盘等外存储设备设计的一种平衡查找树,它是多路平衡查找树。它的本质是一颗2-3-4-n树。为了减少磁盘指针移动的次数,多叉树尽量越矮胖越好。
特点: B-Tree的非叶子节点也会存储数据,这样会使得内存读取的索引页面更小,效率不高。
B+Tree
B+Tree是B-Tree的升级版,它的非叶子节点只存储键值key,所有数据都存在最底层的叶子节点上。同时所有叶子节点之间都有一个链指针相连,这样有利于数据的范围查找和数据遍历。
InnoDB和MyISAM都是B+Tree的实现。但是2者还是有区别的。
InnoDB:
InnoDB会有一个聚簇索引,一般是主键索引。该索引的B+Tree结构上,叶子节点存储了数据行的所有列,因此,一个数据表只能有一个聚簇索引。
InnoDB的二级索引,其叶子节点除了拥有key之外,还会保存主键列的值,因此二级索引需要根据主键值回表查找真正的数据。这意味着在设计二级索引的时候,我们可以使用覆盖索引来获取主键列的值。
MyISAM:
对于MyISAM来说,其主键索引和二级索引的结构是一样的。但是其叶子节点保存的只是数据的行指针,指向真正的数据。
InnoDB和MyISAM的优缺点:
二者不同的实现在不同的场景下是各有优劣的,聚簇索引在读操作多、写操作少的时候,查找效率很高;而非聚簇索引在读操作少、写操作多的情况下,因为只需要修改key(B+Tree)的结构较少,如果是聚簇索引则需要耗费很大的资源。所以针对不同的读写场景,合理地选用表的存储引擎还是挺重要的。