【算法导论】红黑树 java实现

PS:本文章的理论知识来自《数据结构和算法 java描述》

平衡的补救

红黑树的平衡是在插入、删除的过程中取得的。对一个要插入的数据项,插入例程要检查不会破坏树一定的特征。如果破坏了,程序就会进行纠正,根据需要更改树的结构。通过维持树的特征,保持了树的平衡。

 

红-黑规则

  • 每一个节点不是红色的就是黑色的

  • 根总是黑色的

  • 如果节点是红色的,则它的子节点必须是黑色的(反之倒不一定必须为真)

  • 从根到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点。(从根到叶节点的路径上的黑色节点的数目称为黑色高度,所以这条规则的另一种陈述方法是:所有从根到叶节点路径上的黑色高度必须相同。)

 

空子节点

举例,如果节点只有右子节点,那么它空缺的左子节点就是空子节点,反之亦然。

这颗树违背了红-黑规则中的第四条

 

旋转

注意利用颜色规则和节点颜色变换只是有助于决定何时执行旋转。光改变单个节点的颜色不能起任何作用,旋转才是起关键作用的操作。

 

选择一个节点作为旋转的“顶端”,如果做一次右旋,这个“顶端”节点将会向下和向右移动到它右子节点的位置,它的左子节点将会上移到它原来的位置上。

 

必须确保,如果做右旋,顶端节点必须有一个左子节点,否则将没有节点旋转到顶端节点原来所在的位置。类似的,如果做左旋,顶端节点必须有一个右子节点。

 

单个节点位置的改变

移动子树

其实质就是单个节点位置的改变,只不过是要旋转的节点,其子节点是子树。

    

从旋转中可以看出,只要一个节点的左边有很多子孙节点,而右边没有这么多节点,就可以考虑向右旋转,反之亦然。但计算机不善于“只靠观察”这种工作方式,不过如果计算机能遵守几个简单的规则,它们也可以做好,这些规则就是在红-黑方法中提供的,表现形式为颜色码和四个红-黑规则。

 

插入一个新节点

会发生三种情况:

  • 在下行路途中的颜色变换

  • 插入节点之后的旋转

  • 在向下路途上的旋转

 

在下行路途中的颜色变换

红-黑树的插入例程开始时所做的事,和普通的二叉树基本上一样:沿着从根向下走,在每个节点处通过比较节点的关键字相对大小,来决定向左还是向右走。但是在红-黑树中,找到插入点更复杂,因为有颜色变换和旋转。

 

假设插入例程沿着树向下执行,在每一个节点处向左或者向右走,查找插入新节点的位置。为了不违反颜色规则,在必要的时候需要进行颜色变换,其规则是:每当查找例程遇到一个有两个红色子节点的黑色节点时,它必须把子节点变为黑色,而把父节点变为红色(除非父节点为根节点,根总是黑色的)。这种颜色的变换,是因为使红色节点变为黑色节点,在不违背规则3(一个节点和它的父节点不能都是红色的)的情况下,插入(连接)新的红色节点更容易。另外,这种颜色变换并不会改变黑高。

 

插入节点之后的旋转

 

新节点的插入可能会使得树违背红-黑规则,因此在插入之后,必须检测是否违背规则,并采取相应的措施。

 

新插入的节点X总是红色的,X可能插入到相对于P(X的父节点)和G(P的父节点)不同的位置上。

内外侧子孙节点的解释:如果节点X是P的左节点并且P是G的左节点,或者X是P的右节点并且P是G的右节点,则X是一个外则子孙节点。相反,如果节点X在P的一侧,而P在G的另一侧,则X是一个内侧子孙节点。

 

为恢复红-黑规则所采取的措施,由颜色以及X和它亲属的布局所决定。新节点插入后的三种(已经涵盖所有情况)可能布局:

  • P是黑色的

  • P是红色的,X是G的一个外侧子孙节点

  • P是红色的,X是G的一个内侧子孙节点

P是黑色的

如果P是黑色的, 就什么事也不用做。因为刚插入的新节点总是红色的,如果它的父节点是黑色的,则没有红色节点连接红色节点的冲突,并且也不会增加黑色节点的数目,所以黑高也不会改变。

 

P是红色的,X是G的一个外侧子孙节点

这种情况下,需要进行一次旋转和颜色变换

 

 

P是红色的,X是G的一个内侧子孙节点

这种情况下,需要执行两次旋转,第一次旋转后,X和P的身份互换,但此时12是25的外侧子孙。所以图b已经跟“P是红色的, X是G的一个外侧子孙节点”一样,只不过对于图b,X和P的身份已经发生了互换。

 

对于重新为节点着色,在做任何旋转之前来做这件事,是最方便的,因为要是等旋转后再改变节点的颜色,到那时就很难知道如何称呼它们。步骤:

  • 对于图a,改变G的颜色,从黑->红

  • 对于图a,改变X的颜色,从红->黑

  • 对于图a,以P为顶点,对X进行左旋

  • 对于图b,以25为顶点,对18进行右旋

 

其他可能情况?

上面讨论的新节点插入后的三种可能布局,真的包含了所有的情况吗?

 

讨论1:

假设,X有一个兄弟节点S,这种情况会使插入X需要的旋转变得更复杂。但是如果P是黑色,插入X没有任何问题(这属于P是黑色的情况)。如果P是红色的,则X插入前,P是不可能有子节点的。因为假设P有子节点,则它的两个子节点必须是黑色的(规则3),P不能只有一个黑色的子节点,因为这样会违背黑高规则(S和空子节点的黑高不同)。

 

所以,如果P是红色的,在X插入之前,P是不可能有子节点的,除非P是黑色的。

讨论2:

所以,在X插入之前,若P是红色的,则只有以下两种布局情况 

在向下路途上的旋转

在下行路途中查找插入点时所做的旋转,实际上它是发生在节点插入之前(即“插入节点之后的旋转”)。在插入过程中的颜色变换,可能会造成对规则3(父节点和子节点不能都是红色)的违规,所以需要旋转来纠正这种违规的情况。这种违规分别对应“违背规则的节点是一个外侧子孙节点”和“违背规则的节点是一个内侧子孙节点”。

 

违背规则的是一个外侧子孙节点

 

 

违背规则的是一个内侧子孙节点 

 

删除节点

红-黑树节点的删除太复杂了,很多程序员都用不同的方法来回避它。一种方法是(和普通的二叉树一样)为删除的节点做个标记,而不是实际地删除它。任何找到该节点的查找例程都知道不用报告已找到该节点。很多情况下都应用这种方法,特别是在不经常执行删除操作时。

 

红-黑树的实现

在普通二叉树的Node类中,增加一个标志红-黑颜色的字段。

 

跟普通的二叉树相比,除了插入节点(不考虑删除节点操作),其他的操作是一样的。

 

对于红-黑树的插入节点例程

  1. 在向下到插入点的路径上,检查当前节点是否为黑色,以及它的两个节点是否都是红色,如果是,则变换这三个节点的颜色,同时注意根节点总保持黑色。

  2. 在颜色变换后,检查是否违背了“父子节点不能同时为红色”的规则,如果违背了,则执行适当的旋转,对于外侧子孙节点执行一次旋转,对于内侧子孙节点执行两次旋转。

  3. 当到达一个叶节点时,插入新节点并保证这个新节点是红色的,再次检查是否违背了“父子节点不能同时为红色”的规则,如果有则执行需要的旋转操作。

 

插入节点中,破坏了黑高规则?

我们在程序中没有跟踪黑高,只需要检查是否违背了“父子节点不能同时为红色”的规则,如果违背了,当时就可以解决掉(检查黑高规则需要更复杂的记录)。如果执行前面描述的颜色变换、颜色改变以及旋转,节点的黑高应当能自动调整,并且树应该保持平衡。

 

 

Java 代码实现

在二叉树的代码实现上对节点插入函数insert做了修改,Node类加了color域,节点删除暂时不对应。

 

package com.demo;

import java.io.BufferedReader;
import java.io.Console;
import java.io.IOException;
import java.io.InputStreamReader;

public class RBTree {

	private final int SPACE = 6;
	public static final int NULLKEY = Integer.MAX_VALUE;
	
	private final int RED = 0;
	private final int BLACK = 1;
	
	private Node root;
	
	private Stack workStack = new Stack();
	private Stack backupStack = new Stack();
	
	public static class Node {
		
		public int key;
		public int value; // This field can be any object
		public int color;
		
		private Node leftChild;
		private Node rightChild;
		private Node parent;
		
		public Node(int key, int value) {
			this.key = key;
			this.value = value;
		}
	}
	
	/**
	 * Just for graphicDiplay method.
	 * @author hongjunxin
	 *
	 */
	private static class Stack {
		
