红黑树-想说爱你不容易
2015-08-24 09:17
399 查看
前言:
记得在大一懵懵懂懂的时候就接触了红黑树的算法。但由于当时内功尚浅,无法将其内化,只是觉得它很神奇,是个好算法,设计它的人很牛!现今重拾起这个算法,不得不再次被它的精妙所折服!编写本文,是希望以鄙人的理解将红黑树算法的精髓向博客园的园友陈述一番,也希望对其有独特见解的朋友能不吝赐教。准备好了的话,我们就开始吧~
--------------------------------------------
Part I:BST
作为开始,我们得先谈谈二叉树(Binary Search Tree)。
1.假设存在一个如下简单的键值字符表:
Key Value
A 2
C 1
B 6
B 11
H 1
J 3
要求你按照读入顺序建立这样一棵二叉查找树,建好之后要求能够进行对于的查询操作。
源于二分查找的思想,二叉查找树有这样一个特点:
对于树上的任意一个结点,如果它有左右子结点的话,其结点大小必定大于其左子结点且小于其右子结点。
2.查找get(key)
由于单独建立一个二叉查找树起初不好分析,我们就假设现在有一棵已经构造好二叉查找树。我们仅需要思考如何在其上面进行查找操作。
根据二分查找的思想,我们可以按照下面步骤进行查找:
Step1:将需要查找的key与二叉查找树的当前根节点的key作比较,得到比较结果后进行下面的step2;
Step2:若查找的key比根节点的key小,则递归从根节点的左子树进行同样的查找key操作;若比根节点的key大,则递归地从根节点的右子树进行同样的查找key操作;
若,查找的key刚好等于当前根节点的key,则返回当前key对应的value,结束!
3.插入put(key,value)
假设现在已经有了一个二叉查找树,我们要插入一对键值(key-value)。源于查找过程的经验,我们知道插入操作其实近似于查找操作。因为,我们插入的时候同样是拿key跟当前根节点的key比较,之后再确定是往左走还是右走,或者是更新当前值(key=root.key时)。
Code:
输出结果:
分析:
插入或查找时,有可能最坏情况树不断恶意生长(垂直生长),此时的时间复杂度为:O(N),平均的时间复杂度为:O(lgN)
----------------------------------------
Part II:RedBlackBST
1. 2-3树
在二叉树的基础之上,我们引入了平衡2-3树。简单地说,二叉树每个结点至多只能有2个子结点(称为“2结点”),而现在我们可以通过将2个结点“绑”在一起形成一个有3个子结点的“3结点”。见下图:
由于查找操作较简单,我们重点讨论它的插入操作。同样基于上面所给的数据,见图:
------------------------------------------------
2.红黑二叉查找树(简称“红黑树”)
那么问题来了,我们该如何实现这样一棵2-3树呢?正常的思维当然是希望在原先的Node结构中进行重构,再构造一个嵌套的BIGNode。但巧妙的地方就在这里,我们可以以之前的二叉查找树为基础,把结点之间的链接分为“红链接”和“黑链接”。其中,红连接通过连接两个2结点组成3结点,黑连接是之前二叉查找树的普通连接。为了方便,我们不妨把3结点统一表示为一条左斜的红色链接。如图:
上面通过定义红黑树的规则实现我们等价的2-3树结构,于是红黑树也就有了下面等价的定义。
含有红黑链接并且满足下列条件的二叉查找树:
1)红链接均为左链接
2)没有任何结点同时和2条红链接相连
3)任意空链接到根节点路径上的黑链接数相同
---------------------------------------------
既然从上面的阐述中,我们得出 了“红黑树≈2-3树",我们我们紧接着用上面的数据构建我们的红黑树,见图:
其中,存在着3个关键操作:
左旋:当结点出现左子结点为黑,右子结点为红时,进行左旋转;
右旋:当结点出现左子结点以及左子结点的左结点均为红时,进行右旋转;
变色:当结点出现左右子结点均为红时,进行变色操作(2个子链接均变黑色,并将红链接向上传递!)
具体,见下图:
Code:
输出结果:
分析:
有了上面3个关键操作之后,我们保证了树的平衡性,即树不会再恶意生长。插入N个结点后,树的高度为:O(lgN)~O(2*lgN) (思考一下?)。所以,我们得到插入和查找的整体时间复杂度均降为:O(lgN)。
--------------------------
结语:
不得不承认,红黑树算法堪称算法研究领域的非凡之作。在现今的汪洋信息时代,存在着上亿的数据。但是,当我们用红黑树算法对其进行动态的增加和查找时,仅仅需要几十次操作即可完事儿,怎能不让人拍案叫绝!!
记得在大一懵懵懂懂的时候就接触了红黑树的算法。但由于当时内功尚浅,无法将其内化,只是觉得它很神奇,是个好算法,设计它的人很牛!现今重拾起这个算法,不得不再次被它的精妙所折服!编写本文,是希望以鄙人的理解将红黑树算法的精髓向博客园的园友陈述一番,也希望对其有独特见解的朋友能不吝赐教。准备好了的话,我们就开始吧~
--------------------------------------------
Part I:BST
作为开始,我们得先谈谈二叉树(Binary Search Tree)。
1.假设存在一个如下简单的键值字符表:
Key Value
A 2
C 1
B 6
B 11
H 1
J 3
要求你按照读入顺序建立这样一棵二叉查找树,建好之后要求能够进行对于的查询操作。
源于二分查找的思想,二叉查找树有这样一个特点:
对于树上的任意一个结点,如果它有左右子结点的话,其结点大小必定大于其左子结点且小于其右子结点。
2.查找get(key)
由于单独建立一个二叉查找树起初不好分析,我们就假设现在有一棵已经构造好二叉查找树。我们仅需要思考如何在其上面进行查找操作。
根据二分查找的思想,我们可以按照下面步骤进行查找:
Step1:将需要查找的key与二叉查找树的当前根节点的key作比较,得到比较结果后进行下面的step2;
Step2:若查找的key比根节点的key小,则递归从根节点的左子树进行同样的查找key操作;若比根节点的key大,则递归地从根节点的右子树进行同样的查找key操作;
若,查找的key刚好等于当前根节点的key,则返回当前key对应的value,结束!
3.插入put(key,value)
假设现在已经有了一个二叉查找树,我们要插入一对键值(key-value)。源于查找过程的经验,我们知道插入操作其实近似于查找操作。因为,我们插入的时候同样是拿key跟当前根节点的key比较,之后再确定是往左走还是右走,或者是更新当前值(key=root.key时)。
Code:
package com.gdufe.binarysearchtree; import java.io.File; import java.util.Scanner; public class BST<Key extends Comparable<Key>, Value> { Node root; // 维护根节点 class Node { // 二叉树的结点 Key key; Value value; Node left, right; public Node(Key key, Value value) { // 初始化结点 this.key = key; this.value = value; } } public Value get(Key key) { return get(root, key); } //查找操作 public Value get(Node x, Key key) { if (x == null) return null; int cmp = key.compareTo(x.key); if (cmp < 0) return get(x.left, key); else if (cmp > 0) return get(x.right, key); else return x.value; } public void put(Key key, Value value) { root = put(root, key, value); } //插入操作 public Node put(Node x, Key key, Value value) { if (x == null) return new Node(key, value); int cmp = key.compareTo(x.key); if (cmp < 0) x.left = put(x.left, key, value); else if (cmp > 0) x.right = put(x.right, key, value); else x.value = value; return x; } public static void main(String[] args) throws Exception { Scanner input = new Scanner(new File("data_BST.txt")); BST<String, Integer> bst = new BST<String, Integer>(); while (input.hasNext()) { String key = input.next(); int value = input.nextInt(); bst.put(key, value); } System.out.println(bst.get("H")); System.out.println(bst.get("B")); } }
输出结果:
1 11
分析:
插入或查找时,有可能最坏情况树不断恶意生长(垂直生长),此时的时间复杂度为:O(N),平均的时间复杂度为:O(lgN)
----------------------------------------
Part II:RedBlackBST
1. 2-3树
在二叉树的基础之上,我们引入了平衡2-3树。简单地说,二叉树每个结点至多只能有2个子结点(称为“2结点”),而现在我们可以通过将2个结点“绑”在一起形成一个有3个子结点的“3结点”。见下图:
由于查找操作较简单,我们重点讨论它的插入操作。同样基于上面所给的数据,见图:
------------------------------------------------
2.红黑二叉查找树(简称“红黑树”)
那么问题来了,我们该如何实现这样一棵2-3树呢?正常的思维当然是希望在原先的Node结构中进行重构,再构造一个嵌套的BIGNode。但巧妙的地方就在这里,我们可以以之前的二叉查找树为基础,把结点之间的链接分为“红链接”和“黑链接”。其中,红连接通过连接两个2结点组成3结点,黑连接是之前二叉查找树的普通连接。为了方便,我们不妨把3结点统一表示为一条左斜的红色链接。如图:
上面通过定义红黑树的规则实现我们等价的2-3树结构,于是红黑树也就有了下面等价的定义。
含有红黑链接并且满足下列条件的二叉查找树:
1)红链接均为左链接
2)没有任何结点同时和2条红链接相连
3)任意空链接到根节点路径上的黑链接数相同
---------------------------------------------
既然从上面的阐述中,我们得出 了“红黑树≈2-3树",我们我们紧接着用上面的数据构建我们的红黑树,见图:
其中,存在着3个关键操作:
左旋:当结点出现左子结点为黑,右子结点为红时,进行左旋转;
右旋:当结点出现左子结点以及左子结点的左结点均为红时,进行右旋转;
变色:当结点出现左右子结点均为红时,进行变色操作(2个子链接均变黑色,并将红链接向上传递!)
具体,见下图:
Code:
package com.gdufe.binarysearchtree; import java.io.File; import java.util.Scanner; public class RedBlackTree<Key extends Comparable<Key>, Value> { Node root; // 维护根节点 final static boolean RED = true; final static boolean BLACK = false; class Node { // 二叉树的结点 Key key; Value value; boolean color; Node left, right; public Node(Key key, Value value, boolean color) { // 初始化结点 this.key = key; this.value = value; this.color = color; } } public Value get(Key key) { return get(root, key); } // 右旋 public Node rotateRight(Node h) { Node x = h.left; h.left = x.right; x.right = h; x.color = h.color; h.color = RED; return x; } // 左旋 public Node rotateLeft(Node h) { Node x = h.right; h.right = x.left; x.left = h; x.color = h.color; h.color = RED; return x; } // 变色处理 public void flipColors(Node h) { h.left.color = BLACK; h.right.color = BLACK; h.color = RED; } public boolean isRed(Node x){ if(x==null) return false; else return x.color; } public Value get(Node x, Key key) { if (x == null) return null; int cmp = key.compareTo(x.key); if (cmp < 0) return get(x.left, key); else if (cmp > 0) return get(x.right, key); else return x.value; } public void put(Key key, Value value) { root = put(root, key, value); root.color = BLACK; } public Node put(Node x, Key key, Value value) { if (x == null) return new Node(key, value, RED); // 添加的结点链接为红色 int cmp = key.compareTo(x.key); if (cmp < 0) x.left = put(x.left, key, value); else if (cmp > 0) x.right = put(x.right, key, value); else { x.value = value; } // 判断是否需要左旋,右旋,变色操作 if (x != null) { if (!isRed(x.left) && isRed(x.right)) x = rotateLeft(x); if (isRed(x.left) && isRed(x.left.left)) x = rotateRight(x); if (isRed(x.left ) && isRed(x.right)) flipColors(x); } return x; } public static void main(String[] args) throws Exception { Scanner input = new Scanner(new File("data_BST.txt")); RedBlackTree<String, Integer> bst = new RedBlackTree<String, Integer>(); while (input.hasNext()) { String key = input.next(); int value = input.nextInt(); bst.put(key, value); } System.out.println(bst.get("H")); System.out.println(bst.get("B")); } }
输出结果:
1 11
分析:
有了上面3个关键操作之后,我们保证了树的平衡性,即树不会再恶意生长。插入N个结点后,树的高度为:O(lgN)~O(2*lgN) (思考一下?)。所以,我们得到插入和查找的整体时间复杂度均降为:O(lgN)。
--------------------------
结语:
不得不承认,红黑树算法堪称算法研究领域的非凡之作。在现今的汪洋信息时代,存在着上亿的数据。但是,当我们用红黑树算法对其进行动态的增加和查找时,仅仅需要几十次操作即可完事儿,怎能不让人拍案叫绝!!
相关文章推荐
- Combination Sum III C#
- linux动态库与静态库的编译与加载
- 基于ArcGIS for javascript API 轨迹回放
- 收取承兑汇票四大注意事项
- 数据结构基础 各种遍历还原二叉树
- hdu2176 尼姆博弈
- 封装了get post方法
- 五种SQL Server分页存储过程的方法及性能比较
- Raising Modulo Numbers
- HDOJ 5417 Victor and Machine
- ZOJ 1654--Place the Robots【二分匹配 && 经典建图】
- 【POJ 3122】 Pie (二分+贪心)
- OpenStack Weekly Rank 2015.08.24
- java线程死锁实例
- CSS 制作3D魔方 爱的魔方给女(男)朋友一个感动
- listView复用问题
- UIScrollView_UIPageControl
- 如何屏蔽防止别的网站嵌入框架代码
- iOS中修改状态栏StatusBar状态和样式的几种方法
- 滚轮滑动加载更多数据