算导实验三 红黑树插入

实验要求

编码实现红黑树的插入算法,使得插入后依旧保持红黑性质。(即:实现教材p178页的RB-INSERT,  RB_INSERT_FIXUP算法)

       节点属性:(也可自行定义, 不做强制要求)

TNode = {
    Color: red/black;
    Key: int;
    Left: TNode*;
    Right: TNode*;
    P: TNode*;
}

目录

1.实验内容

2.实验目的

3.算法设计思路

4.源码 + 注释

4.1 红黑树的结点结构

4.2插入算法

4.3左旋与右旋方法

5.算法正确性测试

6.实验过程中遇到的困难及收获

7.实验源代码


1.实验内容

掌握红黑树的插入过程。

2.实验目的

编码实现红黑树的插入算法,使得插入后依旧保持红黑性质。

3.算法设计思路

红黑树的插入过程主要分为两个阶段:插入结点和维护红黑树性质。首先按照二叉搜索树的插入规则将新节点插入到树中,并将该节点的颜色设为红色。接下来要进行红黑树性质的维护操作。

分析可知,在插入的过程中,只有可能破坏性质2和性质4。如果违反了性质2,则红色根节点一定是新增的结点z ,他是树中唯一的内部节点。因为z的父结点和子节点都是黑色的nil 结点,没有违反性质4。如果违反了性质4,则由于z 的子节点是黑色nil ,且该树在z 加入之前没有其他性质的违反,所以违反必然是因为zz.p 都是红色的,因此z.p.p 必然存在且为黑色。

基于以上分析,可以将需要调整的情况分为6种,其中case1~case3z.pz.p.p 的左孩子,case4~case6z.pz.p.p 的右孩子。

case1z 的叔叔结点y 为红色。这种情况发在z.py 都为红色时发生。因为z.p.p 是黑色的,所以将z.py 都设为黑色,一次解决zz.p 都是红色的问题,将z.p.p 设为红色以保持性质5,然后把z.p.p 作为新的z 来重复该过程,上溯到根节点时,将根节点设为黑色。

case2z 的叔叔结点y 是黑色且zz.p 的右孩子。这种情况发生时我们可以对z.p 做一个左旋来使该情况转化为case3。

case3z 的叔叔结点y 是黑色且zz.p 的左孩子。此情况下将z.pz.p.p 变色,然后对z.p.p 右旋。

case4~case6case1~case3 对称,因此不做分析。

(此节所述性质1-5及case1-6参考《算法导论》书上定义)

4.源码 + 注释

4.1 红黑树的结点结构

如下所示为红黑树的结点结构,实现过程中提供了对每个属性的gettersetter ,其中红色为false ,黑色为true

public class RBTNode<T>
{
    private T key;              //结点值
    private boolean color;      //结点的颜色
    private RBTNode<T> left;    //左孩子
    private RBTNode<T> right;   //右孩子
    private RBTNode<T> parent;  //父结点
}

//颜色设置
private static final boolean RED = false;
private static final boolean BLACK = true;

4.2插入算法

整个插入过程分为三部分1.insert(T key): 将关键字key 插入树中;2.insert(RBTNode node): 将node 插入树中;3.insertFixup(node): 维护红黑树。

1.insert(T key)

    //外部接口
    public void insert(T key)
    {
        RBTNode<T> node = new RBTNode<>(key, BLACK, null, null, null);
        insert(node);
    }

上述代码所示为调用插入方法时的接口,该方法接收结点的值,以该值建立一个结点node ,将node 作为参数传入内部接口。

2.insert(RBTNode node)

    //内部接口
    private void insert(RBTNode<T> node)
    {
        RBTNode<T> parent = null;   //当前结点的父结点
        RBTNode<T> x = root;
        while (x != null)
        {
            parent = x;
            if (node.getKey().compareTo(x.getKey()) < 0) x = x.getLeft();   //node.key < x.key, 向左走
            else x = x.getRight();
        }

        node.setParent(parent);
        if (parent == null) root = node;    //空树
        else
        {
            if (node.getKey().compareTo(parent.getKey()) < 0) parent.setLeft(node); //node是parent的左孩子
            else parent.setRight(node);
        }
        node.setLeft(null);
        node.setRight(null);
        node.setColor(RED);
        insertFixup(node);
    }

该方法首先按照二叉搜索树的规则将结点插入树中,parent 始终指向插入结点的父结点。当while 循环结束后,若parent 为空,说明当前树为空树,于是当前节点作为根节点插入树中。插入后将其左孩子和右孩子设为nil ,并将其颜色设为RED ,然后调用insertFixup 方法。 

3.insertFixup(node)

根据3节分析可知,若违反了性质2,则直接将根结点涂黑即可。若违反了性质4,则可分为6种情况。当z 的父结点不为nil 且为RED 时,即违反了性质4,此时需要调整。设置parentgrandparentuncle 结点来方便表示。

//case 1,2,3
if (parent == grandparent.getLeft())    //case 1,2,3:父结点是祖父结点的left
{
    RBTNode<T> uncle = grandparent.getRight();  //叔叔结点
    if (uncle != null && uncle.getColor() == RED)    //case 1:叔叔为RED
    {
        uncle.setColor(BLACK);
        parent.setColor(BLACK);
        grandparent.setColor(RED);
        node = grandparent;
        System.out.print("1 ");
    }
    else    //case 2,3:叔叔为BLACK
    {
        if (node == parent.getRight())    //case 2:node是父结点的right
        {
            RBTNode<T> tmp;
            leftRotate(parent);
            tmp = parent;
            parent = node;
            node = tmp;
            System.out.print("2 ");
        }
        //case 3:node是父结点的left
        parent.setColor(BLACK);
        grandparent.setColor(RED);
        rightRotate(grandparent);
        System.out.print("3 ");
    }
}

上述代码所示为case1,2,3 的处理方法。若uncleRED ,则将uncleparent 设为BLACK ,并将grandparent 设为RED ,然后将node 上溯到grandparent ,继续迭代。若uncleBLACK 并且当前节点为parent 的右孩子,则对parent 执行左旋操作,此时nodeparent 的左孩子,将parentgrandparent 变色,并对grandparent 右旋,即可完成修复过程。case4,5,6 的处理过程与上述过程对称。

4.3左旋与右旋方法

    //左旋
    private void leftRotate(RBTNode<T> x)
    {
        //Step1:y指向x的右孩子
        RBTNode<T> y = x.getRight();

        //Step2:y.left连接到x.right
        x.setRight(y.getLeft());
        if (y.getLeft() != null) y.getLeft().setParent(x);

        //Step3:x父结点的left或right指向y
        y.setParent(x.getParent());
        if (x.getParent() == null) root = y;    // 如果 “x的父亲” 是空节点,则将y设为根节点
        else if (x == x.getParent().getLeft()) x.getParent().setLeft(y);    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
        else x.getParent().setRight(y); // 如果 x是它父节点的右孩子,则将y设为“x的父节点的右孩子”

        //Step4:x连接到y.left
        y.setLeft(x);
        x.setParent(y);
    }

上述代码所示为左旋算法。首先将y 指向x 的右孩子,然后将y 的左子树连接到x 的右孩子位置,接下来将y 连接到xparent 孩子位置,最后将x 连接到y 的左孩子,这样就完成了对x 的左旋。右旋与其对称不再赘述。

5.算法正确性测试

将insert.txt的数据依次插入红黑树中并打印,得到LOT.txt文件如下所示。

使用可视化模型建立的红黑树如下,对比可知二者相同,可验证算法的正确性。

6.实验过程中遇到的困难及收获

通过本次实验,细致的了解了红黑树结点插入的过程,对红黑树的性质有了更深的理解。同时也看了JDK源码中的红黑树的实现,还需加强学习。

7.实验源代码

RBTNode.java

/*
 * @Title:红黑树结点类
 * @Package
 * @Description:
 * @author yangf257
 * @date 2021/11/6  15:09
 */

public class RBTNode<T>
{
    private T key;              //结点值
    private boolean color;      //结点的颜色
    private RBTNode<T> left;    //左孩子
    private RBTNode<T> right;   //右孩子
    private RBTNode<T> parent;  //父结点

    public T getKey()
    {
        return key;
    }

    public boolean getColor()
    {
        return color;
    }

    public void setColor(boolean color)
    {
        this.color = color;
    }

    public RBTNode<T> getLeft()
    {
        return left;
    }

    public void setLeft(RBTNode<T> left)
    {
        this.left = left;
    }

    public RBTNode<T> getRight()
    {
        return right;
    }

    public void setRight(RBTNode<T> right)
    {
        this.right = right;
    }

    public RBTNode<T> getParent()
    {
        return parent;
    }

    public void setParent(RBTNode<T> parent)
    {
        this.parent = parent;
    }

    public RBTNode(T key, boolean color, RBTNode<T> left, RBTNode<T> right, RBTNode<T> parent)
    {
        this.color = color;
        this.key = key;
        this.left = left;
        this.right = right;
        this.parent = parent;
    }

}

RBTree.java

/*
 * @Title:红黑树类
 * @Package
 * @Description:红黑树的插入
 * @author yangf257
 * @date 2021/11/6  15:04
 */

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class RBTree<T extends Comparable<T>>
{
    private static final boolean RED = false;
    private static final boolean BLACK = true;

    private RBTNode<T> root;    //根结点

    public RBTree()
    {
    }

    public RBTNode<T> getRoot()
    {
        return root;
    }
    /*
     * 插入算法包括三部分
     * 1.insert(T key):将关键字key插入树中;
     * 2.insert(RBTNode node):将node插入树中;
     * 3.insertFixup(node):维护红黑树;
     */

    //外部接口
    public void insert(T key)
    {
        RBTNode<T> node = new RBTNode<>(key, BLACK, null, null, null);
        insert(node);
    }

    //内部接口
    private void insert(RBTNode<T> node)
    {
        RBTNode<T> parent = null;   //当前结点的父结点
        RBTNode<T> x = root;
        while (x != null)
        {
            parent = x;
            if (node.getKey().compareTo(x.getKey()) < 0) x = x.getLeft();   //node.key < x.key, 向左走
            else x = x.getRight();
        }

        node.setParent(parent);
        if (parent == null) root = node;    //空树
        else
        {
            if (node.getKey().compareTo(parent.getKey()) < 0) parent.setLeft(node); //node是parent的左孩子
            else parent.setRight(node);
        }
        node.setLeft(null);
        node.setRight(null);
        node.setColor(RED);
        insertFixup(node);
    }

    //维护
    private void insertFixup(RBTNode<T> node)
    {
        RBTNode<T> parent, grandparent;
        while ((parent = node.getParent()) != null && parent.getColor() == RED)
        {
            grandparent = parent.getParent();
            //case 1,2,3
            if (parent == grandparent.getLeft())    //case 1,2,3:父结点是祖父结点的left
            {
                RBTNode<T> uncle = grandparent.getRight();  //叔叔结点
                if (uncle != null && uncle.getColor() == RED)    //case 1:叔叔为RED
                {
                    uncle.setColor(BLACK);
                    parent.setColor(BLACK);
                    grandparent.setColor(RED);
                    node = grandparent;
                    System.out.print("1 ");
                }
                else    //case 2,3:叔叔为BLACK
                {
                    if (node == parent.getRight())    //case 2:node是父结点的right
                    {
                        RBTNode<T> tmp;
                        leftRotate(parent);
                        tmp = parent;
                        parent = node;
                        node = tmp;
                        System.out.print("2 ");
                    }
                    //case 3:node是父结点的left
                    parent.setColor(BLACK);
                    grandparent.setColor(RED);
                    rightRotate(grandparent);
                    System.out.print("3 ");
                }
            }
            //case 4,5,6:父结点是祖父结点的right
            else
            {
                RBTNode<T> uncle = grandparent.getLeft();
                if (uncle != null && uncle.getColor() == RED)   //case 4:叔叔为RED
                {
                    uncle.setColor(BLACK);
                    parent.setColor(BLACK);
                    grandparent.setColor(RED);
                    node = grandparent;
                    System.out.print("4 ");
                }
                else    //case 5,6:叔叔为BLACK
                {
                    if (node == parent.getLeft())    //case 5:node是父结点的left
                    {
                        RBTNode<T> temp;
                        rightRotate(parent);
                        temp = node;
                        node = parent;
                        parent = temp;
                        System.out.print("5 ");
                    }
                    //case 6:node是父结点的right
                    parent.setColor(BLACK);
                    grandparent.setColor(RED);
                    leftRotate(grandparent);
                    System.out.print("6 ");
                }
            }
        }
        root.setColor(BLACK);
        System.out.println();
    }

    /*
     * 辅助方法
     * 1.leftRotate(RBTNode x):左旋
     * 2.rightRotate(RBTNode y):右旋
     * 3.print():打印
     * 4.levelOrder()
     */
    //左旋
    private void leftRotate(RBTNode<T> x)
    {
        //Step1:y指向x的右孩子
        RBTNode<T> y = x.getRight();

        //Step2:y.left连接到x.right
        x.setRight(y.getLeft());
        if (y.getLeft() != null) y.getLeft().setParent(x);

        //Step3:x父结点的left或right指向y
        y.setParent(x.getParent());
        if (x.getParent() == null) root = y;    // 如果 “x的父亲” 是空节点,则将y设为根节点
        else if (x == x.getParent().getLeft()) x.getParent().setLeft(y);    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
        else x.getParent().setRight(y); // 如果 x是它父节点的右孩子,则将y设为“x的父节点的右孩子”

        //Step4:x连接到y.left
        y.setLeft(x);
        x.setParent(y);
    }

    //右旋
    private void rightRotate(RBTNode<T> y)
    {
        //Step1:x指向y的左孩子
        RBTNode<T> x = y.getLeft();

        //Step2:x.right连接到y.left
        y.setLeft(x.getRight());
        if (x.getRight() != null) x.getRight().setParent(y);

        //Step3:y父结点的left或right指向x
        x.setParent(y.getParent());
        if (y.getParent() == null) root = x;    // 如果 “y的父亲” 是空节点,则将x设为根节点
        else if (y == y.getParent().getRight()) y.getParent().setRight(x);  // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
        else y.getParent().setLeft(x);   // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”

        //Step4:y.left连接到x
        x.setRight(y);
        y.setParent(x);
    }

    //打印
    public void print(RBTNode<T> tree)
    {
        List<RBTNode<T>> level = levelOrder(tree);
        try
        {
            File writeName = new File("LOT.txt"); // 相对路径,如果没有则要建立一个新的LOT.txt文件
            writeName.createNewFile(); // 创建新文件,有同名的文件的话直接覆盖
            try (FileWriter writer = new FileWriter(writeName); BufferedWriter out = new BufferedWriter(writer))
            {
                for (int i = 0; i < level.size(); i++)
                {
                    String str = level.get(i).getKey() + ", " + (level.get(i).getColor() ? "BLACK" : "RED");
                    out.write(str + "\r\n");
                    out.flush();
                }
            }
        } catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    //层序遍历
    private List<RBTNode<T>> levelOrder(RBTNode<T> root)
    {
        List<RBTNode<T>> level = new ArrayList<>();
        if (root == null) return null;

        Queue<RBTNode<T>> queue = new LinkedList<>();
        queue.add(root);
        RBTNode<T> curr;
        while (!queue.isEmpty())
        {
            curr = queue.poll();
            level.add(curr);
            if (curr.getLeft() != null) queue.add(curr.getLeft());
            if (curr.getRight() != null) queue.add(curr.getRight());
        }

        return level;
    }
}

TestMethod.java

/*
 * @Title:
 * @Package
 * @Description:
 * @author yangf257
 * @date 2021/11/6  17:16
 */


import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class TestMethod
{
    public static void main(String[] args)
    {
        List<Integer> array = input();
        RBTree<Integer> rbTree = new RBTree<>();
        for (int i = 0; i < array.size(); i++)
        {
            rbTree.insert(array.get(i));
            System.out.print("插入第" + (i + 1) + "个结点:");
        }
        rbTree.print(rbTree.getRoot());
    }

    private static List<Integer> input()
    {
        String pathname = "insert.txt";
        String line = null;
        try (FileReader reader = new FileReader(pathname); BufferedReader br = new BufferedReader(reader))
        {
            int count = 0;
            while ((line = br.readLine()) != null)
            {
                count++;
                if (count == 2) break;
            }
        } catch (IOException e)
        {
            e.printStackTrace();
        }

        List<Integer> array = new ArrayList<>();
        int num = 0;
        for (int i = 0; i < line.length(); i++)
        {
            if (line.charAt(i) != ' ')
            {
                if (num != 0) num *= 10;
                num += line.charAt(i) - '0';
            }
            else
            {
                array.add(num);
                System.out.println(num);
                num = 0;
            }
        }
        array.add(num);
        return array;
    }
}

insert.txt

20
12 1 9 2 0 11 7 19 4 15 18 5 14 13 10 16 6 3 8 17

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值