		private int maxSize = 50;
		public Node[] array = new Node[maxSize];
		private int nItems;
		
		/**
		 * 
		 * @param e
		 * @param maskNullKey true: if e is null, mask it as NullKey. false: just return.
		 */
		public void push(Node e, boolean maskNullKey) {
			if (e == null) {
				if (maskNullKey) {
					array[nItems++] = new Node(NULLKEY, NULLKEY);
				} 
				return;
			}
			if (nItems == maxSize) {
				maxSize *= 2;
				Node[] tmp = array;
				array = new Node[maxSize];
				for (int i = 0; i < maxSize/2; i++) {
					array[i] = tmp[i];
				}
			}
			array[nItems++] = e;
		}
		
		public Node pop() {
			if (nItems == 0) {
				return null;
			} else {
				return array[--nItems];
			}
		}
		
		public boolean isEmpty() {
			return (nItems == 0);
		}
	}

	public void insert(int key, int value) {
		this.insert(new Node(key, value));
	}
	
	public void insert(Node node) {
		node.color = RED;
		if (root == null) {
			node.color = BLACK;
			root = node;
			return;
		}
		Node parent = null;
		Node current = root;
		boolean isLeft = false;
		while (current != null) {
			parent = current;
			if (node.key < current.key) {
				current = current.leftChild;
				isLeft = true;
			} else if (node.key > current.key) {
				current = current.rightChild;
				isLeft = false;
			} else {
				System.out.println("Already has " + node.key + ". Ignore insert.");
				return;
			}
			
			if (current == null) break;
			// In this while the following color change and rotation
			// never change variable current.
			
			Node x = parent;
			if (needColorChanged(x)) {
				if (x.equals(root)) {
					x.color = BLACK;
				} else {
					x.color = RED;
				}				
				x.leftChild.color = BLACK;
				x.rightChild.color = BLACK;
				
				if (x.parent == null) continue;
				Node p = x.parent;
				Node g = p.parent;
				
				// If color changed breaks color rule,
				// do rotation to fix it.
				if (x.color == RED && p.color == RED) {
					if ((x.equals(p.leftChild) && p.equals(g.leftChild)) ||
							(x.equals(p.rightChild) && p.equals(g.rightChild))) {
						doOnceRotation(x, p, g);							
					} else {
						doTwiceRotation(x, p, g);
					}
				}
			}
		}
		
		// Insert new node
		if(isLeft) parent.leftChild = node;
		else parent.rightChild = node;
		node.parent = parent;
		
		// After new node inserted we need to check color rule here again
		if (parent.color == BLACK) {
			// Insert done. Needn't any rotation.
		} else {
			Node x = node;
			Node p = parent;
			Node g = p.parent;
			if ((x.equals(p.leftChild) && p.equals(g.leftChild)) ||
					(x.equals(p.rightChild) && p.equals(g.rightChild))) {
				doOnceRotation(x, p, g);
			} else {
				doTwiceRotation(x, p, g);
			}
		}
	}
	
	/**
	 * 如果 x 是外侧子孙节点,则只需要对 p 进行一次旋转操作
	 * @param x
	 * @param p x's parent who needs do once rotation
	 * @param g p's parent
	 */
	private void doOnceRotation(Node x, Node p, Node g) {
		changeColor(p);
		changeColor(g);
		if (g.parent != null) {							
			if (g.equals(g.parent.leftChild )) {
				g.parent.leftChild = p;
			} else {
				g.parent.rightChild = p;
			}
			p.parent = g.parent;
		} else {
			p.parent = null;		
			root = p;
			p.color = BLACK;
		}
		g.parent = p;
		if (x.equals(p.leftChild) && p.equals(g.leftChild)) {							
			if (p.rightChild != null) {
				g.leftChild = p.rightChild;
				g.leftChild.parent = g;
			} else {
				g.leftChild = null;
			}
			p.rightChild = g;
		} else if (x.equals(p.rightChild) && p.equals(g.rightChild)) {							
			if (p.leftChild != null) {
				g.rightChild = p.leftChild;
				g.rightChild.parent = g;
			} else {
				g.rightChild = null;
			}
			p.leftChild = g;
		}
	}
	
	/**
	 * 如果 x 是内侧子孙节点,则 x 需要进行两次旋转操作
	 * @param x who needs to do twice rotation
	 * @param p x's parent
	 * @param g p's parent
	 */
	private void doTwiceRotation(Node x, Node p, Node g) {
		changeColor(x);
		changeColor(g);
		if (x.equals(p.rightChild) && p.equals(g.leftChild)) {
			// x do first left rotation
			g.leftChild = x;
			x.parent = g;
			p.parent = x;
			if (x.leftChild != null) {
				x.leftChild.parent = p;
				p.rightChild = x.leftChild;
			} else {
				p.rightChild = null;
			}
			x.leftChild = p;
			
			// x do second right rotation
			if (g.parent != null) {
				if (g.equals(g.parent.leftChild)) {
					g.parent.leftChild = x;
				} else {
					g.parent.rightChild = x;
				}								
				x.parent = g.parent;
			} else {
				x.parent = null;
				root = x;
				x.color = BLACK;
			}
			g.parent = x;				
			if (x.rightChild != null) {
				g.leftChild = x.rightChild;
				g.leftChild.parent = g;
			} else {
				g.leftChild = null;
			}
			x.rightChild = g;
		} else if (x.equals(p.leftChild) && p.equals(g.rightChild)) {
			// x do first right rotation
			g.rightChild = x;
			x.parent = g;
			p.parent = x;
			if (x.rightChild != null) {
				x.rightChild.parent = p;
				p.leftChild = x.rightChild;
			} else {
				p.leftChild = null;
			}
			x.rightChild = p;
			
			// x do second left rotation
			if (g.parent != null) {
				if (g.equals(g.parent.leftChild)) {
					g.parent.leftChild = x;
				} else {
					g.parent.rightChild = x;
				}								
				x.parent = g.parent;
			} else {
				x.parent = null;
				root = x;
				x.color = BLACK;
			}
			g.parent = x;	
			if (x.leftChild != null) {
				g.rightChild = x.leftChild;
				g.rightChild.parent = g;
			} else {
				g.rightChild = null;
			}
			x.leftChild = g;
		}
	}
	
	private boolean needColorChanged(Node parent) {
		if (parent.leftChild != null && parent.rightChild != null) {
			if (parent.leftChild.color == RED &&
					parent.rightChild.color == RED &&
					parent.color == BLACK) {
				return true;
			}
		}
		return false;
	}
	
	private void changeColor(Node node) {
		if (node != null) {
			node.color = (node.color == RED) ? BLACK : RED;
		}
	}
	
	public void display() {
		System.out.print("[ ");
		recDisplay(root);
		System.out.print("]");
		System.out.println();
	}
	
	private void recDisplay(Node node) {
		if (node != null) {
			recDisplay(node.leftChild);
			System.out.print(node.key + " ");
			recDisplay(node.rightChild);
		}
	}
	
	public void graphicDisplay() {
		if (root == null) return;
		int lineCount = 0;
		Node node;		
		workStack.push(root, false);
		// To count line
		while ((node = workStack.pop()) != null) {						
			backupStack.push(node.leftChild, false);
			backupStack.push(node.rightChild, false);
			if (workStack.isEmpty()) {
				while (!backupStack.isEmpty()) {
					workStack.push(backupStack.pop(), false);
				}
				if (!workStack.isEmpty()) {
					lineCount++;
				}
			}
		}
				
		// To draw every line
		workStack.push(root, false);		
		printTab(lineCount--);
		printNode(root);
		System.out.println();
		printTab(lineCount);
		while (lineCount >= 0) {
			node = workStack.pop();
			printNode(node.leftChild);
			printSpace((int)(SPACE * Math.pow(2, lineCount)));
			printNode(node.rightChild);			
			backupStack.push(node.leftChild, true);
			backupStack.push(node.rightChild, true);
			if (workStack.isEmpty()) {
				System.out.println();
				printTab(--lineCount);
				while (!backupStack.isEmpty()) {
					workStack.push(backupStack.pop(), false);
				}
			} else {
				printSpace((int)(SPACE * Math.pow(2, lineCount)));
			}
		}
	}
	
	private void printNode(Node node) {
		if (node == null) {
			System.out.print("nn");
		} else {
			System.out.print(node.key);
			System.out.print(node.color == RED ? "R" : "B");
		}
	}
	
	private void printSpace(int s) {
		for (int i = 1; i < s-1; i++) {
			System.out.print(" ");
		}
	}
	
	private void printTab(int n) {
		if (n == 0) {
			printSpace((int)(0.5 * SPACE));
		} else {
			int an;
			if (n == 1) an = 1;
			else an = (n - 1) * 2;
			printSpace(an * SPACE);
		}
	}
	
