美文网首页
使用红黑树进行数据检索

使用红黑树进行数据检索

作者: ruoshy | 来源:发表于2019-09-28 06:57 被阅读0次

一、概述

  当业务中需要对一些数据进行检索,使用常规的遍历查找是非常耗时的,有一种方法是实现二叉查找树,但是二叉查找树在一种极端的情况下会变成链表,失去快速查找的优势变成遍历查找,这个时候我们可以将二叉查找树改造成 —— 红黑树

二、什么是红黑树

  红黑树(Red Black Tree) 是一种自平衡二叉查找树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。

  它是在1972年由Rudolf Bayer发明的,当时被称为平衡二叉B树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的“红黑树”。

  红黑树和AVL树类似,都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。

  它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n 是树中元素的数目。

三、构建红黑树的条件

  • 性质1. 节点是红色或黑色。
  • 性质2. 根节点是黑色。
  • 性质3. 每个叶结点,即空结点(NIL)是黑的
  • 性质4. 每个红色节点的两个子节点都是黑色,不能有连续的红节点。
  • 性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

四、保持自平衡的机制

红黑树通过对节点进行变色、左旋、右旋来满足他的性质,达到自平衡的效果。
为了方便修正,父节点默认为黑,每次插入的子节点为红。

例1

红黑树的插入修正有以下几种情况:

父节点、叔节点为红 (图 例1)
  • 若子节点、父节点、叔节点为红色,将祖父节点变为红色,父节点和叔节点变为黑色,再以祖父节点为当前节点,判断是否仍然符合修正条件。
父节点为红,叔节点为黑
  • 当前节点处于父节点左边,父节点处于祖父节点左边(Left-Left) ,对祖父节点进行右旋,设置祖父节点为红色,父节点为黑色。

  • 当前节点处于父节点右边,父节点初一祖父节点左边(Right-Left),对父节点进行左旋,使之符合(Left-Left)条件,执行其操作

  • 当前节点处于父节点右边,父节点处于祖父节点右边(Right-Right),对祖父节点进行左旋,修改父节点为红色,父节点为黑色。

  • 当前节点处于父节点左边,父节点处于祖父节点右边(Left-Right),对父节点进行右旋,使之符合(Right-Right)条件,执行其操作

左旋

对节点 E 左旋

右旋

对节点 S 右旋

五、代码实现

public class RBTree {

    class TreeNode {
        private TreeNode leftNode;
        private TreeNode parentNode;
        private TreeNode rightNode;
        private Integer color;
        private Integer data;
    }
    // 根节点
    private TreeNode treeNode;

    public RBTree() {
        treeNode = new TreeNode();
    }

    public void insert(Integer data) {
        if (treeNode.data == null) {
            treeNode.data = data;
            treeNode.color = 0;
        } else {
            insert(treeNode, data);
        }
    }

    public void insert(TreeNode node, Integer data) {
        TreeNode temp = new TreeNode();
        temp.color = 1;
        temp.data = data;
        while (node != null) {
            if (node.data > data) {
                if (node.leftNode == null) {
                    temp.parentNode = node;
                    node.leftNode = temp;
                    break;
                } else {
                    node = node.leftNode;
                }
            } else {
                if (node.rightNode == null) {
                    temp.parentNode = node;
                    node.rightNode = temp;
                    break;
                } else {
                    node = node.rightNode;
                }
            }
        }
        // 修正
        revise(temp);
    }

    public void revise(TreeNode node) {
        TreeNode parentNode = node.parentNode;
        if (parentNode != null && parentNode.color == 1) {
            TreeNode grandfatherNode = parentNode.parentNode;
            TreeNode uncleNode = null;
            // 获取叔节点
            if (grandfatherNode != null) {
                uncleNode = grandfatherNode.leftNode == parentNode ? grandfatherNode.rightNode
                        : grandfatherNode.leftNode;
            } else {
                parentNode.color = 0;
                return;
            }
            // 若叔节点为黑(null也算黑)
            if (uncleNode != null && uncleNode.color == 1) {
                // 将父节点、叔节点变黑
                parentNode.color = 0;
                uncleNode.color = 0;
                // 将祖父节点变红,再次修正
                grandfatherNode.color = 1;
                revise(grandfatherNode);
            } else {
                // 当前节点在父节点的左边
                if (parentNode.leftNode == node) {
                    // 父节点在祖父节点的左边(Left-Left)
                    //       g                f
                    //     f        -->    n     g
                    //   n
                    if (grandfatherNode.leftNode == parentNode) {
                        // 对祖父节点进行右旋,并变色
                        //       黑
                        //   红       红
                        rotateRight(grandfatherNode);
                        parentNode.color = 0;
                        node.color = 1;
                        grandfatherNode.color = 1;
                    }
                    // 父节点在祖父节点的右边(Left-Right)
                    //   g              g
                    //     f    -->       n
                    //   n                  f
                    else {
                        // 父节点右旋,使其符合(Left-Left),再次修正
                        rotateRight(parentNode);
                        revise(parentNode);
                    }
                }
                // 当前节点在父节点的右边
                else {
                    // 父节点在祖父节点的左边(Right-Left)
                    //       g             g
                    //     f      -->    n  
                    //       n         f
                    if (grandfatherNode.leftNode == parentNode) {
                        // 父节点左旋,使其符合(Right-Right),再次修正
                        rotateLeft(parentNode);
                        revise(parentNode);
                    }
                    // 父节点在祖父节点的右边(Right-Right)
                    //   g                 f
                    //     f     -->    g     n
                    //       n
                    else {
                        // 对祖父节点进行左旋,并变色
                        //       黑
                        //   红       红
                        rotateLeft(grandfatherNode);
                        parentNode.color = 0;
                        node.color = 1;
                        grandfatherNode.color = 1;
                    }
                }
            }
        }
    }
    
    public void rotateLeft(TreeNode node) {
        TreeNode parentNode = node.parentNode;
        TreeNode rightNode = node.rightNode;
        // 判断当前节点位于父节点的方向,并将右节点位置移到当前节点位置
        if(parentNode != null) {
            // 当前节点位于父节点左边
            if(parentNode.leftNode == node) {
                parentNode.leftNode = rightNode;
            }
            // 当前节点位于父节点右边
            else {
                parentNode.rightNode = rightNode;
            }
        } else {
            // 若父节点为空必定是根节点,设置根节点
            this.treeNode = rightNode;
        }
        rightNode.parentNode = parentNode;
        // 将右节点的左节点交给当前节点的右节点
        node.rightNode = rightNode.leftNode;
        if(rightNode.leftNode != null) {
            rightNode.leftNode.parentNode = node;
        }
        // 将当前节点交给右节点的左节点
        rightNode.leftNode = node;
        node.parentNode = rightNode;
    }
    
    public void rotateRight(TreeNode node) {
        TreeNode parentNode = node.parentNode;
        TreeNode leftNode = node.leftNode;
        // 判断当前节点位于父节点的方向,并将左节点位置移到当前节点位置
        if(parentNode != null) {
            // 当前节点位于父节点左边
            if(parentNode.leftNode == node) {
                parentNode.leftNode = leftNode;
            }
            // 当前节点位于父节点右边
            else {
                parentNode.rightNode = leftNode;
            }
        } else {
            // 若父节点为空必定是根节点,设置根节点
            this.treeNode = leftNode;
        }
        leftNode.parentNode = parentNode;
        // 将左节点的右节点交给当前节点的左节点
        node.leftNode = leftNode.rightNode;
        if(leftNode.rightNode != null) {
            leftNode.rightNode.parentNode = node;
        }
        // 将当前节点交给左节点的右节点
        leftNode.rightNode = node;
        node.parentNode = leftNode;
        
    }
    
    /**
     * 中序遍历
     */
    public void middle() {
        middle(treeNode);
    }
    
