Balanced Search Trees
平衡搜索树
前面介绍的二叉搜索树在最坏情况下的性能还是很糟糕,而且我们不能控制操作的顺序,有时根本就不是随机的,我们希望找到有更好性能保证的算法。
2-3 search trees
于是先来了解下 2-3 查找树,它可以保证树的平衡性,维护树高在 lgN 级别。这里的 2,3 指的是孩子的数目,图例:

有两个孩子的节点和二叉搜索树一样,节点里有一个键,且大于左子树的键并小于右子树的键。三个孩子的节点里则有两个键,中间孩子的键的大小介于这两个键之间,左右子树一样。
search
查找和二叉查找树一样,虽然现在有的点有两个键,但是也没有什么关系。
查找图例:

insert
插入操作比较关键,解释了为什么可以保证树的平衡性,下面是各种情况的示意:

插入 2-node 时,直接插入把这个节点变成 3-node 即可,上面也没有列出来。插入 3-node 时比较复杂,要先暂时变成 4-node,然后再把三个键中间的键向父母节点转移,问题就又转移到了父母节点上。不难发现,只有在插入路径上全部都是 3-node 时,插入才会让树的高度加一(一路到根节点变成上图情况一)。
而且,上面的操作找到位置后只是改变链接的局部变换,没有数据转移什么的,时间很快,效率挺高。最坏情况下都是 2-node,树高为 lgN,最好情况下都是 3-node,树高为 \(log_{3}N \approx .631 lgN\),反正树高是对数级别,也就保证了查找和插入对数级别的性能。
但是吧,谈到实现,直接实现太复杂,有好多不同类型的节点,还要进行类型转换,而且需要处理的情况也有很多。实现这些不仅需要大量的代码,而且它们产生的额外开销可能会使算法比标准的二叉查找树更慢。我们希望维护树的平衡,同时也希望保障所需的代码能够越少越好。于是乎,红黑树出现啦!
red-black BSTs
红黑树本质上还是二叉树,关键是在标准二叉查找树的基础上添加了一些信息来表示 3-node:

3-node 里的两个键用左斜的红链接连接,较大的键为根,任意的 2-3 树都有唯一的红黑树与之对应:

在这样的表示下,显然不存在有两条红链接的节点,而且任意从根节点到空链接的路径上的黑链接数都是一样的(perfect black balance),还有注意红链接是左连接,在构建的时候要维护这些性质。
每个节点都只有一条来自父母的链接,我们可以借此表示链接的颜色:
代码:
private static final boolean RED = true;
private static final boolean BLACK = false;
private class Node {
    Key key;
    Value val;
    Node left, right;
    boolean color;    // color of parent link
}
private boolean isRed(Node x) {
    if (x == null) return false;    // null link is black
    return x.color == RED;
}
图例:

红黑树下的查找操作不需要考虑链接的颜色,和二叉查找树一模一样,因为更好的平衡还会快些。
代码:
public Value get(Key key) {
    Node x = root;
    while (x != null) {
        int cmp = key.compareTo(x.key);
        if (cmp < 0) x = x.left;
        else if (cmp > 0) x = x.right;
        else return x.val;
    }
    return null;
}
其它一些只要比较而不会破坏树结构的顺序相关操作也是,直接用原来二叉查找树的代码就好,主要还是看插入操作。
回想 2-3 树的插入操作,实际上都是直接来,性质被破坏了再调整。像 2-node 就直接变成 3-node,而 3-node 会暂时变成 4-node,然后再去调整。红黑树也是这样,新加入一个节点时,都把新链接认为是红色的,不符合性质再调整,像链接不是左斜或是一个节点有两个红链接。
所以,来了解下为了维护性质的调整操作。
Left rotation
左旋,顾名思义就是把红链接从右斜转到左斜。

Right rotation
右旋和左旋相反,这是一个中间状态,有时候需要先右旋再处理才行,下面会见到。

Color flip
颜色转换,甚至不需要改变任何链接,只要改变颜色就好。

虽然红黑树的插入情况看起来好像很多,但是其实可以用下图来概括(最左边的就要先右旋):

处理也只要统一用下面的代码就好:
private Node put(Node h, Key key, Value val) {
    // insert at bottom(and color it red)
    if (h == null) return new Node(Key, val, RED);
    int cmp = key.compareTo(h.key);
    if (cmp < 0) h.left = put(h.left, key, val);
    else if (cmp > 0) h.right = put(h.right, key, val);
    else h.val = val;
    if (isRed(h.right) && !isRed(h.left)) h = rotateLeft(h);     // lean left
    if (isRed(h.left) && isRed(h.left.left)) h = rorateRight(h); // balance 4-node
    if (isRed(h.left) && isRed(h.right)) flipColors(h);
    return h;
}
最后,再来张构造的示例图感受下:

左边一开始的 S,E,A 会形成再上一张图最左边的情况,先对 S 右旋成最右边情况再转换颜色;接下来的 R 很顺利,而 C 会插到 A 的右边形成右斜红链接,需要左旋;H 的插入到 R 的左孩子,这时 S 的左孩子和左孙子的链接都是红色需要右旋,然后 R 两个孩子链接都是红色,转换颜色,之后 E 的右链接变成红色需要左旋...
性能方面,可以保证操作复杂度都是对数级别,一棵大小为 N 的红黑树的高度不会超过 2lgN(证明不管啦)。然后还有其它操作,像删除(更复杂),请参见:RedBlackBST.java。
B-trees(optional)
B 树是一个非常典型的红黑树的实际应用,是平衡树的泛化,每个节点里可以有很多键。因为通常来说,我们需要存储的数据非常大,找到存储数据所在页的时间要比从页里读取数据慢得多,所以我们希望能尽快定位特定的页。B 树每个节点可以有很多很多键,多到可以放一整页的那种:

根节点至少有两个键,内部结点维护键的副本来指导搜索,实际上键有序存储在外部节点上,节点键的数量在 M/2 到 M-1 之间。
查找老样子,来个例子看下:

插入的时候要注意维护树的平衡性,键数目达到 M 的节点需要分裂并向上调整,例图:

因为完美的平衡性,找到特定页的复杂度在 \(log_{M-1}N\) 和 \(log_{M/2}N\) 之间,实际中一般最多只要四次(M =1024; N = 62 billion; \(log_{M/2} \leqslant 4\))。
再贴张 B 树成长的示意图(我都截了):

每一行表示新插入一个键,红色的表示页饱和需要分裂调整。
红黑树被广泛地应用于系统符号表中,Java util 里的 TreeMap 和 TreeSet,C++ STL 里的 map,multimap,multiset 等。
Balanced Search Trees的更多相关文章
- Red Black Tree 红黑树   AVL trees  2-3 trees  2-3-4 trees  B-trees  Red-black trees  Balanced search tree  平衡搜索树
		小结: 1.红黑树:典型的用途是实现关联数组 2.旋转 当我们在对红黑树进行插入和删除等操作时,对树做了修改,那么可能会违背红黑树的性质.为了保持红黑树的性质,我们可以通过对树进行旋转,即修改树中某些 ... 
- 并不对劲的CF1237D&E:Balanced Playlist and Binary Search Trees
		CF1237D Balanced Playlist 题意 有一个长度为\(n\)(\(n\leq 10^5\))的循环播放歌单,每首歌有一个优秀值\(a_i\)(\(a_i\leq 10^9\)). ... 
- Method for balancing binary search trees
		Method for balancing a binary search tree. A computer implemented method for balancing a binary sear ... 
- Optimal binary search trees
		问题 该问题的实际应用 Suppose that we are designing a program to translate text from English to French. For ea ... 
- [LeetCode] Unique Binary Search Trees 独一无二的二叉搜索树
		Given n, how many structurally unique BST's (binary search trees) that store values 1...n? For examp ... 
- [LeetCode] Unique Binary Search Trees II 独一无二的二叉搜索树之二
		Given n, generate all structurally unique BST's (binary search trees) that store values 1...n. For e ... 
- 2 Unique Binary Search Trees II_Leetcode
		Given n, generate all structurally unique BST's (binary search trees) that store values 1...n. For e ... 
- 【leetcode】Unique Binary Search Trees (#96)
		Given n, how many structurally unique BST's (binary search trees) that store values 1...n? For examp ... 
- LeetCode之Unique Binry Search Trees
		4月份很快就过半了,最近都在看WPF,有点落伍了...本来想写一点读书笔记的,还没想好要怎么写.所以为了能够达到每月一篇博客的目标,今天先说一个LeetCode上的面试题:Unique Binary ... 
随机推荐
- UVa 11988 Broken Keyboard(数组模拟链表)
			题目链接: https://cn.vjudge.net/problem/UVA-11988 /* 问题 将一段文本经过一定的规则处理后输出,规则就是[表示home键,表示光标跳到行首,]表示end键, ... 
- Spring基础(2):bean顺序创建
			public class Person{ public Person(){ System.out.println("Person person person ..."); } } ... 
- 设置ul水平居中
			<div class="jdcg-menu-nav"> <ul> <li>基本信息</li> <li>阶段资料</ ... 
- Linux下安装SQL Server 2016(安装篇SQL Server on linux)
			安装过程 如何安装直接参考这个文章:安装sql server 整个安装过程非常简单. 上面的文档里是通过 sudo 命令,用root身份来执行,不过这里为了简单,就用root账号来安装的. (1)下载 ... 
- Git使用(一)——Cygwin
			1.下载2.安装镜像:1)上海交大的FTP:ftp://ftp.sjtu.edu.cn/sites/cygwin.com/pub/cygwin/2)163的镜像:http://mirrors.163. ... 
- SQL查询中的转义字符
			转载自http://blog.csdn.net/Adi_liu/archive/2007/12/06/1920606.aspx 如果想查找“_cs”结尾的的账户 select * from [us ... 
- 鼠标滚轮更改transform的值(vue-scroller在PC端的上下滑动)
			目前上拉刷新,下拉加载,以及区域回弹的组件,绝大多数都是通过transform去实现的.在移动端效果很好,但是PC端使用鼠标拖拽的方式,查看下文首先不符合逻辑,其次容易点进其他页面. 起初,项目的初衷 ... 
- 【读书笔记】iOS-网络-HTTP-请求内容
			一,GET方法. 从服务器获取一段内容,用HTTP术语来说就是实体.GET请求通常不包含请求体,不过也是可以包含的.有些网络缓存设施只会缓存GET响应.GET请求通常不会导致服务器端的数据变化. 二, ... 
- centos7下采用Nginx+uwsgi来部署django
			之前写过采用Apache和mod_wsgi部署django,因为项目需要,并且想比较一下Nginx和Apache的性能,尝试采用Nginx+uwsgi的模式来部署django. 1.安装uwsgi以及 ... 
- npm install、npm init、npm update、npm uninstall和package.json
			npm install 安装本地包 npm install <package_name>:这个命令将在当前目录中创建node_modules目录(如果尚不存在),并将该软件包下载到该目录. ... 
