20172319 实验二《树》实验报告

20172319 2018.11.04-11.12

实验二《树》 实验报告

课程名称:《程序设计与数据结构》  
学生班级:1723班  
学生姓名:唐才铭  
学生学号:20172319 
实验教师:王志强老师
课程助教:张师瑜学姐、张之睿学长
实验时间:2018年11月04日——2018年11月12日
必修/选修:必修

目录


实验内容

  1. 实验二-1-实现二叉树: 完成链树LinkedBinaryTree的实现。
  2. 实验二 树-2-中序先序序列构造二叉树: 基于LinkedBinaryTree,实现基于(中序,先序)序列构造唯一一棵二㕚树的功能
  3. 实验二 树-3-决策树: 自己设计并实现一颗决策树
  4. 实验二 树-4-表达式树: 输入中缀表达式,使用树将中缀表达式转换为后缀表达式,并输出后缀表达式和计算结果
  5. 实验二 树-5-二叉查找树: 完成PP11.3
  6. 实验二 树-6-红黑树分析: 参考http://www.cnblogs.com/rocedu/p/7483915.html对Java中的红黑树(TreeMap,HashMap)进行源码分析,并在实验报告中体现分析结果

返回目录


实验要求

  1. 完成蓝墨云上与实验二《树》相关的活动,及时提交代码运行截图和码云Git链接,截图要有学号水印,否则会扣分。
  2. 完成实验、撰写实验报告,实验报告以博客方式发表在博客园,注意实验报告重点是运行结果,遇到的问题(工具查找,安装,使用,程序的编辑,调试,运行等)、解决办法(空洞的方法如“查网络”、“问同学”、“看书”等一律得0分)以及分析(从中可以得到什么启示,有什么收获,教训等)。报告可以参考范飞龙老师的指导
  3. 严禁抄袭,有该行为者实验成绩归零,并附加其他惩罚措施。

返回目录


实验步骤

  1. 实验二-1-实现二叉树:
    参考教材p212,完成链树LinkedBinaryTree的实现(getRight,contains,toString,preorder,postorder)
    用JUnit或自己编写驱动类对自己实现的LinkedBinaryTree进行测试,提交测试代码运行截图,要全屏,包含自己的学号信息
    课下把代码推送到代码托管平台
  2. 实验二 树-2-中序先序序列构造二叉树:
    基于LinkedBinaryTree,实现基于(中序,先序)序列构造唯一一棵二㕚树的功能,比如给出中序HDIBEMJNAFCKGL和后序ABDHIEJMNCFGKL,构造出附图中的树
    用JUnit或自己编写驱动类对自己实现的功能进行测试,提交测试代码运行截图,要全屏,包含自己的学号信息
    课下把代码推送到代码托管平台
  3. 实验二 树-3-决策树:
    自己设计并实现一颗决策树
    提交测试代码运行截图,要全屏,包含自己的学号信息
    课下把代码推送到代码托管平台
  4. 实验二 树-4-表达式树:
    输入中缀表达式,使用树将中缀表达式转换为后缀表达式,并输出后缀表达式和计算结果(如果没有用树,则为0分)
    提交测试代码运行截图,要全屏,包含自己的学号信息
    课下把代码推送到代码托管平台
  5. 实验二 树-5-二叉查找树:
    完成PP11.3
    提交测试代码运行截图,要全屏,包含自己的学号信息
    课下把代码推送到代码托管平台
  6. 实验二 树-5-二叉查找树:
    参考http://www.cnblogs.com/rocedu/p/7483915.html对Java中的红黑树(TreeMap,HashMap)进行源码分析,并在实验报告中体现分析结果。
    (C:\Program Files\Java\jdk-11.0.1\lib\src\java.base\java\util)

前期准备:

  1. 预先下载安装好IDEA 。

需求分析:

  1. 需要掌握二叉查找树的相关知识;
  2. 需要掌握当任意给出两个序能构建出唯一一棵二叉树;
  3. 需要理解表达式树的实现;
  4. 需要理解决策树的实现。

返回目录


代码实现及解释

本次实验一共分为六个提交点:

  • 实验二-1-实现二叉树:
  • 要实现的方法有:getRight;contains;toString;preorder;postorder;
  • getRight是获取右子树,但这里并没有准确说明针对哪种结点的操作,为了程序的完整性,便实现了可调用如何结点的右子树。
  • getRight具体代码如下:
// 获取某一结点的右子树
    public  String getNodeRightTree(T Elemnet){
        String result;
        BinaryTreeNode node = new BinaryTreeNode(Elemnet);
        LinkedBinaryTree linkedBinaryTree = new LinkedBinaryTree();
        linkedBinaryTree.root = root;
        if (root==null){
            return "";
        }
        else {
            if (root != null && root.left == null && root.right == null) {
                linkedBinaryTree.root = root;
                result = linkedBinaryTree.printTree();
                return result;
            }
            node = findNode(Elemnet,root);
            if (node.right!=null){
                linkedBinaryTree.root = node.right;
                result = linkedBinaryTree.printTree();
            }
            else {
                result = "该结点无右子树";
            }
            return result;
        }
    }
  • 实现结果截图:

  • contains判断树中是否包含某一元素,这里使用了原有的find方法,使得实现更加便捷。

  • contains具体代码如下:

@Override
    public boolean contains(T targetElement)
    {
        // To be completed as a Programming seatwork
        T temp;
        boolean found = false;

        try {
            temp = find(targetElement);
            found = true;
        }
        catch (Exception ElementNotFoundExecption){
            found = false;
        }
        return found;
    }

  • find的具体代码如下:
@Override
    public T find(T targetElement) throws ElementNotFoundException
    {
        BinaryTreeNode<T> current = findNode(targetElement, root);

        if (current == null) {
            throw new ElementNotFoundException("LinkedBinaryTree");
        }

        return (current.getElement());
    }

  • 实现结果截图:

  • toString是输出树中元素,原本是直接通过一个遍历算法来输出,但为了实验的直观和便于操作,借用了表达式中的printTree

  • toString具体代码:此处用了前序遍历来输出

@Override
    public String toString()
    {
        // To be completed as a Programming seatwork
        ArrayUnorderedList<T> tempList = new ArrayUnorderedList<T>();
        preOrder(root,tempList);

        return tempList.toString();
    }
  • printTree具体代码:
public String printTree()
    {
        UnorderedListADT<BinaryTreeNode<T>> nodes =
                new ArrayUnorderedList<BinaryTreeNode<T>>();
        UnorderedListADT<Integer> levelList =
                new ArrayUnorderedList<Integer>();
        BinaryTreeNode<T> current;
        String result = "";
        int printDepth = this.getHeight();
        int possibleNodes = (int)Math.pow(2, printDepth + 1);
        int countNodes = 0;

        nodes.addToRear(root);
        Integer currentLevel = 0;
        Integer previousLevel = -1;
        levelList.addToRear(currentLevel);

        while (countNodes < possibleNodes)
        {
            countNodes = countNodes + 1;
            current = nodes.removeFirst();
            currentLevel = levelList.removeFirst();
            if (currentLevel > previousLevel)
            {
                result = result + "\n\n";
                previousLevel = currentLevel;
                for (int j = 0; j < ((Math.pow(2, (printDepth - currentLevel))) - 1); j++) {
                    result = result + " ";
                }
            }
            else
            {
                for (int i = 0; i < ((Math.pow(2, (printDepth - currentLevel + 1)) - 1)) ; i++)
                {
                    result = result + " ";
                }
            }
            if (current != null)
            {
                result = result + (current.getElement()).toString();
                nodes.addToRear(current.getLeft());
                levelList.addToRear(currentLevel + 1);
                nodes.addToRear(current.getRight());
                levelList.addToRear(currentLevel + 1);
            }
            else {
                nodes.addToRear(null);
                levelList.addToRear(currentLevel + 1);
                nodes.addToRear(null);
                levelList.addToRear(currentLevel + 1);
                result = result + " ";
            }

        }

        return result;
    }
  • 实现结果截图:

  • preorder,postorder,书上只给了inorder的实现,只需更改遍历结点的顺序即可实现:

  • preorderpostorder具体代码如下:

private void preOrder(BinaryTreeNode<T> node,
                            ArrayUnorderedList<T> tempList)
    {
        // To be completed as a Programming seatwork
        if (node!=null){
            tempList.addToRear(node.element);
            preOrder(node.left,tempList);
            preOrder(node.right,tempList);
        }

    }
 private void postOrder(BinaryTreeNode<T> node,
                             ArrayUnorderedList<T> tempList)
    {
        // To be completed as a Programming seatwork
        if (node != null)
        {
            postOrder(node.getLeft(), tempList);
            postOrder(node.getRight(), tempList);
            tempList.addToRear(node.getElement());
        }
    }

  • 实现结果截图:

  • 实验二 树-2-中序先序序列构造二叉树:

  • 先整明白如何通过给定的两个不同遍历来构建一棵唯一的二叉树,在一轮递归中用两个指针分别指向前序和中序中的元素,遍历前序和中序,当两个指针指向的元素一样时,结束该轮次,记录下一次遍历前序的起始位置,开始下轮遍历。

  • 具体代码如下:

//  前序中序构建二叉树
    public BinaryTreeNode BuildTree(char[] preorder, char[] inorder) {
        return BuildLinkedBinaryTree(preorder, inorder, 0, inorder.length - 1, inorder.length);
    }

    /**
     * @param preorder 前序
     * @param inorder  中序
     * @param Start 起始位置
     * @param End 终止位置
     * @param length 结点个数
     */
    public BinaryTreeNode BuildLinkedBinaryTree(char[] preorder,char[] inorder,int Start, int End,int length) {
        if (preorder==null||preorder.length == 0 || inorder == null
                || inorder.length == 0 || length <= 0){
            return null;
        }
        BinaryTreeNode binaryTreeNode;
        binaryTreeNode = new BinaryTreeNode(preorder[Start]);
        if (length==1){
            return binaryTreeNode;
        }
        int flag=0;
        while (flag < length){
            if (preorder[Start] == inorder[End - flag]){
                break;
            }
            flag++;
        }
        binaryTreeNode.left = BuildLinkedBinaryTree(preorder, inorder,Start + 1,  End - flag - 1, length - 1 - flag);
        binaryTreeNode.right = BuildLinkedBinaryTree(preorder, inorder,Start + length - flag,  End, flag );
        return binaryTreeNode;
    }
  • 实现结果截图:

  • 实验二 树-3-决策树:

  • 自己想好所决策需要的问题,修改先前文件的内容即可:

  • 实现结果截图:

  • 实验二 树-4-表达式树

  • 实现思想:

  • 数字是叶子节点,操作符为根节点。

  • 先用中缀表达式构建成树,之后后序遍历可得其后缀表达式;

  • 构树过程:

  • 从表达式的最后一位元素往前扫描,当遇到最后计算的运算符(+或-)时,作为当前根节点,运算符左侧表达式作为左节点,右侧表达式作为右节点,然后递归处理。

  • 具体代码如下:

private boolean priority(String[] operator,int size){
        // 先对有+ - 的式子进行拆分
        boolean found1 = true,found2=true ,found = true;
        for (int i = 0 ; i< size;i++) {
            if (operator[i].equals("+")) {
                found1 = false;
            }
        }
        for (int i = 0 ; i< size;i++) {
            if (operator[i].equals("-")) {
                found2 = false;
                }
            }
        if (found1 == false||found2==false){
            found = false;
        }
        return found;
    }
    public BinaryTreeNode Build_Expression_Tree(String[] expression, int size){
        // 带括号的式子暂未实现(递归出现的问题太多了(╬ ̄皿 ̄))
        BinaryTreeNode binaryTreeNode = new BinaryTreeNode(null);
        int length = size; //  元素个数
        String[] expression_Left_Tree = null; //  左子树
        String[] expression_Right_Tree = null; //  右子树
        for (int i = length - 1; i > 0; i--){  //  遍历数组元素
            String temp = expression[i];
                if (temp.equals("+") || temp.equals("-")) {  // 若遇到+ - ,则对数组进行此元素左右分割
                    binaryTreeNode = new BinaryTreeNode(temp);
                    expression_Left_Tree = new String[i];
                    expression_Right_Tree = new String[length - i - 1];
                    for (int j = 0; j < expression_Left_Tree.length; j++) {  //  拆分结点左边数组(左子树)
                        expression_Left_Tree[j] = expression[j];
                    }
                    for (int k = 0; k < expression_Right_Tree.length; k++) {//  拆分结点右边数组(右子树)
                        expression_Right_Tree[k] = expression[i + k + 1];
                    }
                    if (expression_Left_Tree.length == 1) {  // 若结点左子树数组长度为1
                        binaryTreeNode.setLeft(new BinaryTreeNode(expression_Left_Tree[0]));// 输出数组元素并建立左孩子
                        if (expression_Right_Tree.length!=1){ // 对该结点右端进行建树,后面情况大致一样不做多余复述
                            binaryTreeNode.setRight(Build_Expression_Tree(expression_Right_Tree,expression_Right_Tree.length));
                        }
                        if (expression_Right_Tree.length==1){
                            binaryTreeNode.setRight(new BinaryTreeNode(expression_Right_Tree[0]));
                        }
                        return binaryTreeNode;

                    }


                    if (expression_Right_Tree.length == 1) {
                        binaryTreeNode.setRight(new BinaryTreeNode(expression_Right_Tree[0]));
                        if (expression_Left_Tree.length!=1){
                            binaryTreeNode.setLeft(Build_Expression_Tree(expression_Left_Tree,expression_Left_Tree.length));
                        }
                        if (expression_Left_Tree.length==1){
                            binaryTreeNode.setLeft(new BinaryTreeNode(expression_Left_Tree[0]));
                        }
                        return binaryTreeNode;
                    }
                    break;
                }


            else if (priority(expression,expression.length)!=false){  // 优先级判断,此刻数组里已无加减号
                if (temp.equals("*") || temp.equals("/")) {   // 若遇到+ - ,则对数组进行此元素左右分割
                    binaryTreeNode = new BinaryTreeNode(temp);
                    expression_Left_Tree = new String[i];
                    expression_Right_Tree = new String[length - i - 1];
                    for (int j = 0; j < expression_Left_Tree.length; j++) {
                        expression_Left_Tree[j] = expression[j];
                    }
                    for (int k = 0; k < expression_Right_Tree.length; k++) {
                        expression_Right_Tree[k] = expression[i + k + 1];
                    }
                    if (expression_Left_Tree.length == 1) {
                        binaryTreeNode.setLeft(new BinaryTreeNode(expression_Left_Tree[0]));
                        if (expression_Right_Tree.length!=1){
                            binaryTreeNode.setRight(Build_Expression_Tree(expression_Right_Tree,expression_Right_Tree.length));
                        }
                        if (expression_Right_Tree.length==1){
                            binaryTreeNode.setRight(new BinaryTreeNode(expression_Right_Tree[0]));
                        }
                        return binaryTreeNode;
                    }
                    if (expression_Right_Tree.length == 1) {
                        binaryTreeNode.setRight(new BinaryTreeNode(expression_Right_Tree[0]));
                        if (expression_Left_Tree.length!=1){
                            binaryTreeNode.setLeft(Build_Expression_Tree(expression_Left_Tree,expression_Left_Tree.length));
                        }
                        if (expression_Left_Tree.length==1){
                            binaryTreeNode.setLeft(new BinaryTreeNode(expression_Left_Tree[0]));
                        }
                        return binaryTreeNode;
                    }
                    break;
                }
            }
        }
        binaryTreeNode.setLeft(Build_Expression_Tree(expression_Left_Tree,expression_Left_Tree.length));
        binaryTreeNode.setRight(Build_Expression_Tree(expression_Right_Tree,expression_Right_Tree.length));
        return binaryTreeNode;
    }

  • 运行结果截图:

  • 实验二 树-5-二叉查找树
  • 完成PP11.3:实现removeMin;findMin;findMax操作:
  • 具体代码如下:
  @Override
    public T removeMin() throws EmptyCollectionException
    {
        T result = null;

        if (isEmpty()) {
            throw new EmptyCollectionException("LinkedBinarySearchTree");
        } else
        {
            if (root.left == null) 
            {
                result = root.element;
                root = root.right;
            }
            else 
            {
                BinaryTreeNode<T> parent = root;
                BinaryTreeNode<T> current = root.left;
                while (current.left != null) 
                {
                    parent = current;
                    current = current.left;
                }
                result =  current.element;
                parent.left = current.right;
            }

            modCount--;
        }
 
        return result;
    }


@Override
    public T findMin() throws EmptyCollectionException
    {
        // To be completed as a Programming Project
        T result = null;

        if (isEmpty()) {
            throw new EmptyCollectionException("LinkedBinarySearchTree");
        } else
        {
            if (root.left == null)
            {
                result = root.element;
                root = root.right;
            }
            else
            {
                BinaryTreeNode<T> parent = root;
                BinaryTreeNode<T> current = root.left;
                while (current.left != null)
                {
                    parent = current;
                    current = current.left;
                }
                result =  current.element;
                parent.left = current;
            }
        }
        return result;
    }

