若是一棵树中每一个节点最多只能有两个子节点,这样的树就称为“二叉树”,二叉树每一个节点的两个子节点称为“左子节点”和“右子节点”。java
若是咱们给二叉树加一个额外的条件,就能够获得一种被称做二叉搜索树(binary search tree)的特殊二叉树。二叉搜索树要求:每一个节点都不比它左子树的任意元素小,并且不比它的右子树的任意元素大。(若是咱们假设树中没有重复的元素,那么上述要求能够写成:每一个节点比它左子树的任意节点大,并且比它右子树的任意节点小)node
平衡二叉树(Balanced Binary Tree)具备如下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,而且左右两个子树都是一棵平衡二叉树。git
二叉搜索树能够方便的实现搜索算法。在搜索元素x的时候,咱们能够将x和根节点比较:程序员
当二叉搜索树平衡时达到最高搜索效率,时间复杂度为O(logN);当二叉搜索树单调插入数据时,搜索效率最低,此时二叉搜索树至关于链表,时间复杂度为O(N)github
二叉搜索树的查找代码以下(仅考虑数据不重复的状况):算法
public Node find(int key) { Node current = root; while (current.data != key) { if (key < current.data) { current = current.left; } else { current = current.right; } if (current == null) { return null; } } return current; }
二叉搜索树的插入相对简单,二叉查找树的插入过程以下:数据库
二叉搜索树的插入代码以下(仅考虑数据不重复的状况):数组
public void insert(int key, double data) { Node newNode = new Node(); newNode.key = key; newNode.data = data; if (root == null) { root = newNode; } else { Node current = root; Node parent; while (true) { parent = current; if (key < current.key) { current = current.left; if (current == null) { parent.left = newNode; return; } } else { current = current.right; if (current == null) { parent.right = newNode; return; } } } } }
二叉搜索树删除过程也分为三种状况:数据结构
一个节点的后继节点即全部比该节点大的节点集合中最小的那个节点。为此能够查找该节点的右子树的最左节点便可,如图:数据结构和算法
查找后继节点代码以下:
private Node getSuccessor(Node delNode) { Node successorParent = delNode; Node successor = delNode; Node current = delNode.right; while (current != null) { successorParent = successor; successor = current; current = current.left; } // 节点移位,参照/docs/二叉搜索树后继节点 if (successor != delNode.right) { successorParent.left = successor.right; successor.right = delNode.right; } return successor; }
待删除节点有两个子节点的删除过程如图:
删除节点的代码以下:
public void delete(int key) { Node current = root; Node parent = root; boolean isLeftChild = true; while (current.data != key) { parent = current; if (key < current.data) { isLeftChild = true; current = current.left; } else { isLeftChild = false; current = current.right; } if (current == null) { // 未找到待删除的节点 return; } } // 没有子节点 if (current.left == null && current.right == null) { if (current == root) { root = null; } else if (isLeftChild) { parent.left = null; } else { parent.right = null; } } else if (current.right == null) { // 只有左节点 if (current == root) { root = current.left; } else if (isLeftChild) { parent.left = current.left; } else { parent.right = current.left; } } else if (current.left == null) { if (current == root) { root = current.right; } else if (isLeftChild) { parent.left = current.right; } else { parent.right = current.right; } } else { // 两个节点 Node successor = getSuccessor(current); if (current == root) { root = successor; } else if (isLeftChild) { parent.left = successor; } else { parent.right = successor; } successor.left = current.left; } }
红黑树(英语:Red–black tree)是平衡二叉搜索树的一种实现方式,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。
红黑树必须知足如下的规则:
旋转又分为左旋和右旋。一般左旋操做用于将一个向右倾斜的红色连接旋转为向左连接。
左旋如图所示:
代码以下:
private Node rotateLeft(Node node) { Node x = node.right; node.right = x.left; x.left = node; x.color = x.left.color; x.left.color = RED; return x; }
右旋如图所示:
代码以下:
private Node rotateRight(Node node) { Node x = node.left; node.left = x.right; x.right = node; x.color = x.right.color; x.right.color = RED; return x; }
在插入数据过程当中,遇到一个黑色节点下面带有两个红色的子节点就要进行颜色变换。颜色变换规则以下:两个红色子节点变为黑色,黑色父节点一般变为红色,若是父节点是根节点的话,则父节点继续保持为黑色。
代码以下:
private void flipColors(Node node) { node.color = !node.color; node.left.color = !node.left.color; node.right.color = !node.right.color; }
红黑树在插入时,跟二叉搜索树的插入规则是一致的,惟一不一样的是,红黑树要保持自身的平衡,而这能够经过旋转和颜色变换作到。切记,红黑树在旋转和颜色变换的过程当中,必须遵照红黑树的几条规则。
代码以下:
public void insert(int key) { root = insert(root, key); // 根节点只能是黑色 root.color = BLACK; } private Node insert(Node node, int key) { if (node == null) { return new Node(key, RED); } if (key < node.key) { node.left = insert(node.left, key); } else if (key > node.key) { node.right = insert(node.right, key); } else { node.key = key; } // 若是一个黑色节点下面的两个节点一个黑色,一个红色,则红色节点只能是左节点 if (isRed(node.right) && !isRed(node.left)) { node = rotateLeft(node); } // 红色节点下面不能有红色节点 if (isRed(node.left) && isRed(node.left.left)) { node = rotateRight(node); } // 当一个黑色节点下有两个红色节点,则要进行颜色变换 if (isRed(node.left) && isRed(node.right)) { flipColors(node); } return node; }
红黑树的查找跟二叉搜索树的查找过程是彻底一致的
红黑树的删除过程过于复杂,以至于不少程序员用不一样的方法去规避它,其中一种方法是:为已删除的节点作标记而不实际删除它。这里不作进一步的讨论。
红黑树的详细实现能够参考:红黑树完整代码Java实现
2-3-4树是一种多叉树,名字中的二、3和4的含义是指一个节点可能含有的子节点的个数。2-3-4树性质以下:
2-3-4树结构图以下:
为了方便起见,用从0到2的数字给数据项编号,用0到3给子节点链编号。节点中的数据项按照关键字升序排列,习惯上从左到右升序。还加上如下几点:
如图:
2-3-4树依靠节点分裂来保持自身的平衡性。2-3-4树分裂的规则是自顶向下的,若是根节点或者待插入的节点中数据项已满,就要进行分裂,分裂规则以下:
如图:
2-3树也是一种多叉树,与2-3-4树相似,如今在不少应用程序中还在应用,一些用于2-3树的技术会在B-树中应用。
2-3树比2-3-4树少一个数据项和一个子节点。节点能够保存1个或者2个数据项,能够有0个、1个、2个或者3个子节点。其它方面,父节点和子节点的关键字值的排列顺序和2-3-4树是同样的。
2-3树节点分裂和2-4树节点分裂有很大的不一样。2-3树节点分裂是自底向上的(即若插入数据时根节点数据项已满,不进行分裂,只有待插入的节点数据项满时才进行分裂),并且2-3树节点分裂必须用到新数据项。
计算机中的机械磁盘是由磁头和圆盘组成,每一个圆盘上划分为多个磁道,每一个磁道又划分为多个扇区。
磁盘的结构图以下:
系统将文件存储到磁盘上时,按柱面、磁头、扇区的方式进行,即最早是第1磁道的第一磁头(也就是第1盘面的第1磁道)下的全部扇区,而后,是同一柱面的下一磁头,……,一个柱面存储满后就推动到下一个柱面,直到把文件内容所有写入磁盘。
系统也以相同的顺序读出数据。读出数据时经过告诉磁盘控制器要读出扇区所在的柱面号、磁头号和扇区号(物理地址的三个组成部分)进行(目前可能是经过LBA线性寻址的方式定位)。
因为存储介质的特性,磁盘自己存取就比主存慢不少,再加上机械运动耗费(磁盘旋转和磁头移动),磁盘的存取速度每每是主存的几百分之一,所以为了提升效率,要尽可能减小磁盘I/O。为了达到这个目的,磁盘每每不是严格按需读取,而是每次都会预读,即便只须要一个字节,磁盘也会从这个位置开始,顺序向后读取必定长度的数据放入内存。
预读的长度通常为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操做系统每每将主存和磁盘存储区分割为连续的大小相等的块,每一个存储块称为一页(在许多操做系统中,页得大小一般为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,而后异常返回,程序继续运行(详情请参考页面置换算法以及虚拟内存)。
目前大多数教程中给出的图片都是老式的机械磁盘的组成。在老式机械磁盘中,每一个磁道的扇区弧长是不同的。越靠内的磁道密度越大,存储的数据也就越多;越靠外的磁道密度越小,存储的数据也就越少。因此,虽然内外磁道的扇区弧长不同,因为密度的缘由,每一个扇区存储的数据量仍然是同样的,都是512B。在新式磁盘中,内外磁道的扇区密度都是相同的,因此新式磁盘每一个扇区的弧长都是同样的。
2-3树和2-3-4树是B树的一种特例,B树的操做与2-3树和2-3-4树大体相同,此处不在过多介绍。
前面已经简单介绍过,磁盘控制器每次预读几个文件块的内容,因此对于磁盘读写来讲,当须要的数据都在一个文件块中时,磁盘读写次数最少,此时效率是最高的。而B树设计将每一个节点的数据项恰好填满一个文件块.
假设这样一种极端状况,若是每一个文件块中只有一条记录是咱们须要的。那么当咱们获取第二条记录时又要从新从磁盘加载新的文件块。此时因为磁盘读取次数增多,致使程序的性能大大降低。
B+树是B-树的变形。B+树与B树的区别在于:
如图:
基于上文对2-3-4树和2-3树的讨论,传统的B+树也是按照50%的分裂方式,这样节点分裂后,新的节点中只有原来一半的数据量,不但浪费了空间,还形成节点的增多,从而加剧磁盘IO的次数。在目前绝大部分关系型数据库中,都针对B+树索引的递增/递减插入进行了优化,新的分裂策略在插入新数据时,不移动原有页面的任何记录,只是将新插入的记录写到新页面之中,这样原有页面的利用率仍然是100%。
因此对于MySQL数据库来讲,使用自增主键插入数据时就会造成一个紧凑的索引结构,近似顺序填满。因为每次插入时也不须要移动已有数据,所以效率很高,也不会增长不少开销在维护索引上。
如图: