Tree
子树是不相交的,除了根结点以外,每个结点有且只有一个父结点,一个N个结点的树只有N-1条边
包含的基本概念
结点的度
结点的子树个数树的度
树中结点的度的最大值叶结点
度为0的结点父结点
子结点
二叉树
度为2的树(树的所有结点中最大的度),子树有左右顺序之分
哈夫曼树HuffmanTree
编码及实现
构造一颗二叉树,该树的带权路径长度达到最小,称为最优二叉树,也称为哈夫曼树(Huffman Tree)
总结 :所有结点的权重乘以路径长度之和最小
代码举例:
package com.tangkun.tree;
import java.util.ArrayList;
import java.util.List;
/**
* 哈夫曼树:构造一颗二叉树,该树的带权路径长度达到最小,称为最优二叉树,也称为哈夫曼树(Huffman Tree)
*/
public class HuffmanTree {
/**
* 结点
*/
public static class Node<T> {
public T data;
public int weight;
public Node leftChild;
public Node rightChild;
public Node(T data, int weight) {
super();
this.data = data;
this.weight = weight;
}
@Override
public String toString() {
return "Node{" +
"weight=" + weight +
", data=" + data +
'}';
}
}
public static void main(String[] args) {
List<Node> nodeList = new ArrayList<>();
nodeList.add(new Node("a", 10));
nodeList.add(new Node("b", 15));
nodeList.add(new Node("c", 12));
nodeList.add(new Node("d", 3));
nodeList.add(new Node("e", 4));
nodeList.add(new Node("f", 13));
nodeList.add(new Node("g", 1));
//构建哈夫曼树,得到根结点
Node root = HuffmanTree.createHuffmanTree(nodeList);
//通过根结点遍历哈夫曼树
printTree(root);
}
/**
* 通过集合中的权值将集合排序,冒泡排序
*/
public static void sort(List<Node> nodeList) {
//TODO 只有一个元素的时候即不需要排序了
if (nodeList.size() <= 1)
return;
System.out.println("打印通过冒泡排序前的集合:" + nodeList.toString());
for (int i = 0; i < nodeList.size(); i++) {
for (int j = 0; j < nodeList.size() - 1 - i; j++) {
if (nodeList.get(j).weight > nodeList.get(j + 1).weight) {
//替换结点
Node temp = nodeList.get(j);
nodeList.set(j, nodeList.get(j + 1));
nodeList.set(j + 1, temp);
}
}
}
System.out.println("打印通过冒泡排序后的集合:" + nodeList.toString());
}
/**
* 构建哈夫曼树根,返回根结点
*/
public static Node createHuffmanTree(List<Node> nodeList) {
// 实现思路:
// 通过遍历集合,将集合中两个权值最小的结点构建出一个parent父结点,父结点的权值就是这两个子结点的权值之和;
// 同时将父结点的左右子树分别指向这两个权值最小的结点,然后将这两个权值最小的结点从集合中移除,将新构建的parent父结点添加到集合中去
while (nodeList.size() > 1) {
//TODO 注意这里的排序方法位置哦,每次添加完构造的父结点后需要重新排序,否则构建出来的哈夫曼树就有问题
//通过集合中的权值将集合排序
sort(nodeList);
//获取权值最小的结点
Node left = nodeList.get(0);
//获取权值第二小的结点
Node right = nodeList.get(1);
//构建父结点
Node parent = new Node(null, left.weight + right.weight);
//父结点的左右指针分别指向这两个权值最小的结点
parent.leftChild = left;
parent.rightChild = right;
//移除左结点
nodeList.remove(0);
//移除右结点
nodeList.remove(0);
//添加新构建的parent父结点
nodeList.add(parent);
}
//返回这个构建好树的根结点
return nodeList.get(0);
}
/**
* 通过根结点遍历哈夫曼树
*/
public static void printTree(Node node) {
//TODO 打印父结点
System.out.println(node.toString());
//递归打印左子树
if (node.leftChild != null) {
//TODO 打印日志
System.out.print("left ");
printTree(node.leftChild);
}
//递归打印右子树
if (node.rightChild != null) {
//TODO 打印日志
System.out.print("right ");
printTree(node.rightChild);
}
}
}
二叉排序树(BST树,又名:二叉查找树、二叉搜索树)
特点:有序性
右结点值 > 父结点的值 > 左结点的值
二叉平衡树(AVL树)
也是一个二叉排序树,但是左右两个子树的高度差值不能大于1,并且左右两个字树都是一颗二叉平衡树
作用
- 使得树的高度最低,因为树的查找效率取决于树的高度
插入
- 平衡没有被打破,不用调整
- 平衡被打破,需要调整
调整规则
- 插入结点在失衡结点的左子树的左边,只需要经过一次左旋(
LL
)即可达到平衡- 插入结点在失衡结点的右子树的右边,只需要经过一次右旋即可(
RR
)达到平衡- 插入结点在失衡结点的左子树的右边,失衡结点的左子树先做
RR
旋转,失衡结点再做LL
旋转也可以达到平衡- 插入结点在失衡结点的右子树的左边,失衡结点的右子树先做
LL
旋转,失衡结点再做RR
旋转也可以达到平衡
代码举例,这个构建平衡二叉树理解起来比较困难:
- 首先需要先通过左右子树高度来判断是否调整平衡二叉树
- 其次需要通过判断结点插入位置(
LL、RR、LR、RL
) - 然后根据插入位置采用相对应的规则(
LL、RR、RL、LR
)来进行平衡操作
(这里需要注意:RL和LR操作的参照结点不同: 第1
次是对原根结点的左子树(root.left
)或右子树(root.right
)进行旋转,将旋转后的值赋值给左子树(root.left
)或右子树(root.right
);然后第2
次才是对原根结点(root
)进行旋转) - 最后需要注意
LL
和RR
的旋转规则
(这里需要注意:LL
和RR操作,首先根据原根结点(失衡结点
)找到新的根结点,接着将新的根结点的右子树
或左子树,作为原根结点(失衡结点
)的左子树
或右子树,然后将更改过的原根结点赋值给新根结点的右子树
或左子树,最后重新计算原根节点(失衡结点
)和新根结点的高度,返回新根结点)
package com.tangkun.tree;
/**
* 二叉平衡树
* 也是一个二叉排序树,但是左右两个子树的高度差值不能大于1,并且左右两个字树都是一颗二叉平衡树
* <p>
* **插入**
* 1. 平衡没有被打破,不用调整
* 2. 平衡被打破,需要调整
* 调整规则:
* 插入结点在失衡结点的左子树的左边,只需要经过一次左旋(`LL`)即可达到平衡
* 插入结点在失衡结点的右子树的右边,只需要经过一次右旋即可(`RR`)达到平衡
* 插入结点在失衡结点的左子树的右边,失衡结点的左子树先做`RR`旋转,失衡结点再做`LL`旋转也可以达到平衡
* 插入结点在失衡结点的右子树的左边,失衡结点的右子树先做`LL`旋转,失衡结点再做`RR`旋转也可以达到平衡
*/
public class AVLTree {
/**
* 结点
*/
public static class Node {
public int data;//数据
public int height;//记录该结点所在的高度
public Node leftChild;
public Node rightChild;
public Node(int data) {
super();
this.data = data;
}
@Override
public String toString() {
return "Node{" +
"data=" + data +
",height=" + height +
'}';
}
}
/**
* 获取结点的高度
*/
public static int getHeight(Node node) {
return node == null ? 0 : node.height;//空树的高度是-1
}
public static void main(String[] args) {
Node root = null;
root = insert(root, 30);
root = insert(root, 20);
root = insert(root, 40);
root = insert(root, 10);
root = insert(root, 25);
//插入结点在失衡结点的左子树的左边
root = insert(root, 5);
//打印树,按照先打印左子树,再打印右子树的方式
printTree(root);
}
/**
* AVL树的插入方法
*
* @param root 根结点
* @param data 插入结点的数据
*/
public static Node insert(Node root, int data) {
if (root == null) {
root = new Node(data);
return root;
}
if (data <= root.data) {//插入到左子树
//递归遍历左子树,插入新结点
root.leftChild = insert(root.leftChild, data);
//平衡二叉树失衡判断
if (getHeight(root.leftChild) - getHeight(root.rightChild) > 1) {
//插入到左子树的左边
if (data <= root.leftChild.data) {
root = LLRotate(root);
} else {//插入到左子树的右边
root = LRRotate(root);
}
}
} else {//插入到右子树
//递归遍历右子树,插入新结点
root.rightChild = insert(root.rightChild, data);
//平衡二叉树失衡判断
if (getHeight(root.rightChild) - getHeight(root.leftChild) > 1) {
//插入到右子树的左边
if (data <= root.rightChild.data) {
root = RLRotate(root);
} else {//插入到右子树的右边
root = RRRotate(root);
}
}
}
// 重新调整root结点的高度
root.height = Math.max(getHeight(root.leftChild), getHeight(root.rightChild)) + 1;
return root;
}
/**
* 插入到左子树的右边
* 插入结点在失衡结点的左子树的右边,失衡结点的左子树先做`RR`旋转,失衡结点再做`LL`旋转也可以达到平衡
*/
public static Node LRRotate(Node root) {
System.out.println("LR旋转");
root.leftChild = RRRotate(root.leftChild);//将失衡结点的左子树右旋RR
return LLRotate(root);//再将失衡结点进行LL平衡旋转,并返回新结点代替原失衡结点
}
/**
* 插入到右子树的左边
* 插入结点在失衡结点的右子树的左边,失衡结点的右子树先做`LL`旋转,失衡结点再做`RR`旋转也可以达到平衡
*/
public static Node RLRotate(Node root) {
System.out.println("RL旋转");
root.rightChild = LLRotate(root.rightChild);
return RRRotate(root);
}
/**
* 插入到左子树的左边
* 插入结点在失衡结点的左子树的左边,只需要经过一次左旋(`LL`)即可达到平衡
* 左旋示意图(对结点20进行左旋)
* 30 20
* / \ / \
* 20 40 10 30
* / \ --LL旋转- / / \
* 10 25 5 25 40
* /
* 5
*/
public static Node LLRotate(Node root) {//30为失衡点
System.out.println("LL旋转");
Node lsubtree = root.leftChild;//失衡点的左子树的根结点20作为新的根结点
root.leftChild = lsubtree.rightChild;//将新结点的右子树25成为失衡点30的左子树
lsubtree.rightChild = root;//将失衡点30作为新的根结点的右子树
//重新设置失衡点30和新的根结点20的高度
root.height = Math.max(getHeight(root.leftChild), getHeight(root.rightChild)) + 1;
lsubtree.height = Math.max(getHeight(lsubtree.leftChild), root.height) + 1;
return lsubtree;//将新的根结点返回
}
/**
* 插入到右子树的右边
* 插入结点在失衡结点的右子树的右边,只需要经过一次右旋即可(`RR`)达到平衡
* 右旋示意图(对结点30进行左旋)
* 20 30
* / \ / \
* 10 30 20 40
* / \ --RR旋转- / \ \
* 25 40 10 25 50
* \
* 50
*/
public static Node RRRotate(Node root) {//20为失衡点
System.out.println("RR旋转");
Node rsubtree = root.rightChild;//失衡点的右子树的根结点30作为新的根结点
root.rightChild = rsubtree.leftChild;//将新结点的左子树25成为失衡点20的右子树
rsubtree.leftChild = root;//将失衡点20作为新的根结点的左子树
//重新设置失衡点20和新的根结点30的高度
root.height = Math.max(getHeight(root.leftChild), getHeight(root.rightChild)) + 1;
rsubtree.height = Math.max(root.height, getHeight(rsubtree.rightChild)) + 1;
return rsubtree;//将新的根结点返回
}
/**
* 通过根结点遍历哈夫曼树
*/
public static void printTree(Node node) {
//TODO 打印父结点
System.out.println(node.toString());
//递归打印左子树
if (node.leftChild != null) {
//TODO 打印日志
System.out.print("left ");
printTree(node.leftChild);
}
//递归打印右子树
if (node.rightChild != null) {
//TODO 打印日志
System.out.print("right ");
printTree(node.rightChild);
}
}
}
红黑树(Red-Black Tree
)
是一种特殊的二叉查找树
特点
- 结点要么是红色要么是黑色
- 根结点必须是黑色
- 所有的NULL结点称为叶子结点,颜色必须是黑色
- 所有的红色结点,他的子结点只能是黑色
- 任意结点到他的叶子结点,路径上包含的黑色结点数量都相同
- 从根结点到叶子结点的最长路径不能超过最短路径的
2
倍
插入
- 插入的结点必须是红色的;因为插入黑色结点,就不满足任意结点到其叶子结点路径上包含的黑色结点数量相同
变色
- 根结点是黑色
- 所有红色结点的子结点必须是黑色
- 从任意结点到其叶子结点路径上包含相同数量的黑色结点
插入结点调整规则(条件:插入结点的父结点是红色,叔父结点是黑色
)
- 插入左子树的左边,执行一次右旋(
以插入结点父节点为轴
) - 插入右子树的右边,执行一次左旋(
以插入结点父节点为轴
) - 插入左子树的右边,第一次执行左旋(
以插入结点父节点为轴
),第二次执行右旋(以插入结点祖父节点为轴
) - 插入右子树的左边,第一次执行右旋(
以插入结点父节点为轴
),第二次执行左旋(以插入结点祖父节点为轴
)
还有红黑树的删除操作也没有去研究?
,代码如下:
package com.tangkun.tree;
/**
* 红黑树
*/
public class RBTree2<T extends Comparable<T>> {
private Node<T> mRoot;//根结点
public static final boolean RED = false;//红色是false
public static final boolean BLACK = true;//黑色是true
public RBTree2() {
mRoot = null;
}
private boolean getColor(Node node) {
if (node != null) {
return node.color;
}
return BLACK;
}
private Node getParent(Node node) {
if (node != null) {
return node.parent;
}
return null;
}
private boolean isRed(Node node) {
if (node != null && node.color == RED) {
return true;
}
return false;
}
private boolean isBlack(Node node) {
if (!isRed(node))
return true;
else
return false;
}
private void setColor(Node node, boolean color) {
if (node != null)
node.color = color;
}
private void setParent(Node node, Node parent) {
if (node != null)
node.parent = parent;
}
private void setRed(Node node) {
if (node != null)
node.color = RED;
}
private void setBlack(Node node) {
if (node != null)
node.color = BLACK;
}
public Node<T> search(T data) {
return search(mRoot, data);
}
/**
* (递归实现)查找红黑树中值为data的结点
*/
public Node<T> search(Node<T> node, T data) {
if (node == null)
return null;
int cmp = data.compareTo(node.data);
if (cmp < 0) {//在左子树
search(node.leftChild, data);
} else if (cmp > 0) {
search(node.rightChild, data);
} else {
return node;
}
return null;
}
public Node<T> iterativeSearch(T data) {
return iterativeSearch(mRoot, data);
}
/**
* (迭代器实现) 查找红黑树中值为data的结点
*/
public Node<T> iterativeSearch(Node<T> node, T data) {
while (node != null) {
int cmp = data.compareTo(node.data);
if (cmp < 0) {
node = node.leftChild;
} else if (cmp > 0) {
node = node.rightChild;
} else {
return node;
}
}
return node;
}
/**
* 查询红黑树中值最小的那个结点,也就是最左边那个结点
*/
public Node<T> minimum(Node<T> node) {
if (node == null)
return null;
while (node.leftChild != null) {
node = node.leftChild;
}
return node;
}
/**
* 查询最小结点的值
*/
public T minimum() {
Node<T> node = minimum(mRoot);
if (node != null)
return node.data;
return null;
}
/**
* 查询红黑树中值最大的那个结点,也就是最右边那个结点
*/
public Node<T> maximum(Node<T> node) {
if (node == null)
return null;
while (node.rightChild != null) {
node = node.rightChild;
}
return node;
}
/**
* 查询最大结点的值
*/
public T maximum() {
Node<T> node = maximum(mRoot);
if (node != null)
return node.data;
return null;
}
/**
* 插入一个新的元素
*/
public void insert(T data) {
Node<T> node = new Node<T>(data, BLACK, null, null, null);
if (node != null)
insert(node);
}
/**
* 往红色树中添加一个结点
*/
public void insert(Node<T> node) {
//大小比价
int cmp;
//上一个结点
Node<T> prev = null;
//当前结点
Node<T> curr = this.mRoot;
// 1. 将红黑树当作一颗二叉查找树,将节点添加到二叉查找树中。
while (curr != null) {//迭代查询,插入新结点
prev = curr;
cmp = node.data.compareTo(curr.data);
if (cmp < 0) {
curr = curr.leftChild;
} else if (cmp > 0) {
curr = curr.rightChild;
} else {
//TODO 我这里还会增加了相等的判断,相等就退出不在插入,不知道有没有问题; 没有问题,更健壮了!!!
System.out.println("会进入到等于这个逻辑里面来吗?1");
return;
}
}
//curr就是当前要插入结点的位置,而prev就是插入结点的父结点
if (node == null)
System.out.println("结点为空 node == null");
node.parent = prev;
if (prev != null) {//插入到非根结点
cmp = node.data.compareTo(prev.data);
if (cmp < 0) {
prev.leftChild = node;
} else if (cmp > 0) {
prev.rightChild = node;
} else {
//TODO 我这里还会增加了相等的判断,相等就退出不在插入,不知道有没有问题; 没有问题,更健壮了!!!
System.out.println("会进入到等于这个逻辑里面来吗?2");
return;
}
} else {//插入到根结点
mRoot = node;
}
// 2. 设置节点的颜色为红色
//将插入结点的颜色更改为红色
setRed(node);
// 3. 将它重新修正为一颗二叉查找树
insertFixUp(node);
}
/**
* 红黑树插入修正函数
* <p>
* 往红黑树中插入结点导致红黑树失衡,再调用该函数(此时这颗树不在满足红黑树条件,也就插入新结点的这颗树不再是红黑树);
* 目的是将这颗树重新构造成一颗红黑色
*
* @param node 插入的结点
*/
public void insertFixUp(Node<T> node) {
//父结点
Node<T> parent = null;
//祖父结点
Node<T> grandParent;
// 若“父节点存在,并且父节点的颜色是红色”
while ((parent = getParent(node)) != null && isRed(node.parent)) {
grandParent = getParent(parent);
//祖父结点非空判断
if (grandParent == null)
return;
//若“父节点”是“祖父节点的左孩子”
if (parent == grandParent.leftChild) {
//叔父结点
Node<T> uncle = grandParent.rightChild;
if (uncle != null && isRed(uncle)) {// 情况2:叔叔节点是红色
//插入结点是红色,将父结点和叔父结点设置成黑色,祖父结点设置成红色
setBlack(parent);
setBlack(uncle);
setRed(grandParent);
//TODO 这一句将祖父结点赋值给当前结点是干嘛?是为了将下一次while循环迭代重新赋值吗?将祖父结点作为下一次遍历处理的结点?
//TODO 原因:是因为这一次while遍历下来的结果是,当前结点是红色,父结点被重新赋值成黑色,祖父结点被赋值成红色;
// 此时需要进入下一次while循环,将红色祖父结点当作插入的结点,根据其父结点和祖父结点颜色,看进行下面那种处理情况
node = grandParent;
//TODO 注意!!!注意这个continue的使用,如果叔父结点uncle是红色,代码逻辑就不会往下走了哦;如果叔父结点uncle是黑色,或者叔父结点为null,才会跳过这个if逻辑
continue;
}
// 情况5:叔叔是黑色,且当前节点是右孩子(两次旋转,先左后右) 左子树的右边,进行LR旋转
//TODO 这里的if判断中不需要判断isBlack(uncle)吗? 原来是上continue关键字的妙用
if (parent.rightChild == node) {
Node<T> temp = null;
//左旋
leftRotate(parent);
//调换新节点和其父节点的角色
temp = parent;
parent = node;
node = temp;
}
//情况3:叔叔是黑色,且当前节点是左孩子。(一次右旋转)
//TODO 这里不用添加if逻辑判断,里面判断条件是isBlack(uncle)吗?原来是上continue关键字的妙用
setBlack(parent);
setRed(grandParent);
rightRotate(grandParent);
} else {
//叔父结点
Node<T> uncle = grandParent.leftChild;
if (uncle != null && isRed(uncle)) {// 情况2:叔叔节点是红色
//插入结点是红色,将父结点和叔父结点设置成黑色,祖父结点设置成红色
setBlack(parent);
setBlack(uncle);
setRed(grandParent);
//TODO 这一句将祖父结点赋值给当前结点是干嘛?是为了将下一次while循环迭代重新赋值吗?将祖父结点作为下一次遍历处理的结点?
node = grandParent;
continue;
}
// 情况6:叔叔是黑色,且当前节点是左孩子(两次旋转,先右后左) 右子树的左边,进行RL旋转
if (parent.leftChild == node) {
Node<T> temp = null;
rightRotate(grandParent);
temp = parent;
parent = node;
node = temp;
}
// 情况4:叔叔是黑色,且当前节点是右孩子。(一次左旋转)
setBlack(parent);
setRed(grandParent);
leftRotate(grandParent);
}
}
//将根结点设置成黑色
setBlack(mRoot);
}
/**
* 对红黑树的节点进行左旋转
* <p>
* 左旋示意图(对节点x进行左旋):
* 13 17
* / \ / \
* nul 17 13 27
* / \ --(左旋)-. / \ / \
* nul 27 nul nul nul nul
* / \
* nul nul
*
* @param oldRoot 原结点
*/
public void leftRotate(Node<T> oldRoot) {
// 设置新的根结点为原根结点的有子结点
Node<T> newRoot = oldRoot.rightChild;
//将新根结点的左子结点赋值给原根结点的右子结点
//这是一个双向处理的过程,除了将子结点赋值给父结点外,还需要将子结点的parent指向父结点
oldRoot.rightChild = newRoot.leftChild;
if (newRoot.leftChild != null)
newRoot.leftChild.parent = oldRoot;
//将原根结点的父结点赋值给新根结点的父结点
newRoot.parent = oldRoot.parent;
//如果原根结点的父结点为空,那么新根结点成为整棵树的根结点
if (oldRoot.parent == null) {
this.mRoot = newRoot;
} else {
//如果原根结点的父结点非空
//若原根结点在其父结点的左孩子结点上,就将新根结点赋值给原根结点的父结点的左孩子结点;
if (oldRoot.parent.leftChild == oldRoot)
oldRoot.parent.leftChild = newRoot;
else //若原根结点在其父结点的右孩子结点上,就将新根结点赋值给原根结点的父结点的有孩子结点;
oldRoot.parent.rightChild = newRoot;
}
//将修改过的原根结点赋值给新根结点的左孩子结点上
newRoot.leftChild = oldRoot;
//同时将原根结点的父结点指向新的根结点
oldRoot.parent = newRoot;
}
/**
* 对红黑树的节点(8)进行右旋转
* <p>
* 右旋示意图(对节点8进行右旋):
* 13 8
* / \ / \
* 8 nul 1 13
* / \ --(右旋)- / \ / \
* 1 nul nul nul nul nul
* / \
* nul nul
*
* @param oldRoot 原根结点
*/
private void rightRotate(Node<T> oldRoot) {
// 将原根结点的左孩子结点赋值为新的根结点
Node<T> newRoot = oldRoot.leftChild;
//将新根结点的右孩子结点赋值给原根结点的左孩子结点
oldRoot.leftChild = newRoot.rightChild;
//同时将新根结点的右孩子结点的父结点指针parent指向原根结点
if (newRoot.rightChild != null)
newRoot.rightChild.parent = oldRoot;
//将原根结点的父结点赋值给新根结点的父结点
newRoot.parent = oldRoot.parent;
//原根结点的父结点为null,则将新根结点作为整个红黑树的根结点
if (oldRoot.parent == null) {
this.mRoot = newRoot;
} else {
//如果原根结点的父结点非空
if (oldRoot == oldRoot.parent.rightChild)
oldRoot.parent.rightChild = newRoot; //如果原根结点是其父结点的右子树,则将新根结点作为原根结点父结点的右子树
else
oldRoot.parent.leftChild = newRoot; //如果原根结点是其父结点的左子树,则将新根结点作为原根结点父结点的左子树
}
//将修改过的原根结点赋值给新根结点的右孩子结点
newRoot.rightChild = oldRoot;
//同时将原根结点的父结点指针parent指向新根结点
oldRoot.parent = newRoot;
}
/**
* 前序遍历红黑树
*/
public void preOrder() {
System.out.println("前序遍历");
preOrder(mRoot);
System.out.println();
}
public void preOrder(Node<T> node) {
if (node != null) {
System.out.print("" + node.data + " ");
preOrder(node.leftChild);
preOrder(node.rightChild);
}
}
public void inOrder() {
System.out.println("中序遍历");
inOrder(mRoot);
System.out.println();
}
public void inOrder(Node<T> node) {
if (node != null) {
inOrder(node.leftChild);
System.out.print("" + node.data + " ");
inOrder(node.rightChild);
}
}
public void postOrder() {
System.out.println("后续遍历");
postOrder(mRoot);
System.out.println();
}
public void postOrder(Node<T> node) {
if (node != null) {
postOrder(node.leftChild);
postOrder(node.rightChild);
System.out.print("" + node.data + " ");
}
}
/*
* 销毁红黑树,递归将红黑树中每一个结点都设置成null
*/
private void destroy(Node<T> tree) {
if (tree == null)
return;
if (tree.leftChild != null)
destroy(tree.leftChild);
if (tree.rightChild != null)
destroy(tree.rightChild);
tree = null;
}
//销毁红黑树,递归将红黑树中每一个结点都设置成null 同时将根结点设置成null
public void clear() {
destroy(mRoot);
mRoot = null;
}
/*
* 打印"红黑树"
*
* key -- 节点的键值
* direction -- 0,表示该节点是根节点;
* -1,表示该节点是它的父结点的左孩子;
* 1,表示该节点是它的父结点的右孩子。
*/
private void print(Node<T> tree, T data, int direction) {
if (tree != null) {
if (direction == 0) // tree是根节点
System.out.printf("%2d(B) is root\n", tree.data);
else // tree是分支节点
System.out.printf("%2d(%s) is %2d's %6s child\n", tree.data, isRed(tree) ? "R" : "B", data, direction == 1 ? "right" : "left");
print(tree.leftChild, tree.data, -1);
print(tree.rightChild, tree.data, 1);
}
}
public void print() {
if (mRoot != null)
print(mRoot, mRoot.data, 0);
}
/**
* 构建一个结点类
*/
public static class Node<T extends Comparable<T>> {
public boolean color;//颜色 true:黑色 false:红色
public T data;//存储的值
public Node leftChild;//左孩子结点
public Node rightChild;//右孩子结点
public Node parent;//父结点
public Node(T data, boolean color, Node parent, Node leftChild, Node rightChild) {
this.color = color;
this.data = data;
this.leftChild = leftChild;
this.rightChild = rightChild;
this.parent = parent;
}
@Override
public String toString() {
return "Node{" +
"color=" + color +
", data=" + data +
'}';
}
}
public static void main(String[] args) {
RBTree2<Integer> rbTree2 = new RBTree2<>();
Node<Integer> node = new Node(2, true, null, null, null);
node.leftChild = new Node<>(1, false, null, null, null);
node.rightChild = new Node<>(3, false, null, null, null);
node.leftChild.leftChild = new Node<>(0, false, null, null, null);
node.rightChild.rightChild = new Node<>(4, false, null, null, null);
rbTree2.insert(node);
rbTree2.insert(5);
rbTree2.print();
rbTree2.preOrder();
rbTree2.inOrder();
rbTree2.postOrder();
}
}
打印日志如下:
> Task :javalib:RBTree2.main()
2(B) is root
1(R) is 2's left child
0(R) is 1's left child
3(R) is 2's right child
4(R) is 3's right child
5(R) is 4's right child
前序遍历
2 1 0 3 4 5
中序遍历
0 1 2 3 4 5
后续遍历
0 1 5 4 3 2