@Override
    public T findMax() throws EmptyCollectionException
    {
        // To be completed as a Programming Project
        T result = null;

        if (isEmpty()) {
            throw new EmptyCollectionException("LinkedBinarySearchTree");
        } else
        {
            if (root.right== null)
            {
                result = root.element;
                root = root.left;
            }
            else
            {
                BinaryTreeNode<T> parent = root;
                BinaryTreeNode<T> current = root.right;
                while (current.right != null)
                {
                    parent = current;
                    current = current.right;
                }
                result =  current.element;
                parent.right = current;
            }
        }
        return result;
    }
  • 实现结果截图:

  • 实验二 树-6-红黑树分析

  • TreeMap

  • TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
    TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
    TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
    TreeMap 实现了Cloneable接口,意味着它能被克隆
    TreeMap 实现了java.io.Serializable接口,意味着它支持序列化
    TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
    TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。
    另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

  • 1.类名及成员:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
    // 比较器对象
    private final Comparator<? super K> comparator;

    // 根节点
    private transient Entry<K,V> root;

    // 集合大小
    private transient int size = 0;

    // 树结构被修改的次数
    private transient int modCount = 0;

    // 静态内部类用来表示节点类型
    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;     // 键
        V value;   // 值
        Entry<K,V> left;    // 指向左子树的引用(指针)
        Entry<K,V> right;   // 指向右子树的引用(指针)
        Entry<K,V> parent;  // 指向父节点的引用(指针)
        boolean color = BLACK; // 
    }
}

  • 2.类构造方法:
public TreeMap() {   // 1,无参构造方法
        comparator = null; // 默认比较机制
    }

    public TreeMap(Comparator<? super K> comparator) { // 2,自定义比较器的构造方法
        this.comparator = comparator;
    }

    public TreeMap(Map<? extends K, ? extends V> m) {  // 3,构造已知Map对象为TreeMap
        comparator = null; // 默认比较机制
        putAll(m);
    }

    public TreeMap(SortedMap<K, ? extends V> m) { // 4,构造已知的SortedMap对象为TreeMap
        comparator = m.comparator(); // 使用已知对象的构造器
        try {
            buildFromSorted(m.size(), m.entrySet().iterator(), null, null);
        } catch (java.io.IOException cannotHappen) {
        } catch (ClassNotFoundException cannotHappen) {
        }
    }



  • 3.红黑树:
  • (1) 结点颜色及其对应类:
    // 红黑树的节点颜色--红色
    private static final boolean RED   = false;
    // 红黑树的节点颜色--黑色
    private static final boolean BLACK = true;

    // “红黑树的节点”对应的类。
    // 包含了 key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)
    static final class Entry<K,V> implements Map.Entry<K,V> {
        // 键
        K key;
        // 值
        V value;
        // 左孩子
        Entry<K,V> left = null;
        // 右孩子
        Entry<K,V> right = null;
        // 父节点
        Entry<K,V> parent;
        // 当前节点颜色
        boolean color = BLACK;

        // 构造函数
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }

        // 返回“键”
        public K getKey() {
            return key;
        }

        // 返回“值”
        public V getValue() {
            return value;
        }

        // 更新“值”,返回旧的值
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        // 判断两个节点是否相等的函数,覆盖equals()函数。
        // 若两个节点的“key相等”并且“value相等”,则两个节点相等
        public boolean equals(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;

            return valEquals(key,e.getKey()) && valEquals(value,e.getValue());
        }

        // 覆盖hashCode函数。
        public int hashCode() {
            int keyHash = (key==null ? 0 : key.hashCode());
            int valueHash = (value==null ? 0 : value.hashCode());
            return keyHash ^ valueHash;
        }

        // 覆盖toString()函数。
        public String toString() {
            return key + "=" + value;
        }
    }
  • (2) 在树中结点的共同操作:
    // 返回“红黑树的第一个节点”
    final Entry<K,V> getFirstEntry() {
        Entry<K,V> p = root;
        if (p != null)
            while (p.left != null)
                p = p.left;
        return p;
    }

    // 返回“红黑树的最后一个节点”
    final Entry<K,V> getLastEntry() {
        Entry<K,V> p = root;
        if (p != null)
            while (p.right != null)
                p = p.right;
        return p;
    }

    // 返回“节点t的后继节点”
    static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
        if (t == null)
            return null;
        else if (t.right != null) {
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else {
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.right) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

    // 返回“节点t的前继节点”
    static <K,V> Entry<K,V> predecessor(Entry<K,V> t) {
        if (t == null)
            return null;
        else if (t.left != null) {
            Entry<K,V> p = t.left;
            while (p.right != null)
                p = p.right;
            return p;
        } else {
            Entry<K,V> p = t.parent;
            Entry<K,V> ch = t;
            while (p != null && ch == p.left) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

    // 返回“节点p的颜色”
    // 根据“红黑树的特性”可知:空节点颜色是黑色。
    private static <K,V> boolean colorOf(Entry<K,V> p) {
        return (p == null ? BLACK : p.color);
    }

    // 返回“节点p的父节点”
    private static <K,V> Entry<K,V> parentOf(Entry<K,V> p) {
        return (p == null ? null: p.parent);
    }

    // 设置“节点p的颜色为c”
    private static <K,V> void setColor(Entry<K,V> p, boolean c) {
        if (p != null)
        p.color = c;
    }

    // 设置“节点p的左孩子”
    private static <K,V> Entry<K,V> leftOf(Entry<K,V> p) {
        return (p == null) ? null: p.left;
    }

    // 设置“节点p的右孩子”
    private static <K,V> Entry<K,V> rightOf(Entry<K,V> p) {
        return (p == null) ? null: p.right;
    }
  • (3)结点的旋转:


    // 对节点p执行“左旋”操作
    private void rotateLeft(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> r = p.right;
            p.right = r.left;
            if (r.left != null)
                r.left.parent = p;
            r.parent = p.parent;
            if (p.parent == null)
                root = r;
            else if (p.parent.left == p)
                p.parent.left = r;
            else
                p.parent.right = r;
            r.left = p;
            p.parent = r;
        }
    }

    // 对节点p执行“右旋”操作
    private void rotateRight(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> l = p.left;
            p.left = l.right;
            if (l.right != null) l.right.parent = p;
            l.parent = p.parent;
            if (p.parent == null)
                root = l;
            else if (p.parent.right == p)
                p.parent.right = l;
            else p.parent.left = l;
            l.right = p;
            p.parent = l;
        }
    }

  • (4)结点的插入和删除
    // 插入之后的修正操作。
    // 目的是保证:红黑树插入节点之后,仍然是一颗红黑树
    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;

        while (x != null && x != root && x.parent.color == RED) {
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                Entry<K,V> 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 {
                    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;
    }

    // 删除“红黑树的节点p”
    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // If strictly internal, copy successor's element to p and then make p
        // point to successor.
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor (p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        } // p has 2 children

        // Start fixup at replacement node, if it exists.
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        if (replacement != null) {
            // Link replacement to parent
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;

            // Null out links so they are OK to use by fixAfterDeletion.
            p.left = p.right = p.parent = null;

            // Fix replacement
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) { // return if we are the only node.
            root = null;
        } else { //  No children. Use self as phantom replacement and unlink.
            if (p.color == BLACK)
                fixAfterDeletion(p);

            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

    // 删除之后的修正操作。
    // 目的是保证:红黑树删除节点之后,仍然是一颗红黑树
    private void fixAfterDeletion(Entry<K,V> x) {
        while (x != root && colorOf(x) == BLACK) {
            if (x == leftOf(parentOf(x))) {
                Entry<K,V> sib = rightOf(parentOf(x));

                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }

                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(rightOf(sib)) == BLACK) {
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;
                }
            } else { // symmetric
                Entry<K,V> 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);
    }


  • 红黑树的性质:

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

  • put方法


    // 将“key, value”添加到TreeMap中
    // 理解TreeMap的前提是掌握“红黑树”。
    // 若理解“红黑树中添加节点”的算法,则很容易理解put。
    public V put(K key, V value) {
        Entry<K,V> t = root;
        // 若红黑树为空,则插入根节点
        if (t == null) {
        // TBD:
        // 5045147: (coll) Adding null to an empty TreeSet should
        // throw NullPointerException
        //
        // compare(key, key); // type check
            root = new Entry<K,V>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        // 在二叉树(红黑树是特殊的二叉树)中,找到(key, value)的插入位置。
        // 红黑树是以key来进行排序的,所以这里以key来进行查找。
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        // 新建红黑树的节点(e)
        Entry<K,V> e = new Entry<K,V>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        // 红黑树插入节点后,不再是一颗红黑树;
        // 这里通过fixAfterInsertion的处理,来恢复红黑树的特性。
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

  • 代码分析

  • 1.校验根节点:校验根节点是否为空,若为空则根据传入的key-value的值创建一个新的节点,若根节点不为空则继续第二步
    2.寻找插入位置:由于TreeMap内部是红黑树实现的,在插入元素时,遍历左子树,或者右子树
    3.新建并恢复:在第二步中实际上是需要确定当前插入节点的位置,而这一步是实际的插入操作,而插入之后为啥还需要调用fixAfterInsertion方法,红黑树插入一个节点后可能会破坏红黑树的性质,因此需要使红黑树从新达到平衡,

  • HashMap:

  • TreeNode : HashMap的静态内部类,继承与LinkedHashMap.Entry<K,V>类,真正维护红黑树结构的方法都在其内部。

static final class TreeNode<K, V> extends LinkedHashMap.Entry<K, V>
    {
        TreeNode<K, V> parent; // red-black tree links
        TreeNode<K, V> left;
        TreeNode<K, V> right;
        TreeNode<K, V> prev; // needed to unlink next upon deletion
        boolean red;

        TreeNode(int hash, K key, V val, Node<K, V> next)
        {
            super(hash, key, val, next);
        }
        
        final void treeify(Node<K,V>[] tab)
        {
            // ......
        }
        
        static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root, TreeNode<K,V> x)
        {
            // ......
        }
        
        static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root, TreeNode<K,V> p)
        {
            // ......
        }
        
        static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root, TreeNode<K,V> p)
        {
            // ......
        }
        
        // ......其余方法省略
    }

  • treeifyBin :在HashMap中put方法时候,但数组中某个位置的链表长度大于某一值时,会调用treeifyBin方法将链表转化为红黑树。
final void treeifyBin(Node<K, V>[] tab, int hash)
    {
        int n, index;
        Node<K, V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            // resize()方法这里不过多介绍,感兴趣的可以去看上面的链接。
            resize();
        // 通过hash求出bucket的位置。
        else if ((e = tab[index = (n - 1) & hash]) != null)
        {
            TreeNode<K, V> hd = null, tl = null;
            do
            {
                // 将每个节点包装成TreeNode。
                TreeNode<K, V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else
                {
                    // 将所有TreeNode连接在一起此时只是链表结构。
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                // 对TreeNode链表进行树化。
                hd.treeify(tab);
        }
    }
  • treeify:将Treenode链转化成红黑树,第一次循环会将链表中的首节点作为红黑树的根,而后的循环会将链表中的的项通过比较hash值然后连接到相应树节点的左边或者右边,插入可能会破坏树的结构。
final void treeify(Node<K, V>[] tab)
    {
        TreeNode<K, V> root = null;
        // 以for循环的方式遍历刚才我们创建的链表。
        for (TreeNode<K, V> x = this, next; x != null; x = next)
        {
            // next向前推进。
            next = (TreeNode<K, V>) x.next;
            x.left = x.right = null;
            // 为树根节点赋值。
            if (root == null)
            {
                x.parent = null;
                x.red = false;
                root = x;
            } else
            {
                // x即为当前访问链表中的项。
                K k = x.key;
                int h = x.hash;
                Class<?> kc = null;
                // 此时红黑树已经有了根节点,上面获取了当前加入红黑树的项的key和hash值进入核心循环。
                // 这里从root开始,是以一个自顶向下的方式遍历添加。
                // for循环没有控制条件,由代码内break跳出循环。
                for (TreeNode<K, V> p = root;;)
                {
                    // dir:directory,比较添加项与当前树中访问节点的hash值判断加入项的路径,-1为左子树,+1为右子树。
                    // ph:parent hash。
                    int dir, ph;
                    K pk = p.key;
                    if ((ph = p.hash) > h)
                        dir = -1;
                    else if (ph < h)
                        dir = 1;
                    else if ((kc == null && (kc = comparableClassFor(k)) == null)
                            || (dir = compareComparables(kc, k, pk)) == 0)
                        dir = tieBreakOrder(k, pk);

                    // xp:x parent。
                    TreeNode<K, V> xp = p;
                    // 找到符合x添加条件的节点。
                    if ((p = (dir <= 0) ? p.left : p.right) == null)
                    {
                        x.parent = xp;
                        // 如果xp的hash值大于x的hash值,将x添加在xp的左边。
                        if (dir <= 0)
                            xp.left = x;
                        // 反之添加在xp的右边。
                        else
                            xp.right = x;
                        // 维护添加后红黑树的红黑结构。
                        root = balanceInsertion(root, x);
                        
                        // 跳出循环当前链表中的项成功的添加到了红黑树中。
                        break;
                    }
                }
            }
        }
        // Ensures that the given root is the first node of its bin,自己翻译一下。
        moveRootToFront(tab, root);
    }
  • balanceInsertion: 重新平衡二叉树
static <K, V> TreeNode<K, V> balanceInsertion(TreeNode<K, V> root, TreeNode<K, V> x)
    {
        // 正如开头所说,新加入树节点默认都是红色的,不会破坏树的结构。
        x.red = true;
        // 这些变量名不是作者随便定义的都是有意义的。
        // xp:x parent,代表x的父节点。
        // xpp:x parent parent,代表x的祖父节点
        // xppl:x parent parent left,代表x的祖父的左节点。
        // xppr:x parent parent right,代表x的祖父的右节点。
        for (TreeNode<K, V> xp, xpp, xppl, xppr;;)
        {
            // 如果x的父节点为null说明只有一个节点,该节点为根节点,根节点为黑色,red = false。
            if ((xp = x.parent) == null)
            {
                x.red = false;
                return x;
            } 
            // 进入else说明不是根节点。
            // 如果父节点是黑色,那么大吉大利(今晚吃鸡),红色的x节点可以直接添加到黑色节点后面,返回根就行了不需要任何多余的操作。
            // 如果父节点是红色的,但祖父节点为空的话也可以直接返回根此时父节点就是根节点,因为根必须是黑色的,添加在后面没有任何问题。
            else if (!xp.red || (xpp = xp.parent) == null)
                return root;
            
            // 一旦我们进入到这里就说明了两件是情
            // 1.x的父节点xp是红色的,这样就遇到两个红色节点相连的问题,所以必须经过旋转变换。
            // 2.x的祖父节点xpp不为空。
            
            // 判断如果父节点是否是祖父节点的左节点
            if (xp == (xppl = xpp.left))
            {
                // 父节点xp是祖父的左节点xppr
                // 判断祖父节点的右节点不为空并且是否是红色的
                // 此时xpp的左右节点都是红的,所以直接进行上面所说的第三种变换,将两个子节点变成黑色,将xpp变成红色,然后将红色节点x顺利的添加到了xp的后面。
                // 这里大家有疑问为什么将x = xpp?
                // 这是由于将xpp变成红色以后可能与xpp的父节点发生两个相连红色节点的冲突,这就又构成了第二种旋转变换,所以必须从底向上的进行变换,直到根。
                // 所以令x = xpp,然后进行下下一层循环,接着往上走。
                if ((xppr = xpp.right) != null && xppr.red)
                {
                    xppr.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                }
                // 进入到这个else里面说明。
                // 父节点xp是祖父的左节点xppr。
                // 祖父节点xpp的右节点xppr是黑色节点或者为空,默认规定空节点也是黑色的。
                // 下面要判断x是xp的左节点还是右节点。
                else
                {
                    // x是xp的右节点,此时的结构是:xpp左->xp右->x。这明显是第二中变换需要进行两次旋转,这里先进行一次旋转。
                    // 下面是第一次旋转。
                    if (x == xp.right)
                    {
                        root = rotateLeft(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    // 针对本身就是xpp左->xp左->x的结构或者由于上面的旋转造成的这种结构进行一次旋转。
                    if (xp != null)
                    {
                        xp.red = false;
                        if (xpp != null)
                        {
                            xpp.red = true;
                            root = rotateRight(root, xpp);
                        }
                    }
                }
            } 
            // 这里的分析方式和前面的相对称只不过全部在右测不再重复分析。
            else
            {
                if (xppl != null && xppl.red)
                {
                    xppl.red = false;
                    xp.red = false;
                    xpp.red = true;
                    x = xpp;
                } else
                {
                    if (x == xp.left)
                    {
                        root = rotateRight(root, x = xp);
                        xpp = (xp = x.parent) == null ? null : xp.parent;
                    }
                    if (xp != null)
                    {
                        xp.red = false;
                        if (xpp != null)
                        {
                            xpp.red = true;
                            root = rotateLeft(root, xpp);
                        }
                    }
                }
            }
        }
    }

返回目录


测试过程及遇到的问题

  • 问题1: 无任何记录。
  • 解决:

返回目录


分析总结

返回目录


代码托管


返回目录


参考资料

Intellj IDEA 简易教程

返回目录

posted @ 2018-11-11 19:06  ⊙ω⊙  阅读(433)  评论(0编辑  收藏  举报