数据结构-各类树

数据结构(Java)-各类树

《算法导论》学习自己总结的一点有关树的数据结构。如果有写的不对的地方还请大家帮忙指正。
转载请标明出处:http://blog.csdn.net/u013963380/article/details/78472497
[目录]

1.二叉树

二叉树的定义及其表示

二叉树数据结构中一种重要的数据结构,也是树表家族最为基础的结构。

二叉树的定义

一颗二叉树是一个有穷的结点集合。这个集合可以为空,若不为空,则它是由根结点和其左子树和右子树的两个不相交的二叉树组成。下图是一颗二叉树的示例。

这里写图片描述

二叉树的性质

  • 每个结点至多只有二棵子树
  • 二叉树的子树有左右之分,次序不能颠倒
  • 二叉树的第i层至多有 2i1 个结点
  • 深度为k的二叉树至多有 2k1 个结点
  • 对任何一棵二叉树T,如果其终端结点数为 n0 ,度为2的结点数为 n2 ,则 n0=n2+1

满二叉树和完全二叉树

满二叉树的定义

一颗二叉树除最后一层无任何子结点外,每一层上的所有结点都有两个子结点。

满二叉树的性质

  • 一棵树的深度为k,最大层数为h,深度与最大层数相同, h=k
  • 叶子结点数为 2h1
  • 第h层的结点数为 2h1
  • 一颗满二叉树的总结点数为 2k1

完全二叉树

一颗二叉树除最后一层的结点外,每一层上的所有结点都有两个子结点。看起来和满二叉树的定义差不多,看下图就知道了。

这里写图片描述

二叉树的存储结构

在计算机内存中存储二叉树的时候,除了存储每个结点的本身数据外,还要体现每个结点之间的逻辑关系。

  • 顺序存储:是用一组连续的存储单元(数组)来存储二叉树结点的数据,结点的父子关系是通过它们相对位置来反映的,不需要任何附加的存储单元来存放指针。通常情况下,顺序存储结构用于完全二叉树的存储。

  • 链表存储:二叉树最常用的表示方式是用链表表示,每个结点由数据和左右”指针“3个成员组成。其结构定义如下:

    class Node{
      int key;
      int val;
      Node left;
      Node right;
      Node(int key, int val){
          this.key = key;
          this.val = val;
      }
    }

2.二叉搜索树

二叉搜索树的定义以及操作

二叉搜索树(Binary Search Tree)也称二叉排序树或二叉查找树,它是一种对排序和查找都很有用的特殊二叉树。

二叉搜索树的定义

一颗二叉搜索树是一颗二叉树,它可以为空。如果不为空,它将满足以下性质:

  • 非空左子树的所有键值小于其根结点的键值
  • 非空右子树的所有键值大于其根结点的键值
  • 左右子树也为二叉搜索树

如下图是一颗二叉搜索树:

这里写图片描述

二叉搜索树的时间复杂度

它和二分查找一样,插入和查找的时间复杂度均为 O(logn) ,但是在最坏的情况下仍然会有 O(n) 的时间复杂度。同时,二叉树的高度决定了二叉树的查找效率。

二叉树的查找过程

  • 查找从树的根结点开始,如果树为空,返回null,表示未找到
  • 根结点的键值和X比较,X大,接下来的搜索在右子树中进行;X小,接下来的搜索在左子树中进行;若相等,搜索完成,返回结点的位置。

二叉树的插入过程

  • 若当前的二叉树为空,则插入的元素为根结点
  • 若插入的元素键值小于根结点键值,则将元素插入到左子树中
  • 若插入的元素键值大于根结点键值,则将元素插入到右子树中

二叉树的删除过程

删除的结点为X,这里删除较为复杂分为三种情况

(1)待删除的结点为叶子结点,此时可直接删除

(2)待删除的结点只有一个孩子结点,左孩子或者右孩子

(3)待删除的结点既有左孩子又有右孩子,这种情况最为复杂;一般思路,用待删除结点右子树中的键值最小的结点替代要删除的结点键值,然后删除右子树中键值最小的结点。注意,右子树最小的结点一定不含左子树,所以删除的这个键值最小的结点一定是叶子结点或者只有右子树的结点。这里如果不清楚的话,可以自行画图就明白了。

二叉树的相关操作代码实现Java

package datasctructure;

/**
 * Created by xudong on 2017/11/4.
 * 二叉查找树(二叉搜索树,二叉排序树)
 */
public class BSTree {

    /**
     * 二叉查找树满足的四个条件:
     * 1.若任意结点的左子树不空,则左子树上所有的结点的值均小于它的根结点的值
     * 2.若任意结点的右子树不空,则右子树上所有的结点的值均大于它的根结点的值
     * 3.任意结点的左右子树也分别为二叉查找树
     */

    //根结点
    private Node root;
    //构造函数
    public BSTree(){}
    public BSTree(Node root){
        this.root = root;
    }

    //查找指定键值结点
    public Node find(int key){
        Node currentNode = root;
        while (currentNode != null && currentNode.key != key){
            if (key < currentNode.key){
                currentNode = currentNode.left;
            }else {
                currentNode = currentNode.right;
            }
        }
        return currentNode;
    }

    //插入结点
    public void insert(int key, int val){
        if (root == null){
            root = new Node(key, val);
            return;
        }

        Node currentNode = root;
        Node parentNode = root;
        boolean isLeft = true;
        while (currentNode != null){
            parentNode = currentNode;
            if (key < currentNode.key){
                currentNode = currentNode.left;
                isLeft = true;
            } else if (key > currentNode.key){
                currentNode = currentNode.right;
                isLeft = false;
            } else { return; }//树中存在当前的结点,直接返回
        }
        Node newNode = new Node(key, val);
        if (isLeft){
            parentNode.left = newNode;
        } else {
            parentNode.right = newNode;
        }
    }

    //删除结点

    /**
     * 删除结点的操作较为复杂,分三种情况:
     * 1.待删除的结点为叶子结点,可以直接删除
     * 2.待删除的结点只有一个孩子结点(左子结点或者右子结点)
     * 3.待删除的结点既有左孩子又有右孩子
     * @param key
     * @return
     */
    public boolean delete(int key){
        Node currentNode = root;//保存待删除的结点
        Node parentNode = root;//保存待删除结点的父亲结点
        boolean isLeft = true;
        while (currentNode != null && currentNode.key != key){
            parentNode = currentNode;
            if (key < currentNode.key){
                currentNode = currentNode.left;
                isLeft = true;
            } else {
                currentNode = currentNode.right;
                isLeft = false;
            }
        }
        if (currentNode == null) return false;

        if (currentNode.left == null && currentNode.right == null){//1.第一种情况
            if (currentNode == root)
                root = null;
            else if (isLeft)
                parentNode.left = null;
            else
                parentNode.right = null;
        } else if (currentNode.right == null){//2.第二种情况,只有左孩子
            if (currentNode == root)
                root = currentNode.left;
            else if (isLeft)
                parentNode.left = currentNode.left;
            else
                parentNode.right = currentNode.left;
        } else if (currentNode.left == null){//2.第二种情况,只有右孩子
            if (currentNode == root)
                root = currentNode.right;
            else  if (isLeft)
                parentNode.left = currentNode.right;
            else
                parentNode.right = currentNode.right;
        } else {//3.第三种情况,既有左孩子又有右孩子
            Node directPostNode = getDirectPostNode(currentNode);
            currentNode.key = directPostNode.key;
            currentNode.val = directPostNode.val;
        }
        return true;
    }
    private Node getDirectPostNode(Node delNode){//这个方法是为得到待删除结点的直接后继结点
        Node parentNode = delNode;//保存待删除结点的直接后继结点的父亲结点
        Node directPostNode = delNode;//保存待删除结点的直接后继结点
        Node currentNode = delNode.right;
        while (currentNode != null){
            parentNode = directPostNode;
            directPostNode = currentNode;
            currentNode = currentNode.left;
        }
        if (directPostNode != delNode.right){//从树中删除直接后继结点
            parentNode = directPostNode.right;
            directPostNode.right = null;
        }
        return directPostNode;
    }

}

3.平衡二叉树

在二叉搜索树中,我们提到其查找的时间复杂度为 O(logn) ,其取决于树的高度。但是,事实上,n个结点的二叉树高度取决于其树枝分布的情况,所以当一颗二叉树退化为一颗单枝树的极端情况下,查找的时间复杂度将是 O(n) 。平衡二叉树又称为AVL树,AVL树的插入、删除、查找操作均可在 O(logn) 时间内完成。

平衡二叉树

平衡二叉树的定义

平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用算法有红黑树、AVL树等。在平衡二叉搜索树中,我们可以看到,其高度一般都良好地维持在O(logn),大大降低了操作的时间复杂度。

平衡二叉树-AVL树

AVL树的定义

AVL树是最先发明的自平衡二叉查找树。AVL树得名于它的发明者 G.M. Adelson-Velsky 和 E.M. Landis,他们在 1962 年的论文 “An algorithm for the organization of information” 中发表了它。在AVL中任何节点的两个儿子子树的高度最大差别为1,所以它也被称为高度平衡树,n个结点的AVL树最大深度约 1.44logn 。查找、插入和删除在平均和最坏情况下都是 O(logn) 。增加和删除可能需要通过一次或多次树旋转来重新平衡这个树。

这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在 O(logn) 。但是频繁旋转会使插入和删除牺牲掉 O(logn) 左右的时间,不过相对二叉查找树来说,时间上稳定了很多。

AVL树的调整

当向一颗AVL树中插入新的结点的时候,就会破坏了树的平衡,这时就需要做“平衡化处理”,即作出相应的局部“旋转”调整,使得调整后的树达到平衡。

平衡因子:对于二叉树中任一结点T,其平衡因子(Balance Factor,BF)定义为 BF(T)=HLHR ,其中 HL HR 分别为T的左子树高度和右子树高度。,因此一颗AVL树中任意的结点的平衡因子只能在集合{-1,0,1}中取值。

(1)单旋转:单旋转是针对左左和右右两种情况的解决方案,这两种情况是对称的。

下图是一个右单旋的过程:首先是一颗AVL树,结点A的平衡因子为-1,其左子结点AL是一颗AVL树,右子结点B的平衡因子为0,其左右子结点为BL和BR。现在将结点C插入到BR中去,插入后变成不平衡状态,结点A的平衡因子为-2,结点B的平衡因子为-1,为了保持平衡状态,此时将结点B变成树的根结点,结点A变成B的左子结点,将原本B的左子结点BL置为结点A的右子结点,B的右子结点保持不变。这样既满足了二叉搜索树的性质又满足了平衡二叉树的性质。同样的左单旋也是一个类似的过程,这里就不过多讲解。

这里写图片描述

(2)双旋转:双旋转是针对左右和右左这两种情况,单旋转不能使它达到一个平衡状态,要经过两次旋转。同样的,这两种情况也是对称的。

如下图是左-右双旋的过程:是因为再调整的过程中相当于先对B为根结点的子树做了一次右单旋,然后再对A为根结点的子树做了一次左单旋。

这里写图片描述

AVL树的相关操作代码实现Java

package datasctructure;

/**
 * Created by xudong on 2017/11/7.
 * AVL树的简单实现
 */

//AVL数的结点定义
class AVLNode{
    int key;//键值
    int val;//值
    AVLNode left;
    AVLNode right;
    int height;//树的高度
    public AVLNode(int key, int val){
        this.key = key;
        this.val = val;
        this.height = 0;
    }
}
public class AVLTree {

    private AVLNode root;//根结点
    public AVLTree(){}
    public AVLTree(AVLNode root){this.root = root;}

    /**
     * 求树的高度
     */
    private int height(AVLNode node){
        if(node != null){
            return node.height;
        }
        return 0;
    }
    public int height(){
        return height(root);
    }

    /**
     * 旋转:
     * 1.LL单旋:
     * 2.RR单旋:
     * 3.LR双旋
     * 4.RL双旋
     */
    //左单旋转
    private AVLNode leftSingleRotation(AVLNode node){
        /**
         * node必须有一个左子结点B
         */
        AVLNode currentNode = node.left;
        node.left = currentNode.right;
        currentNode.right = node;

        node.height = Math.max(height(node.left), height(node.right)) + 1;
        currentNode.height = Math.max(height(currentNode.left), node.height) + 1;

        return currentNode;
    }
    //右单旋转
    private AVLNode rightSingleRotation(AVLNode node){
        AVLNode currentNode = node.right;
        node.right = currentNode.left;
        currentNode.left = node;

        node.height = Math.max(height(node.left), height(node.right)) + 1;
        currentNode.height = Math.max(height(currentNode.right), node.height) + 1;
        return currentNode;
    }
    //左右旋转
    private AVLNode leftRightRotation(AVLNode node){
        node.left = rightSingleRotation(node.left);
        return leftSingleRotation(node);
    }
    //右左旋转
    private AVLNode rightLeftRotation(AVLNode node){
        node.right = leftRightRotation(node.right);
        return rightSingleRotation(node);
    }

    /**
     * 插入结点
     * @param root
     * @param newNode
     * @return 新的根结点
     */
    private AVLNode insert(AVLNode root, AVLNode newNode){
        if (root == null){
            root = newNode;
            root.height = 1;
            return root;
        } else {
            int key = root.key;
            if (newNode.key < key){//插入到root的左子树
                root.left = insert(root.left, newNode);
                if (height(root.left) - height(root.right) >= 2){
                    if (newNode.key < root.left.key)
                        root = leftSingleRotation(root);
                    else
                        root = rightSingleRotation(root);
                }
            } else if (newNode.key > key){//插入到root的右子树
                root.right = insert(root.right, newNode);
                if (height(root.right) - height(root.left) >= 2){
                    if (newNode.key > root.right.key)
                        root = rightSingleRotation(root);
                    else
                        root = rightLeftRotation(root);
                }
            } else {

                System.out.println("添加失败!添加了相同的结点!");
            }
        }
        root.height = Math.max(height(root.left), height(root.right)) + 1;
        return root;
    }

    /**
     * 删除结点
     * @param root
     * @param key
     * @return 新的根结点
     */
    private AVLNode delete(AVLNode root, int key){
        if (root == null)
            return null;

        if (root.key > key){//待删除的在左子树中
            root.left = delete(root.left, key);
            if (height(root.left) - height(root.right) >= 2){
                AVLNode r = root.right;
                if (height(r.left) > height(r.right))
                    root = rightLeftRotation(root);
                else
                    root = rightSingleRotation(root);
            }
        } else if (root.key < key){//待删除的在右子树中
            root.right = delete(root.right, key);
            if (height(root.left) - height(root.right) >= 2){
                AVLNode r = root.left;
                if (height(r.left) > height(r.right))
                    root = leftRightRotation(root);
                else
                    root = leftRightRotation(root);
            }
        } else {//找到待删除的结点
            //待删除的结点左右子结点都非空
            if (root.left != null && root.right != null){
                /**
                 * 这里的做法类似于二叉搜索树的删除做法
                 * 如果左子树比右子树高,找到左子树中最大的结点赋值给root
                 * 然后删除该最大的结点
                 */
                if (height(root.left) > height(root.right)){
                    AVLNode max = maximum(root);
                    root.key = max.key;
                    root.val = max.val;
                    root.left = delete(root.left, max.key);
                }else {
                    /**
                     * 同样的,如果右子树比左子树高,找到右子树中最小的结点赋值给root
                     * 然后删除该最小的结点
                     *
                     * 以上这两种做法的木器是:删除最大或最大的结点后,AVL树仍然是平衡的
                     */
                    AVLNode min = minmum(root);
                    root.key = min.key;
                    root.val = min.val;
                    root.right = delete(root.right, min.key);
                }
            } else {
                AVLNode temp = root;
                root = (root.left != null) ? root.left : root.right;
                temp = null;
            }
        }
        return root;
    }

    /**
     * 查找最大的结点
     */
    private AVLNode maximum(AVLNode root){
        if (root == null)
            return null;
        while (root.right != null){
            root = root.right;
        }
        return  root;
    }

    /**
     * 查找最小的结点
     */
    private  AVLNode minmum(AVLNode root){
        if (root == null)
            return null;
        while (root.left != null){
            root = root.left;
        }
        return root;
    }
}

平衡二叉树-红黑树

红黑树的定义

红黑树是一种自平衡二叉搜索树,是在计算机科学中用到的一种数据结构,典型的用途是实现关联数组。它是在1972年由鲁道夫·贝尔发明的,称之为”对称二叉B树”,它现代的名字是在 Leo J. Guibas 和 Robert Sedgewick 于1978年写的一篇论文中获得的。它是复杂的,但它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在 O(logn) 时间内做查找,插入和删除,这里的n是树中元素的数目。

红黑树的性质

红黑树是每个结点都带有颜色属性的二叉搜索树,颜色为红色或黑色。

  • 性质1:结点是红色或黑色
  • 性质2:根是黑色的
  • 性质3:所有的叶子都是黑色的(这里的叶子结点是NIL或NULL的叶子结点!)
  • 性质4:如果一个结点是红色的,则它的两个子结点都是黑色的
  • 性质5:对每一个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同署名的黑色结点

下面是一个具体的红黑树的图例:

这里写图片描述

红黑树的定理:一颗有n个内部结点的红黑树的高度至多为 2log(n+1) 。其时间复杂度为 O(logn)

红黑树的调整

当向一颗红黑树进行插入或删除的操作,就会破坏红黑树的平衡性,使得性质发生变化,可能就不是一颗红黑树。而通过旋转操作,可以使其重新变成红黑树。旋转包括两种:左旋右旋

左旋:所谓的左旋其实对结点X,将其变成某一个结点Y的左子结点。

右旋:和左旋一样,不同的是将结点X变成结点Y的右子结点。

下面是左旋和右旋的图示:

这里写图片描述

这里写图片描述

左旋和右旋是个相对的概念,原理类似,理解一个就会理解了另一个。

红黑树的基本操作-插入结点

将一个结点插入到一颗红黑树中去,由于其插入过程有可能会破坏其红黑树的性质,需要进行旋转和重新着色的方法来修正这课树使其变成红黑树。其详细的过程如下:

第一步,红黑树原本就是一颗二叉搜索树,按照其二叉搜索树的规则插入需要插入的结点。

第二步,将插入的结点着色为红色,设置为红色是因为红黑树的性质5。

第三步,通过一系列的旋转和着色等操作,使之重新成为一颗红黑树。

