2-3树、平衡树、红黑树学习笔记
1、二叉树
度(拥有子树个数)不超过2的树。左边叫左子树,右边叫右子树
二叉树的遍历
前序遍历的递归和迭代实现
//递归遍历
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class Solution {
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
//递归遍历,这种方法下,树的中序后序遍历只要改个顺序就可以
public List<Integer> preorderTraversalRecu(TreeNode root) {
List<Integer> a = new ArrayList<>();
if(root==null){
return a;
}
if(root.left!=null){
preorderTraversal(root.left);
a.addAll(preorderTraversal(root.left));
}
a.add(root.val);
if(root.right!=null){
a.addAll(preorderTraversal(root.right));
}
return a;
}
//先序遍历迭代遍历,这种遍历方式下,中序和后序算法还需要重新考虑一下
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> l = new ArrayList<>();
Stack<TreeNode> stack = new Stack();
if(root==null){
return l;
}
stack.push(root);
while(!stack.empty()){
TreeNode temp = stack.pop();
l.add(temp.val);
if(temp.right!=null){
stack.push(temp.right);
}
if(temp.left!=null){
stack.push(temp.left);
}
}
return l;
}
//中序遍历迭代实现
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if(root==null){
return res;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode tem1 = root;
while(!stack.empty()||tem1!=null){
while(tem1!=null){
stack.push(tem1);
tem1 = tem1.left;
}
if(!stack.empty()){
TreeNode tem0 = stack.pop();
res.add(tem0.val);
tem1 = tem0.right;
}
}
return res;
}
}
}
满二叉树
每一层节点都能到最大值,第一层1个,第二层2个,第k层2^(K-1)
完全二叉树
只能最后一层的叶子节点不要求满(叶子节点只能出现在最下层和次下层),一定要符合叶子节点从左到右满(集中左边)
二叉查找树
一般来说,左子节点比父节点小,右子节点比父节点大。查找某数的时候通过和父节点比大小来确定下一步
2、平衡树
如果往二叉查找树中加入987654321,这样在查询1很慢,效率很低,效率低就是因为这个树不平衡,全部是向左分支。
定义与性质
- 平衡树是一种特殊的二叉树,也可以指空树,如果不是空树,在二叉树的基础上要满足任意一个节点的两颗子树高度相差不超过1。他的查找速度比正常二叉树要快一点,因为一般树矮,比较查找次数少。但是在插入或者删除的时候,为了保证树的平衡,我们要额外地进行左旋或者右旋。
3、2-3查找树
定义
一个2-3树要么为空,要么节点是以下两种:
2结点:含有一个键和两条链,左链接指向2-3键都小于该节点,右链接指向的2-2键都大于该节点
3结点:含有两个键及其对应的值和三条链,左链接指向的键都小于该节点,中链接指向的节点位于该节点两个键中间,右链接指向的节点键大于该节点的键。
查找
小值往左,大值往右,中间值往中走,相等说明找到了。
插入:一切都是为了平衡!!
-
向2-节点插入:和二叉树插入是一样的:比大小找到合适的位置,但是不是直接查到某个节点的空子节点上,而是将合适位置的2-节点变为3-节点进行插入,需要注意的是,要和原来2-节点的键值进行比较,插对位置。
-
向一颗只有3-节点的树中插入新键:和该节点中的两个键进行比较,将在两个键中间的键向上提升,左边的键作为其左子节点,右边的键作为其右子节点,也就是说将一个3-节点变为3个2-节点。中间其实是将三个键值假设为一个4-节点(三个键4条链接,之后将4-节点向上提升,左边键作为左子节点,右边键作为右子节点)
-
向一颗父节点为2-节点的3-节点的树插入新键:原理同上,转为4-节点之后将向上提升的节点和上面的2-节点转变为一个3-节点。
-
向一个父节点为3-节点的3-节点中插入新键:遇到合适的3-节点,将其变为4-节点向上提升,但是因为该节点父节点也为3-节点,那就将提升上来的节点和父节点变为4-节点,一直向上,直到遇到一个2-节点,与提升上来的值一起变为3-节点
-
上面是一直往上遇到2-节点了,下面是一直往上没遇到,根节点也是3-节点的情况:这种情况要对根节点进行分解,将根节点拆分为两个2-节点,树的高度加1。
2-3树的性质
- 任意空链接(叶子节点,左右子节点为空)连接到根节点的路径长度都是相等的;
- 4-节点变换为3-节点时(父节点有2-节点),树的高度不会发生变化,只有临时4-节点是根节点的时候,树高+1;
- 2-3树与普通二叉树最大的区别时自顶向下生长(直接插到合适节点的子节点位置上),而2-树时自底向上生长(插入向上提升节点)。
4、红黑树
红黑树主要是对2-3树进行编码,基本思想是用标准的二叉查找树(完全由2-节点组成的)和一些额外的信息(替换为3-节点)来表示2-3树,我们将树中的链接分为两种类型。
- 红链接:将两个2-节点连接起来构成一个3-节点(红链接指向的子节点才是红色节点)
- 黑链接:2-3树中的普通连接
确切的说,我们将3-节点表示为由一条左斜的红色链接(两个2-节点其中一个是另一个的左子节点)相连的两个2-节点。这种表示法的一个优点,我们无需修改就可以直接使用标准的二叉查找树的get方法。
红黑树定义
- 红黑树红链接均为左链接(只能是左子节点是红色节点)
- 没有任何一个节点同时和两条红线相连(没有两个连在一起的红色节点)
- 该树是完美黑色平衡树,即任意空连接到根节点的路径上黑链接数量相同(根节点是黑节点,每条从根节点到叶子节点上的黑节点数目是相同的)
旋转平衡化
左旋
当某个节点左子节点为黑色,右子节点为红色,会发生左旋
右旋
左子节节点为红色,左子节点的左子节点同样为红色,需要红色
颜色反转
一个节点的左右节点都是红节点的话,就把两个子节点的颜色变为黑色,本节点颜色变为红色
插入
- 向一个普通黑色节点(不连红链接)插入新值,新插入节点设为红节点,然后按照比本节点大小放置在该节点右或者左边。之后视情况进行旋转或者颜色进行改变。
- 向一个3-节点(红链接相连)插入新键,比较大小,比两都大往其中黑色节右子节点处插红节点(然后颜色反转),比两都小往红节点左子节点插入新键(然后右旋||颜色反转),介于两者之间,往红节点左边插入红色节点(左旋右旋然后颜色反转)
public class RedBlackTree {
class TreeNode{
int key;
int value;
boolean color;
TreeNode left;
TreeNode right;
public TreeNode(int key, int value, boolean color, TreeNode left, TreeNode right) {
this.key = key;
this.value = value;
this.color = color;
this.left = left;
this.right = right;
}
}
private TreeNode root;
private int N;
private static final boolean RED = true;
private static final boolean BLACK = false;
public void insert(int key,int value){
root = insert(root,key,value);
root.color = BLACK;
}
/**
* 往跟节点为h的树中插入键为key,值为value的新节点,并返回添加元素后的树
* @param h
* @param key
* @param value
* @return
*/
public TreeNode insert(TreeNode h,int key,int value){
if(h==null){
N++;
return new TreeNode(key,value,RED,null,null);
}
if(key>h.key){
h.right = insert(h.right,key,value);
}else if(key<h.key){
h.left = insert(h.left,key,value);
}else{
h.value = value;
}
//进行左旋
if(!isRed(h.left) && isRed(h.right)){
h = rotateLeft(h);
}
//进项右旋
if(isRed(h.left) && isRed(h.left.left)){
h = rotataeRight(h);
}
//变换颜色
if(isRed(h.left) && isRed(h.right)){
changeColor(h);
}
return h;
}
public int getNode(int key){
return getNode(root,key);
}
public int getNode(TreeNode node,int key){
if(node==null){
return -1;
}
if(node.key>key){
return getNode(node.left,key);
}else if(node.key<key){
return getNode(node.right,key);
}else{
return node.value;
}
}
private boolean isRed(TreeNode x){
if(x==null){//说明空的节点都以黑色算
return false;
}
return x.color==RED;
}
public int getSize(){
return N;
}
/**
* 左旋,当红链接出现到右边
* @param h
* @return
*/
private TreeNode rotateLeft(TreeNode h){
//将h的右子节点记录下
TreeNode x = h.right;
//左旋之后,h的右子节点变为h的左子节点(满足比h大比x小)
h.right = x.left;
//x的左子节点变为h
x.left = h;
//改变x的颜色和h相同
x.color = h.color;
//h的颜色变为红色
h.color = RED;
return x;
}
/**
* 右旋。当当前节点的左子节点和左子节点的左子节点都是红节点时右旋
* @param h
* @return 旋转之后顶替h节点的节点
*/
private TreeNode rotataeRight(TreeNode h){
//将当前节点的左子节点保存为x;
TreeNode x = h.left;
//右旋,h的左子节点由原来变为x的右子节点(比x大比h小)
h.left = x.right;
//x的右子节点变为h
x.right = h;
//x颜色改为和原来h一样
x.color = h.color;
//h颜色改为红色
h.color = RED;
return x;
}
/**
* 颜色反转,当某个节点的左右子节点都是红色节点的话,将父节点变红,子节点变黑
* @param h
*/
private void changeColor(TreeNode h){
h.color = RED;
h.left.color = BLACK;
h.right.color = BLACK;
//为了保证改变颜色把根节点颜色变为红色,所以要
//root.color = BLACK;
}
public static void main(String[] args) {
RedBlackTree tree = new RedBlackTree();
tree.insert(6,666);
tree.insert(7,777);
tree.insert(8,888);
tree.insert(2,222);
System.out.println(tree.getNode(8));
System.out.println(tree.getNode(7));
System.out.println(tree.getNode(6));
System.out.println(tree.getNode(2));
}
}
5、红黑树和平衡树比较与区别
- 首先红黑树的节点多了一个节点属性——颜色
- 其次二者都有注意保持平衡,平衡树是严格一点,子树高度差不得超过1,红黑树笼统一点,它的性质决定了它从根到叶子的最长的可能路径不多于最短的可能路径的两倍长,它是保证了树的整体平衡(这意思是,就算去掉颜色这一属性,红黑树不会变成平衡树的),所以说,节点一样的情况下,平衡树的查找速度快一点,因为它更矮。但是在旋转这一块,红黑树最多转三次就能达到大致平衡,但是平衡树可不一定,他可能需要转很多次。
- 应用中,如果搜索的次数远远大于插入和删除,那么选择AVL树,如果搜索,插入删除次数几乎差不多,应选择红黑树。