红黑树原理和算法,超详细介绍(图,内附实现代码)

掘金链接

二叉搜索树

普通的二叉搜索树,如果是一颗平衡的二叉树。
平衡的二叉搜索树

则n个节点查找的时间复杂度为O(log2n),近似折半查找。
如果二叉搜索树完全不平衡,则会退化为一条链表

完全不平衡的二叉树

它的时间复杂度为O(n)。
因此为了解决这个问题,出现了avl树(自平衡二叉树)。
嗯,它不重要,不是我要讲的重点。(~ ̄▽ ̄)~

avl树

avl树一个高度平衡的二叉树。
它具有一下性质:它的左右子树的高度差绝对值不超过1,且它的左右子树都是一颗平衡二叉树。
avl树追求完全平衡,当插入一个新节点,只要不满足高度差的绝对值小于等于1,就通过旋转来平衡。这种方式,在插入,删除频繁的操作中,反而牺牲了性能。嗯,它也不重要,也不是我要说的重点。ο(=•ω<=)ρ⌒☆

红黑树

接下来就是我要说的重点啦(✿◡‿◡)。红黑树是一种特殊的avl树。它不追求高度的平衡,它只要求部分平衡,给树的节点增加颜色和规则,达到非严格的平衡。
一颗标准的红黑树,它满足以下性质:
       1.树节点不是黑色就是红色。
       2.根节点是黑色的。
       3.红色节点的子节点必须是黑色。
       4.空叶子节点是黑色的。
       5.从一个节点到叶子节点所有路径包含的黑色节点个数是相同的。
       6.刚插入的节点为红色,即一个节点初始化时为红色。

红黑树的旋转

必要知识点✧
左旋

如上图,对0078节点进行左旋 下图 ↓ (其实就是将0078节点变成0089的左节点的意思o‿≖✧)

变色↓👇

=====================================
右旋

如上图,对0067节点进行右旋 下图↓ (将0067节点变成0056的右节点)

变色↓

红黑树插入时维护规则

新插入的节点存在以下几种可能 ✧:
1.当前插入的节点是根节点直接变色(性质2)。(・ω< )★
if(root==null){
  root=new Node(data);
  root.color=Color.BLACK;
  return true;
}
2.当前插入节点的父节点和叔父节点都是红色节点。父节点,叔父节点,祖父节点变色
if(uncle!=null&&parent.color==Color.RED&&uncle.color==Color.RED){
  //父节点变色 red->black 该路径下多了一个黑节点
  changeColor(parent);
  //叔叔节点变色red->black 该路径下多了一个黑节点
  changeColor(uncle);
  //祖父节点变色black->red 该路径下少一个黑节点,则到parent和到uncle节点的路径黑节点的个数不变
  changeColor(gParent);
  //下一个可能冲突的节点
  node=gParent;
  continue;
}

图示

   ↓↓↓👇👇👇👇👇👇   

3.红红冲突,父节点为红色,叔父节点黑色,并且当前插入节点是父节点的右节点,父节点是祖父节点的右节点,对祖父节点进行左旋,并变色(・ω< )★

    //旋转核心思想 将红-红冲突往下移,避免往上波及从而影响整棵树
	if((uncle==null||uncle.color==Color.BLACK)&&parent.color==Color.RED){
		//case 2: parent红色节点uncle黑色节点,node为parent的右节点
		if(node==parent.right&&parent==gParent.right){
			//对node的祖父节点进行左旋
			Node<T> left=parent.left;
					
			if(root!=gParent&&gParent.parent.left==gParent){
				gParent.parent.left=parent;
			}else if(root!=gParent){
				gParent.parent.right=parent;
			}else{
				root=parent;
			}
			parent.parent=gParent.parent;
			gParent.parent=parent;
			parent.left=gParent;
			gParent.right=left;
					
			if(left!=null){
				left.parent=gParent;
			}
					
			//更改父节点,祖父节点颜色
			changeColor(parent);//红-->黑
			changeColor(gParent);//黑-->红,可能引起冲突
			//由于现在gPatent的右节点是原来的parent左节点,且parent原来的颜色是红色,则原来parent的左节点颜色是黑色,移动到gParent的右边必不会产生冲突
			//所以新冲突点可能在现在的gParent的左边
			node=gParent.left;
			continue;
		}
	}

图示↓

  ↓↓↓👇👇👇👇👇👇  

 

3.红红冲突,父节点为红色,叔父节点黑色,并且当前插入节点是父节点的左节点,父节点是祖父节点的左节点,对祖父节点进行右旋,并变色(・ω< )★

    //旋转核心思想 将红-红冲突往下移,避免往上波及从而影响整棵树
	if((uncle==null||uncle.color==Color.BLACK)&&parent.color==Color.RED){
	    if(node==parent.left&&parent==gParent.left){
			//对node的祖父节点进行右旋
			Node<T> right = parent.right;
			if(gParent!=root){
				if(gParent.parent.left==gParent){
					gParent.parent.left=parent;
				}else{
					gParent.parent.right=parent;
				}
			}else{
				root=parent;
			}
			parent.parent=gParent.parent;
			parent.right=gParent;
			gParent.parent=parent;
			gParent.left=right;
			if(right!=null){
				right.parent=gParent;
			}
			//更改父节点,祖父节点颜色
			changeColor(parent);//红-->黑
			changeColor(gParent);//黑-->红,可能引起新一轮冲突
			//由于现在gPatent的右节点是原来的parent右节点,且parent原来的颜色是红色,则原来parent的右节点颜色是黑色,移动到gParent的左边必不会产生冲突
			//所以新冲突点可能在现在的gParent的右边
			node=gParent.right;
			continue;
		}
	}

图示↓


  👇👇👇  

满头大汗,终于写完这部分了

 
 
 
以下是本人自己实现的源代码,只实现了添加方法,至于删除的方法来日方长。
RedBlackTree.java

import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
/**
 * 红黑树<br>
 * 规则:<br>
 * 1.根节点必须是黑色<br>
 * 2.红色节点的子节点必须是黑色<br>
 * 3.从一个节点到叶子节点的所有路径包含的黑色节点个数相同<br>
 * 4.节点颜色只有红色黑色两种<br>
 * 5.空叶子节点是黑色节点<br>
 * 6.刚插入未确定位置的节点,即初始化一个节点的颜色为红色<br>
 * @author Administrator
 *
 * @param <T>
 */