	public void delete(int key) {
		Node delNode = find(key);
		if (delNode == null) {
			System.out.println("Node " + key + " not found.");
			return;
		}
		
		boolean isLeft = false;
		if (!delNode.equals(root)) {
			isLeft = delNode.equals(delNode.parent.leftChild) ? true : false;
		}
		
		if (delNode.leftChild == null && 				
				delNode.rightChild == null) {
			if (delNode.equals(root)) {
				root = null;
			} else {
				if (isLeft) {
					delNode.parent.leftChild = null;
				} else {
					delNode.parent.rightChild = null;
				}	
			}		
		} else if (delNode.leftChild != null &&
					delNode.rightChild != null) {
			Node successor = getSuccessor(delNode.key);
			 
			if (successor.equals(delNode.rightChild)) {
				successor.leftChild = delNode.leftChild;
				if (delNode.leftChild != null) {
					delNode.leftChild.parent = successor;
				}
			} else {
				successor.parent.leftChild = successor.rightChild;
				if (successor.rightChild != null) {
					successor.rightChild.parent = successor.parent;
				}			
				
				delNode.leftChild.parent = successor;
				delNode.rightChild.parent = successor;
				successor.leftChild = delNode.leftChild;
				successor.rightChild = delNode.rightChild;
			}
			
			successor.parent = delNode.parent;
			if (delNode.equals(root)) {				
				root = successor;
			} else {
				if (isLeft) {
					delNode.parent.leftChild = successor;
				} else {
					delNode.parent.rightChild = successor;
				}
			}
		} else {
			Node child;
			child = (delNode.leftChild == null) ? 
						delNode.rightChild : delNode.leftChild;
			child.parent = delNode.parent;
			if (delNode.equals(root)) {
				root = child;
			} else {
				if (isLeft) {
					delNode.parent.leftChild = child;
				} else {
					delNode.parent.rightChild = child;
				}
			}
		}
	}
	
	private Node getSuccessor(int key) {
		Node delNode = find(key);
		Node current = delNode.rightChild;
		if (current != null) {
			while (current.leftChild != null) {
				current = current.leftChild;
			}
		}
		return current;
	}
		
	public Node find(int key) {
		Node current = root;
		while (current != null) {
			if (key == current.key) {
				return current;
			} else if (key < current.key) {
				current = current.leftChild;
			} else {
				current = current.rightChild;
			}
		}
		return null;
	}
	
	public Node getMax() {
		Node right = root;
		Node parent = null;
		while (right != null) {
			parent = right;
			right = right.rightChild;
		}
		return parent;
	}
	
	public Node getMin() {
		Node left = root;
		Node parent = null;
		while (left != null) {
			parent = left;
			left = left.leftChild;
		}
		return parent;
	}
	
	public static void main(String[] args) {
		RBTreeApp.test();
	}
	
}


class RBTreeApp {
	
	static void test() {
		RBTree tree = new RBTree();
		int n;
		tree.insert(1, 1); 
		
		System.out.println();
		tree.display();		
		tree.graphicDisplay();
		
		String key;		
		String action;
		while (true) {
			System.out.println();
			
			System.out.println("Choose action ===============");
			System.out.print("i:insert  d:delete  f:find  b:max  s:min >> ");
			System.out.println();
			System.out.flush();
			action = getInput();
			System.out.print("Input key >> ");			
			System.out.flush();
			key = getInput();
			
			if (action.equals("i")) {
				n = Integer.parseInt(key);
				tree.insert(n, n);
				tree.display();
				tree.graphicDisplay();
			} else if (action.equals("d")) {
//				tree.delete(Integer.parseInt(key));
//				tree.display();
				tree.graphicDisplay();
				System.out.println("not support now");
			} else if (action.equals("f")) {
				RBTree.Node found;
				found = tree.find(Integer.parseInt(key));
				if (found != null) {
					System.out.println("Found " + key);
				} else {
					System.out.println("Not found " + key);
				}
			} else if (action.equals("b")) {
				System.out.println("Max node: " + tree.getMax().key);
			} else if (action.equals("s")) {
				System.out.println("Min node: " + tree.getMin().key);
			} else {
				break;
			}
		}
	}
	
	static String getInput() {
		InputStreamReader isr = new InputStreamReader(System.in);
		BufferedReader r = new BufferedReader(isr);
		String s = null;
		try {
			s = r.readLine();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return s;
	}
}

运行结果

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值