树(Tree)

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,并且左右两个字树都是一颗二叉平衡树

作用

  • 使得树的高度最低,因为树的查找效率取决于树的高度

插入

  1. 平衡没有被打破,不用调整
  2. 平衡被打破,需要调整

调整规则

  • 插入结点在失衡结点的左子树的左边,只需要经过一次左旋(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)进行旋转)
  • 最后需要注意LLRR的旋转规则
    (这里需要注意: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 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值