public class RedBlackTree<T extends Comparable<T>> {
	private Node<T> root;
	public boolean add(T data){
		if(root==null){
			root=new Node<T>(data);
			root.color=Color.BLACK;
			return true;
		}
		Node<T> node=new Node<>(data);
		Node<T> currNode=root;
		//寻找插入位置
		A:while(true){
			if(currNode.data.compareTo(node.data)<=0){
				if(currNode.right!=null){
					currNode=currNode.right;
				}else{
					currNode.right=node;
					node.parent=currNode;
					break A;
				}
			}else{
				if(currNode.left!=null){
					currNode=currNode.left;
				}else{
					currNode.left=node;
					node.parent=currNode;
					break A;
				}
			}
		}
		//判断是否是标准红-黑树
		B:while(true){
			if(node==null){
				break B;
			}
			//不是新插入的节点,经过变色,旋转,找到下一个可能冲突的节点
			if(node.color==Color.BLACK){
				break B;
			}
			if(root==node){
				//如果当前节点是根节点且是红色,直接变换颜色
				if(root.color==Color.RED){
					changeColor(root);
				}
				break B;
			}
			Node<T> parent = node.parent;
			//如果当前插入节点的父节点是红色,红红冲突,变换父节点颜色
			if(parent==root){
				if(root.color==Color.RED){
					changeColor(root);
				}
				break B;
			}
			//父节点颜色是黑色,没有违反红黑树规则
			if(parent.color==Color.BLACK){
				break B;
			}
			Node<T> uncle=null;
			Node<T> gParent=parent.parent;
			if(parent==gParent.left){
				uncle=gParent.right;
			}else{
				uncle=gParent.left;
			}
			//case 1: uncle和parent都是红色节点
			if(uncle!=null&&parent.color==Color.RED&&uncle.color==Color.RED){
				//父节点变色 red->black 该路径下多了一个黑节点
				changeColor(parent);
				//叔叔节点变色red->black 该路径下多了一个黑节点
				changeColor(uncle);
				//祖父节点变色black->red 该路径下少一个黑节点,则到parent和到uncle节点的路径黑节点的个数不变
				changeColor(gParent);
				//下一个可能冲突的节点
				node=gParent;
				continue;
			}
			//旋转核心思想 将红-红冲突往下移,避免往上波及从而影响整棵树
			if((uncle==null||uncle.color==Color.BLACK)&&parent.color==Color.RED){
				//case 2: parent红色节点uncle黑色节点,node为parent的右节点
				if(node==parent.right&&parent==gParent.right){
					//对node的祖父节点进行左旋
					Node<T> left=parent.left;
					
					if(root!=gParent&&gParent.parent.left==gParent){
						gParent.parent.left=parent;
					}else if(root!=gParent){
						gParent.parent.right=parent;
					}else{
						root=parent;
					}
					parent.parent=gParent.parent;
					gParent.parent=parent;
					parent.left=gParent;
					gParent.right=left;
					
					if(left!=null){
						left.parent=gParent;
					}
					
					//更改父节点,祖父节点颜色
					changeColor(parent);//红-->黑
					changeColor(gParent);//黑-->红,可能引起冲突
					//由于现在gPatent的右节点是原来的parent左节点,且parent原来的颜色是红色,则原来parent的左节点颜色是黑色,移动到gParent的右边必不会产生冲突
					//所以新冲突点可能在现在的gParent的左边
					node=gParent.left;
					continue;
				}
				//case 3: parent红色节点uncle黑色节点,node为parent的左节点
				else if(node==parent.left&&parent==gParent.left){
					//对node的祖父节点进行右旋
					Node<T> right = parent.right;
					if(gParent!=root){
						if(gParent.parent.left==gParent){
							gParent.parent.left=parent;
						}else{
							gParent.parent.right=parent;
						}
					}else{
						root=parent;
					}
					parent.parent=gParent.parent;
					parent.right=gParent;
					gParent.parent=parent;
					gParent.left=right;
					if(right!=null){
						right.parent=gParent;
					}
					//更改父节点,祖父节点颜色
					changeColor(parent);//红-->黑
					changeColor(gParent);//黑-->红,可能引起新一轮冲突
					//由于现在gPatent的右节点是原来的parent右节点,且parent原来的颜色是红色,则原来parent的右节点颜色是黑色,移动到gParent的左边必不会产生冲突
					//所以新冲突点可能在现在的gParent的右边
					node=gParent.right;
					continue;
				}
			}
		}
		cengciBinli();
		return true;
	}
	private void changeColor(Node<T> node){
		if(node.color==Color.RED){
			node.color=Color.BLACK;
		}else{
			node.color=Color.RED;
		}
	}
	/**
	 * 非递归 中序遍历
	 * @param node
	 */
	public void midPrintNode(Node<T> node){
		Stack<Node<T>> stack=new Stack<>();
		while(node!=null||!stack.isEmpty()){
			while(node!=null||!stack.isEmpty()){
				while(node!=null){
					stack.push(node);
					node=node.left;
				}
				if(!stack.isEmpty()){
					Node<T> pop = stack.pop();
					System.out.print("[data:"+pop.data+",color:"+pop.color.name()+"]  ");
					node=pop.right;
				}
			}
		}
		
	}
	public void cengciBinli(){
		System.out.println("\n--------------层次遍历----------------");
		//定义一个节点队列
		Queue<Node<T>> queue = new LinkedList<>();
		Queue<Node<T>> sq=new LinkedList<>();
		queue.add(this.root);
		A:while(true){
			while(queue.size()>0){
				
				Node<T> node = queue.poll();
				if(node == null){
					break A;
				}
				System.out.print("[data:"+node.data+",color:"+node.color.name()+",parent:"+(node.parent!=null?node.parent.data:"")+"]   ");
				if(node.left != null){
					sq.add(node.left);
				}
				if(node.right != null){
					sq.add(node.right);
				}
			}
			if(sq.size()==0){
				break A;
			}
			queue.addAll(sq);
			sq.clear();
			System.out.println();
		}
	}
	public Node<T> getRoot(){
		return this.root;
	}
	private class Node<T extends Comparable<T>>{
		private T data;
		private Node<T> left;
		private Node<T> right;
		private Node<T> parent;
		private Color color;
		public Node(T data){
			this.data=data;
			this.color=Color.RED;
		}
		public Node(){
			
		}
	}
	private enum Color{
		RED,
		BLACK;
	}
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值