zoukankan      html  css  js  c++  java
  • 红黑树的插入

    一、红黑树的介绍

    先来看下算法导论对R-B Tree的介绍:
      红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。
    通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其
    他路径长出俩倍,因而是接近平衡的。

      前面说了,红黑树,是一种二叉查找树,既然是二叉查找树,那么它必满足二叉查找树的一般性质。
    下面,在具体介绍红黑树之前,咱们先来了解下 二叉查找树的一般性质:
    1.在一棵二叉查找树上,执行查找、插入、删除等操作,的时间复杂度为O(lgn)。
        因为,一棵由n个结点,随机构造的二叉查找树的高度为lgn,所以顺理成章,一般操作的执行时间为O(lgn)。
        至于n个结点的二叉树高度为lgn的证明,可参考算法导论 第12章 二叉查找树 第12.4节。
    2.但若是一棵具有n个结点的线性链,则此些操作最坏情况运行时间为O(n)。

    红黑树,能保证在最坏情况下,基本的动态几何操作的时间均为O(lgn)。

    我们知道,红黑树上每个结点内含五个域,color,key,left,right,p。如果相应的指针域没有,则设为NIL。

    一般的,红黑树,满足以下性质,即只有满足以下全部性质的树,我们才称之为红黑树:

    1)每个结点要么是红的,要么是黑的。
    2)根结点是黑的。
    3)每个叶结点,即空结点(NIL)是黑的。
    4)如果一个结点是红的,那么它的俩个儿子都是黑的。
    5)对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点。

    注:实际上考虑的情况有6种,而其中的三种与另外三种是对称的。这取决于下面算法中  p[z] = left[p[p[z]]]这一句。

    红黑树插入的第一种情况(RB-INSERT-FIXUP(T, z)代码的具体分析一)

    为了保证阐述清晰,重述下RB-INSERT-FIXUP(T, z)的源码:

    RB-INSERT-FIXUP(T, z)
     1 while color[p[z]] = RED
     2     do if p[z] = left[p[p[z]]]
     3           then y ← right[p[p[z]]]
     4                if color[y] = RED
     5                   then color[p[z]] ← BLACK                    ▹ Case 1
     6                        color[y] ← BLACK                       ▹ Case 1
     7                        color[p[p[z]]] ← RED                   ▹ Case 1
     8                        z ← p[p[z]]                            ▹ Case 1
     9                   else if z = right[p[z]]
    10                           then z ← p[z]                       ▹ Case 2
    11                                LEFT-ROTATE(T, z)              ▹ Case 2
    12                           color[p[z]] ← BLACK                 ▹ Case 3
    13                           color[p[p[z]]] ← RED                ▹ Case 3
    14                           RIGHT-ROTATE(T, p[p[z]])            ▹ Case 3
    15           else (same as then clause
                             with "right" and "left" exchanged)
    16 color[root[T]] ← BLACK

     //case1表示情况1,case2表示情况2,case3表示情况3.

    咱们,先来透彻分析红黑树插入的第一种情况:

    插入情况1z的叔叔y是红色的。

    第一种情况,即上述代码的第5-8行:
     5                   then color[p[z]] ← BLACK                    ▹ Case 1
     6                        color[y] ← BLACK                       ▹ Case 1
     7                        color[p[p[z]]] ← RED                   ▹ Case 1
     8                        z ← p[p[z]]                            ▹ Case 1

    如上图所示,a:z为右孩子,b:z为左孩子。

    只有p[z]和y(上图a中A为p[z],D为z,上图b中,B为p[z],D为y)都是红色的时候,才会执行此情况1.

    咱们分析下上图的a情况,即z为右孩子时

    因为p[p[z]],即c是黑色,所以将p[z]、y都着为黑色(如上图a部分的右边),

    此举解决z、p[z]都是红色的问题,将p[p[z]]着为红色,则保持了性质5.

    红黑树插入的第二种、第三种情况

    插入情况2:z的叔叔y是黑色的,且z是右孩子

    插入情况3:z的叔叔y是黑色的,且z是左孩子

    这俩种情况,是通过z是p[z]的左孩子,还是右孩子区别的。

    参照上图,针对情况2,z是她父亲的右孩子,则为了保持红黑性质,左旋则变为情况3,此时z为左孩子,

    因为z、p[z]都为黑色,所以不违反红黑性质(注,情况3中,z的叔叔y是黑色的,否则此种情况就变成上述情况1 了)。

     我们已经看出来了,情况2,情况3都违反性质4(一个红结点的俩个儿子都是黑色的)。

    所以情况2->左旋后->情况3,此时情况3同样违反性质4,所以情况3->右旋,得到上图的最后那部分。

    注,情况2、3都只违反性质4,其它的性质1、2、3、5都不违背。

    下面附上java代码:

    效果图如下:

     

    package com.hjzgg.rbt;
    
    import java.awt.Rectangle;
    
    public class RBTNode {
        public static final boolean RED = true;
        public static final boolean BLACK = false;
        public RBTNode[] child = new RBTNode[2];
        public RBTNode parent;
        public int key;
        public boolean color;
        public RBTNode(RBTNode parent, int key, boolean color) {
            super();
            this.parent = parent;
            this.key = key;
            this.color = color;
            child[0] = child[1] = null;
        }
        
        private int level;//这个节点在树中的层次
        private Rectangle rect;//节点在图形面板中的位置
        public int getLevel() {
            return level;
        }
        public void setLevel(int level) {
            this.level = level;
        }
        public Rectangle getRect() {
            return rect;
        }
        public void setRect(Rectangle rect) {
            this.rect = rect;
        }
        
    }
    package com.hjzgg.rbt;
    
    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Font;
    import java.awt.Graphics;
    import java.awt.HeadlessException;
    import java.awt.Rectangle;
    import java.util.ArrayList;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.JScrollPane;
    
    public class RBTGraphic {
        private RBTNode T = null;
        private final int distNode = 50;//节点之间的距离
        private final int heightNode = 50;//节点的高度
        private final int widthNode = 50;//节点的宽度
        private final int levelHeight = 100;//层与层之间的高度
        private ArrayList<Rectangle> line = new ArrayList<Rectangle>();
        private int curY = 0;
        private int curX = 10;
        
        public RBTGraphic(RBTNode T) {
            super();
            this.T = T;
            prepareGraphic();
        }
        
        public class TreeFrame extends JFrame{
            private JPanel panel = new JPanel(){
                @Override
                protected void paintComponent(Graphics g) {
                    super.paintComponent(g);
                    for(Rectangle rect : line){
                        g.drawLine(rect.x, rect.y, rect.width, rect.height);
                    }
                }
            };
            private JScrollPane scrollPane = new JScrollPane(panel);
            public TreeFrame() throws HeadlessException {
                super();
                init();
            }
    
            public TreeFrame(String title) throws HeadlessException {
                super(title);
                init();
            }
            
            private void init(){
                setLayout(new BorderLayout());
                panel.setLayout(null);
                drawTree(T);
                add(scrollPane, BorderLayout.CENTER);
                int width = curX + 50;
                int height = curY + 50;
                //这里要设置面板的PreferredSize,如果当前Frame大小不能显示PreferredSize那么才会出现滚动条
                panel.setPreferredSize(new Dimension(width, height));
                if(width > 600) width = 600;
                if(height > 300) height = 500;
                setBounds(400, 100, width, height);
                setVisible(true);
            }
            
            public void drawTree(RBTNode o){
                if(o==null) return;
                JLabel label = new JLabel(o.key+"", JLabel.CENTER);
                label.setBounds(o.getRect());
                label.setFont(new Font("宋体",Font.BOLD, 32));
                label.setForeground(Color.WHITE);
                label.setOpaque(true);
                if(o.color == RBTNode.RED)
                    label.setBackground(Color.RED);
                else
                    label.setBackground(Color.BLACK);
                panel.add(label);
                if(o.child[0]==null && o.child[1]==null) return;
                int x = o.getRect().x+ widthNode/2;
                int y = o.getLevel()*levelHeight+heightNode;
                for(int i=0; i<2; ++i){
                    drawTree(o.child[i]);
                    if(o.child[i]==null) continue;
                    int xx = o.child[i].getRect().x + widthNode/2;
                    int yy = y+levelHeight-heightNode;
                    line.add(new Rectangle(x, y, xx, yy));
                }
            }
        }
        
        private void prepareNodeLevel(RBTNode o, int level){//确定每一个节点的层次
            if(o==null) return; 
            o.setLevel(level);
            prepareNodeLevel(o.child[0], level+1);
            prepareNodeLevel(o.child[1], level+1);
            if(curY < (level+1)*levelHeight) curY = (level+1)*levelHeight;
        }
        
        private void prepareNodeSize(RBTNode o){//确定节点的坐标位置
            if(o==null) return;
            if(o.child[0]==null && o.child[1]==null){//终结点
                int x = curX; curX+=distNode+widthNode;
                int y = o.getLevel()*levelHeight;
                o.setRect(new Rectangle(x, y, widthNode, heightNode));
                return;
            }
            prepareNodeSize(o.child[0]);
            prepareNodeSize(o.child[1]);
            
            int leftChildx = o.child[0] != null ? o.child[0].getRect().x : o.child[1].getRect().x;
            int rightChildx = o.child[1] == null ? o.child[0].getRect().x : o.child[1].getRect().x;
            //根据左右两边孩子的节点,确定父节点的坐标尺寸
            int parentx = (leftChildx+rightChildx)/2;
            int parenty =o.getLevel()*levelHeight;
            o.setRect(new Rectangle(parentx, parenty, widthNode, heightNode));
        }
        
        private void prepareGraphic(){
            prepareNodeLevel(T, 0);
            prepareNodeSize(T);
        }
        
    }
    package com.hjzgg.rbt;
    
    import java.util.Scanner;
    
    public class RBTree {
        public RBTNode T = null;
        private boolean isRoot = true;//是不是第一个插入的节点,也就是根节点
        
        public void rotateT(RBTNode o, int rotate){//rotate表明旋转方向
            RBTNode k = o.child[rotate^1];
            if(o.parent==null)
                T = k;
            else if(o.parent.child[0] == o)
                o.parent.child[0] = k;
            else
                o.parent.child[1] = k;
            k.parent = o.parent;
            o.child[rotate^1] = k.child[rotate];
            k.child[rotate] = o;
            o.parent = k;
        }
        
        private void rbtInsertFixup(RBTNode o){//红黑树平衡的调整,并更改节点的颜色
            if(o.parent.color == RBTNode.RED){
                int childIndex;//左子树或者是右子树索引
                if(o.parent == o.parent.parent.child[0])
                    childIndex = 0;
                else 
                    childIndex = 1;
                
                //找到o节点对应的叔节点
                RBTNode ou = o.parent.parent.child[childIndex^1];
                if(ou!=null && ou.color == RBTNode.RED){//如果叔节点的颜色是红色
                    o.parent.parent.color = RBTNode.RED;
                    ou.color = RBTNode.BLACK;
                    o.parent.color = RBTNode.BLACK;
                } else {//叔节点是空或者是黑色
                    if(o == o.parent.child[childIndex^1]){
                        o = o.parent;
                        rotateT(o, childIndex);
                    }
                    o.parent.color = RBTNode.BLACK;
                    o.parent.parent.color = RBTNode.RED;
                    rotateT(o.parent.parent, childIndex^1);
                }
                T.color = RBTNode.BLACK;
            }
        }
        
        public void outT(RBTNode o){
            if(o==null) return;
            System.out.print(o.key+" ");
            outT(o.child[0]);
            outT(o.child[1]);
        }
        
        public void rbtInsert(RBTNode o, RBTNode op, int key, int childIndex){//红黑树的插入
            if(o == null){
                o = new RBTNode(op, key, RBTNode.RED);
                if(op==null){
                    T = o;
                    T.color = RBTNode.BLACK;
                } else {
                    op.child[childIndex] = o;
                    o.parent = op;
                }
                if(o.color==RBTNode.RED)
                    rbtInsertFixup(o);
            } else if(o.key < key){
                rbtInsert(o.child[0], o, key, 0);
                if(o.color==RBTNode.RED)
                    rbtInsertFixup(o);
            } else {
                rbtInsert(o.child[1], o, key, 1);
                if(o.color==RBTNode.RED)
                    rbtInsertFixup(o);
            }
        }
        
        public void rbtDelete(){//红黑树的删除
            
        }
        
    
        public static void main(String[] args) {
            RBTree rbt = new RBTree();
            Scanner scan = new Scanner(System.in);
            for(int i=0; i<15; ++i){
                int key = scan.nextInt();
                rbt.rbtInsert(rbt.T, null, key, 0);
    //            rbt.outT(rbt.T);
    //            System.out.println();
            }
            new RBTGraphic(rbt.T).new TreeFrame("红黑树");
        }
    }
    
    /*
      2 3 4 6 7 9 11 9 18 14 12 17 19 22 20
      */
  • 相关阅读:
    linux常用命令
    windows 安装elasticsearch-head插件
    spring boot 使用logback日志系统的详细说明
    mysql 修改密码的几种方式
    html跑马灯效果
    windows 安装elk日志系统
    logstash 启动报找不主类或无法加载 java
    MySQL和Postgresql的区别
    Swift-----泛型Generic
    Swift-----扩展extension
  • 原文地址:https://www.cnblogs.com/hujunzheng/p/4802416.html
Copyright © 2011-2022 走看看