    public void middle(TreeNode node) {
        // 定义栈
        Stack<TreeNode> stack = new Stack<>();
        // 直到节点为空且栈为空停止循环
        while (node != null || !stack.isEmpty()) {
            while (node != null) {
                // 将左节点顺序入栈
                stack.push(node);
                node = node.leftNode;
            }
            // 取出栈顶元素
            node = stack.pop();
            // 输出节点元素
            System.out.println(node.data + "  "+node.color);
            // 获取该栈顶元素的右节点并继续将其全部左节点入栈
            node = node.rightNode;
        }
    }

    public void search(Integer data) {
        search(treeNode, data);
    }
    
    public void search(TreeNode node, Integer data) {
        int num = 1;
        while (node != null) {
            num++;
            if (node.data.equals(data)) {
                System.out.println("查询结果:" + node.data);
                break;
            } else if (node.data > data) {
                node = node.leftNode;
            } else {
                node = node.rightNode;
            }
        }
        System.out.println("查询比较次数: "+num);
    }

}
测试类
public class Test {

    public static void main(String[] args) {
        RBTree tree = new RBTree();
        long start = System.currentTimeMillis();
        for (int i = 1; i < 100000; i++) {
            tree.insert(i);
        }
        tree.insert(932145);
        System.out.println("插入十万条数据,耗时:" + (System.currentTimeMillis() - start));
        start = System.currentTimeMillis();
        tree.search(932145);
        System.out.println("查询耗时:" + (System.currentTimeMillis() - start));
    }

}

⚠️ 以下为对树顺序插入 1 到 100000 的整型数据测试结果

不使用红黑树插入、查询测试
二叉查找树
使用红黑树插入、查询测试
红黑树

通过测试我们发现普通的二叉树插入非常的耗时,查询由于比较的次数过多,耗时比红黑树要长。

六、结论

  红黑树通过对树的节点修正,使其不会发生变成链表的情况,充分发挥二叉树的优点,保持各节点子树的平衡。

相关文章

  • 使用红黑树进行数据检索

    一、概述   当业务中需要对一些数据进行检索,使用常规的遍历查找是非常耗时的,有一种方法是实现二叉查找树,但是二叉...

  • 小探红黑树

    本文对红黑树的特点和性质进行说明,不对红黑树的操作等问题进行代码方面的探究。 1、上帝视角看红黑树 红黑树的本质是...

  • Red-Black Tree

    红黑树 前段时间看到STL map使用的数据结构是红黑树,研究了一下。 红黑树的由来 红黑树是二叉查找树的升级版本...

  • 二叉树与红黑树

    红黑树工程中使用: 1.利用红黑树顺序功能(最小节点,最大节点);2.利用红黑树快速查找的功能,key-value...

  • C++boolan part3_week3

    由于对红黑树理解不深,课后对红黑树进行了较深入的探索。 此笔记主要对红黑树进行归纳理解,其中不免参照网上资料 红黑...

  • 数据结构—树—红黑树

    红黑树概述 红黑树的插入 红黑树的删除

  • 算法分析--红黑树

    1.概述 红黑树(Red Black Tree) 是一种自平衡二叉查找树,红黑树和AVL树类似,都是在进行插入和删...

  • JDK源码-Map系列

    Map 类图 TreeMap实现 TreeMap是通过红黑树进行的,红黑树能够保证在最坏的情况,基本的动态集合操作...

  • Java集合详解6:TreeMap和红黑树

    今天我们来深入探索一下TreeMap和红黑树原理,并使用treemap的使用实例来模拟红黑树的插入删除和调整的操作...

  • TreeMap

    需要先了解红黑树,这是之前分析红黑树的文章。之前在分析红黑树时,我认为红黑树=二叉查找树+红黑平衡,关于二叉查找树...

网友评论

      本文标题:使用红黑树进行数据检索

      本文链接:https://www.haomeiwen.com/subject/ojfquctx.html