插入的过程一般有可能会违背红黑树的性质4,所以这里需要调整使其满足性质4即可。而根据被插入结点的父结点的情况,可以将其插入的结点的步骤第三步分为以下几种情况来处理:

  • 1.被插入的结点是根结点,此时直接把此结点设置为黑色即可。
  • 2.被插入的结点的父结点是黑色的,此时什么都不需要做,结点被插入后还是红黑树。
  • 3.被插入的结点的父结点是红色的,此时与性质5冲突了,这个时候,被插入的结点是一定存在非空祖父结点,所以一定存在叔结点(即使是空,也是视为存在,NIL是黑色的)。根据其叔结点的情况,又可以分为三种情况(这里的三种情况均是在被插入结点的父结点是其祖父结点的左子结点的情况下,如果是右子结点的情况,接下来的操作均是和左子结点的情况相对应的)(以下括号里面是相对应的情况)(case2的目的是将其转换为case3的情况,这点从代码的实现就可以看出来,代码会在下面给出)(这里给一个红黑树的模拟地址:http://sandbox.runjs.cn/show/2nngvn8w可以通过这个去测试这三种情况)。
Case1:当前结点的父结点是红色,且当前结点的祖父结点的另一个结点(叔结点)也是红色的
处理方法:
(1)将父结点设为黑色的
(2)将叔结点设为黑色的
(3)将祖父结点设为红色的
(4)将祖父结点设为当前结点,然后继续对当前结点进行操作
Case2:当前结点的父结点是红色的,且叔结点是黑色的,且当前结点是其父结点的右子结点(左子结点)
处理方法:
(1)将父结点设为当前结点
(2)将当前结点左旋(右旋)
Case3:当前结点的父结点是红色的,且叔结点是黑色的,且当前结点是其父结点的左子结点(右子结点)
处理方法:
(1)将父结点设为黑色的
(2)将祖父结点设为红色
(3)将祖父结点进行右旋(左旋)

接下来图示说明:

如下,是我们在一颗红黑树中插入了结点55.

这里写图片描述

调整的过程:

(1)首先当前结点是55,当前结点的父结点是红色的,且其叔结点也是红色的。按照Case1的处理方法:将其父结点设为黑色的,将其叔结点设为黑色的,将其祖父结点设为红色的,然后将祖父结点设为当前结点,之后对当前结点进行操作。

这里写图片描述

(2)根据(1)过程,当前结点是60,当前结点的父结点是红色的,叔结点是黑色的,且当前结点是其父结点的右孩子,按照Case2的处理方法:将其父结点当做当前结点,将当前结点进行左旋。

这里写图片描述

(3)根据(2)过程,当前结点是40,此时当前结点的父结点是红色的,叔结点是黑色的,且当前结点是父结点的左孩子,按照Case3的处理方法:将其父结点设为黑色,将其祖父结点设为红色,以祖父结点进行右旋。

这里写图片描述

根据以上的过程,最后得到是一颗红黑树。

红黑树的基本操作-删除结点:

同样的和插入结点一样,删除红黑树中的一个结点会导致红黑树的性质被破坏,通过旋转和重新着色使其恢复。其过程如下:

第一步,将红黑树当作一颗二叉搜索树,将其结点删除。这里的删除和二叉搜索树中的删除结点的方法是一样的。复习一下

  • 被删除的结点没有子结点,即为叶子结点,直接删除结点就行。
  • 被删除的结点只有一个子结点,删除该结点,并用该结点的唯一子结点顶替它的位置即可。
  • 被删除的结点有两个子结点,用待删除结点右子树中的键值最小的结点替代要删除的结点键值,然后删除右子树中键值最小的结点。

第二步,通过旋转和重新着色调整修正该树,使其成为一颗红黑树。

这里删除结点后会违背性质2,性质4,性质5,需要进行相关的调整使其继续符合红黑树的性质。为了便于分析,我们假设”x包含一个额外的黑色”(x原本的颜色还存在),这样就不会违反特性5。为什么呢?
​ 通过删除,我们知道:删除结点y之后,x占据了原来结点y的位置。 既然删除y(y是黑色),意味着减少一个黑色节点;那么,再在该位置上增加一个黑色即可。这样,当我们假设”x包含一个额外的黑色”,就正好弥补了”删除y所丢失的黑色节点”,也就不会违反特性5。 因此,假设”x包含一个额外的黑色”(x原本的颜色还存在),这样就不会违反特性5。现在,x不仅包含它原本的颜色属性,x还包含一个额外的黑色。即x的颜色属性是”红+黑”或”黑+黑”,它违反了特性1。所以现在只需要通过调整使其回复性质1,2,4即可,一般会有如下几种情况:

  • 1.x是一个“红+黑”结点,处理方式:直接将x设为黑色,结束。
  • 2.x是一个“黑+黑”结点,且x是根结点:什么都不需要做,依旧是红黑树。
  • 3.x是一个“黑+黑”结点,且x不是根结点:这种情况又可以划分4中case来讨论。
    (注意:如下的几种Case情况均是在x是左子结点,如果x是右子结点,接下来的操作是相对应的对称情况,这里就不画图了好麻烦,结合代码自行画图学习~)
Case1:x"黑+黑"结点,x的兄弟结点是红色(此时x的父结点和x的兄弟结点的子结点都是黑色的)。
处理方法:
(1)将x的兄弟结点设为黑色的
(2)将x的父结点设为红色
(3)对x的父结点进行左旋
(4)左旋后,重新设置x的兄弟结点
Case2:x"黑+黑"结点,x的兄弟结点是黑色的,x的兄弟结点的两个孩子都是黑色的。
处理方法:
(1)将x的兄弟结点设为红色
(2)将x的父结点设为当前结点x
Case3:x"黑+黑"结点,x的兄弟结点是黑色的,x的兄弟结点的左孩子是红色的,右孩子是黑色的。
处理方法:
(1)将x兄弟结点的左孩子设为黑色
(2)将x兄弟结点设为红色
(3)对x的兄弟结点进行右旋
(4)右旋后,重新设置x的兄弟结点
Case4:x"黑+黑"结点,x的兄弟结点是黑色的,x的兄弟结点的右孩子是红色的,左孩子是黑色的。
处理方法:
(1)将x父结点颜色赋值给x的兄弟结点
(2)将x父结点设为黑色
(3)将x兄弟结点的右子结点设为黑色
(4)对x的父结点进行左旋
(5)设为x为根结点

红黑树的相关操作代码实现Java

package datasctructure;

import javax.print.DocFlavor;
import java.util.TreeMap;

/**
 * Created by xudong on 2017/11/8.
 * 红黑树的简单实现
 */
public class RedBlackTree {

    //定义红黑属性
    private static final boolean RED = false;
    private static final boolean BLACK = true;

    //定义红黑树结点
    static final class RBNode{
        int key;
        int val;
        RBNode left, right;
        RBNode parent;
        boolean color = BLACK;

        public RBNode(int key, int val, RBNode parent){
            this.key = key;
            this.val = val;
            this.parent = parent;
        }
    }

    //定义根结点和构造函数
    private RBNode root;
    public RedBlackTree(){}
    public RedBlackTree(RBNode root){
        root.color = BLACK;
        this.root = root;
    }

    //左旋:将当前结点变成左子结点
    public void rotateLeft(RBNode node){
        if (node != null){
            RBNode r = node.right;
            node.right = r.left;
            if (r.left != null){
                r.left.parent = node;
            }
            r.parent = node.parent;
            if (node.parent == null)
                root = r;
            else if (node.parent.left == node)
                node.parent.left = r;
            else
                node.parent.right = r;
            r.left = node;
            node.parent = r;
        }
    }

    //右旋:将当前的结点变成右子结点
    public void rotateRight(RBNode node){
        if (node != null){
            RBNode l = node.left;
            node.left = l.right;
            if (l.right != null)
                l.right.parent = node;
            l.parent = node.parent;
            if (node.parent == null)
                root = l;
            else  if (node.parent.left == node)
                node.parent.left = l;
            else
                node.parent.right = l;
            l.right = node;
            node.parent = l;
        }
    }

    /**
     * 下面几个方法分别是:
     * 求结点左子
     * 求结点右子
     * 求结点的父
     * 求结点的color
     * 设置结点的color
     */
    private static RBNode leftOf(RBNode node){
        return (node == null) ? null : node.left;
    }
    private static RBNode rightOf(RBNode node){
        return (node == null) ? null : node.right;
    }
    private static RBNode parentOf(RBNode node){
        return (node == null) ? null : node.parent;
    }
    private static boolean colorOf(RBNode node){
        return (node == null) ? BLACK : node.color;
    }
    private static void setColor(RBNode node, boolean c){
        if (node != null)
            node.color = c;
    }

    /**
     * 添加一个结点:
     * 情况一:被插入的结点是根结点,直接替换为root结点,设置black
     * 情况二:被插入的结点的父结点是黑色的,直接插入即可
     * 情况三:被插入的结点的父结点是红色的:
     *    case1:其叔结点是红色的;
     *    case2:其叔结点是黑色的,切当前结点是父结点的右孩子;
     *    case3:其叔结点是黑色的,且当前结点是父结点的左孩子;
     * @param x
     */
    public  void fixAfterInsertion(RBNode x){
        x.color = RED;
        while (x != null && x != root && x.parent.color == RED){
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))){//添加的结点的父节点是其祖父结点的左结点
                RBNode y = rightOf(parentOf(parentOf(x)));//叔结点
                if (colorOf(y) == RED){
                    /**
                     * case1:叔结点是红色的;
                     * 首先将父节点和叔父结点设为黑色
                     * 然后将祖父结点设为红色
                     * 最后将祖父结点设为当前结点x
                     */
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    /**
                     * case2:叔结点是黑色的,且当前结点是其父结点的右孩子
                     * 将父结点作为新的当前结点,然后以当前结点左旋
                     */
                    if (x == rightOf(parentOf(x))){
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    /**
                     * case3:叔结点是黑色的,且当前结点是其父结点的左孩子
                     * 将父结点设为黑色的,然后将祖父结点设为红色的,然后以祖父结点进行右旋
                     */
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                RBNode y = leftOf(parentOf(parentOf(x)));//叔结点
                if (colorOf(y) == RED){
                    setColor(parentOf(x),BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    //case2:叔结点是黑色的,且当前结点是其父结点的左孩子
                    if (x == leftOf(parentOf(x))){
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        root.color = BLACK;
    }

    /**
     * 求一个结点的后继结点,删除节点的时候相当于求删除结点的右子树上的最小值
     * @param node
     * @return
     */
    private static RBNode successor(RBNode node){
        if (node  == null){//如果当前结点为空
            return null;
        }else if (node.right != null){//求右子树上键值最小的结点
            RBNode p = node.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else {//按照delete中的条件可以看出,这种情况是不会发生的?
            RBNode p = node.parent;
            RBNode ch = node;
            while (p != null && ch == p.right){
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }
    private void delete(RBNode node){
        if (node.left != null && node.right != null){
            RBNode s = successor(node);
            node.key = s.key;
            node.val = s.val;
            node = s;
        }
        RBNode replacement = (node.left != null ? node.left : node.right);

        if (replacement != null){
            replacement.parent = node.parent;
            if (node.parent == null)
                root = replacement;
            else if (node == node.parent.left)
                node.parent.left = replacement;
            else
                node.parent.right = replacement;

            node.left = node.right = node.parent = null;

            if (node.color == BLACK)//删除的结点的颜色是黑色的会导致不符合红黑树的性质
                fixAfterDeletion(replacement);
        }else if (node.parent == null){
            root = null;//rootjieidan
        }else {
            if (node.color == BLACK)
                fixAfterDeletion(node);
            if (node.parent != null){
                if (node.parent.left == node)
                    node.parent.left = null;
                else if (node.parent.right == node)
                    node.parent.right = null;
                node.parent = null;
            }
        }
    }
    private void fixAfterDeletion(RBNode x){
        while (x != root && colorOf(x) == BLACK){
            if (x == leftOf(parentOf(x))){//x是左子结点
                RBNode sib = rightOf(parentOf(x));//x的兄弟结点
                if (colorOf(sib) == RED){//case1:x是"黑+黑"结点,x的兄弟结点是红色(此时x的父结点和x的兄弟结点的子结点都是黑色的)
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }
                if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK){
                    //case2::x是"黑+黑"结点,x的兄弟结点是黑色的,x的兄弟结点的两个孩子都是黑色的。
                    setColor(sib, RED);
                    x = parentOf(x);
                }else {
                    if (colorOf(rightOf(sib)) == BLACK){//case3:x是"黑+黑"结点,x的兄弟结点是黑色的,x的兄弟结点的左孩子是红色的,右孩子是黑色的。
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, BLACK);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    //case4:x是"黑+黑"结点,x的兄弟结点是黑色的,x的兄弟结点的右孩子是红色的,左孩子是黑色的。
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;
                }

            } else { //相反的情况
                RBNode sib = leftOf(parentOf(x));
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }

                if (colorOf(rightOf(sib)) == BLACK &&
                        colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }
        setColor(x, BLACK);
    }

    public static void main(String[] args){
        TreeMap tee = new TreeMap();
    }
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值