红黑树 都可以这么细?面试官还能怎么说.

Posted 程序字母K

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红黑树 都可以这么细?面试官还能怎么说.相关的知识,希望对你有一定的参考价值。

女:你一点都不注重细节;
男:你说我不都细节?? 你看看下面的红黑树讲解,,你说我不够细节??
随后男发送下面红黑树代码发给女朋友之后;
女:哇,哥哥果然好细;
男:细节拉满,关注又是一个细节;
当然也不能太细,这样也不太好;

在这里插入图片描述

代码+图解

红黑树的性质:

首先记住它的性质:

在增删节点时需要调整满足性质

  1. 根节点是黑色的;
  2. 如果一个节点是红色的,则它的两个孩子结点是黑色的;
  3. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点;
  4. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点);

对颜色进行定义:

//我们用枚举表示表示出两种颜色  
//枚举默认第一个参数 BLACK 为 0 ,第二个参数 RED 为 1;
enum COLOR {
	BLACK,
	RED
};

红黑树的节点表示:

在这里插入图片描述
看图说节点
红黑树的节点参数,

一个指向上面的节点定义为_parent
一个指向左边的节点的节点指针 _left
一个指向右边的 _right
一个pair类型的值
一个颜色的识别

当然这么写参数那得来个构造函数了,“细节”。

template<class K,class V>
struct RBNode {
	//各个节点
	RBNode<K, V>* _parent;
	RBNode<K, V>* _left;
	RBNode<K, V>* _right;
	//key-value
	pair<K, V> _kv;
	//颜色
	COLOR _color;
	RBNode(const pair<K, V>& kv = pair<K, V>())
		:_parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		, _kv(kv)
		//新申请的节点定义为RED,黑色的加入,会导致其他所有路径的黑色都遭到破坏;
		, _color(RED) {
	}
};

对树的类进行定义:

在这里插入图片描述

红黑树的头节点——header

指向如图上图:具体下面再写

template<class K,class V>
class RBTree {
public:
	//类型定义,用Node表示RBNode<K,V>
	typedef RBNode<K,V> Node;

	RBTree()
		:_header(new Node) {
		//创建空树
		_header->_left = _header->_right = _header;
	}
private:
	Node* _header;
};

红黑树的插入:

当插入c节点时,为了满足性质,需要对节点进行调整,为了保证调整最优;
如果插入节点的父节点(u)和叔叔节点(u)为红色,那么只需要改变叔叔节点(u)和父节点(p)颜色为黑色,祖父节(g)为黑色即可;
当然如果祖父节点(g) 为根节点,再把其变为黑色;
在这里插入图片描述
不是根节点,向上调整;
在这里插入图片描述

结构右旋:

写一个左旋的操作,在树结构变化时可高效的改变保证结构性质;
如果
当出现情况1:
插入节点为c,当它的叔叔节点为黑色,或者叔叔节点不存在,操作相同;

在这里插入图片描述

记录三个节点,只有这三个节点指向变化;
由图:
用parent表示g,
用subL表示p,
用subLR表示p的右子节点即c;
用pparent表示g的父节点;

	//		  parent
	//	subL
	//			subLR	
	void RotateR(Node* parent) {
		Node* subL = parent->_left;
		Node* subLR = subL->_right;
		//改变节点指向:
		subL->_right = parent;
		parent->_left = subLR;
		//如果存在父节点存在,如果为nullptr则没有父节点;
		if (subLR)
			subLR->_parent = parent;
		//判断根
		if (parent == _header->_parent) {
			_header->_parent = subL;
			subL->_parent = _header;
		}
		//不是根
		else {
			Node* pparent = parent->_parent;
			subL->_parent = pparent;
			if (pparent->_left == parent)
				pparent->_left = subL;
			else
				pparent->_right = subL;
		}
		//先要将parent的父节点赋值出去,才能改变paremt的父节点;
		parent->_parent = subL;
	}

结构左旋:

:如图顺序(未画完全,子节点仍然有且满足性质

在这里插入图片描述

记录三个节点,只有这三个节点指向变化;(操作和右旋思路相同)
由图:
用pparent表示p的父节点;
	//			parent
	//					subR
	//			subRL				
	void RotateL(Node* parent) {
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		subR->_left = parent;
		parent->_right = subRL;
		if (subRL) {
			subRL->_parent = parent;
		}
		if (_header->_parent == parent) {
			_header->_parent = subR;
			subR->_parent = _header;
		}
		else {
			Node* pparent = parent->_parent;
			subR->_parent = pparent;
			if (pparent->_left == parent)
				pparent->_left = subR;
			else
				pparent->_right = subR;
		}
		parent->_parent = subR;
	}


头节点指向的左右节点:

如图:header头节点指向最左最右的节点;
#

找红黑树的最左节点:

	//最左节点
	Node* leftMost() {
		Node* cur = _header->_parent;
		while (cur && cur->_left) {
			cur = cur->_left;
		}
		return cur;
	}

找红黑树的最右节点:

//最右节点
	Node* rightMost() {
		Node* cur = _header->_parent;
		while (cur && cur->_right) {
			cur = cur->_right;
		}
		return cur;
	}

红黑树的插入情况分析:

  • 情况1:插入节点的父节点颜色为黑色

处理1:直接插入;

  • 情况2:插入节点的父节点为红色,叔叔节点存在且为红色

处理2:插入后叔叔节点和父节点颜色变黑祖父节点颜色变红,向上遍历查看情况;

  • 情况3:父节点为红色,叔叔节点存在为黑色或不存在,插入节点为父节点的左子树,父节点为祖父节点的左子树;

处理3:进行结构右旋操作(RotateR);

  • 情况4:和情况3的结构调整相反父节点为红色,叔叔节点存在为黑色或不存在,插入节点为父节点的右子树,父节点为祖父节点的右子树;

处理4:进行结构左旋操作(RotateL);

  • 情况5:和情况6结构调整相反父节点为红色,叔叔节点存在为黑色或不存在,插入节点为父节点的左子树,父节点为祖父节点的右子树;

处理5:先以父节点为根,先右旋操作(RotateR),再以祖父节点为根,左旋操作(RotateL);

  • 情况6:父节点为红色,叔叔节点存在为黑色或不存在,插入节点为父节点的右子树,父节点为祖父节点的左子树;

处理6:先以父节点为根,左旋操作(RotateL),再以祖父节点为根,右旋操作(RotateR);

上图:(个情况)

  • 情况1:在这里插入图片描述

  • 情况2:在这里插入图片描述

  • 情况3:在这里插入图片描述

  • 情况4:
    和情况3相反,自己画去;

  • 情况5:
    情况6相反,自己画去;

  • 情况6在这里插入图片描述

上代码:

	//插入
	bool insert(const pair<K,V>& kv) {
		//1.搜索树颜色
		//空树:_header->parent:nullptr
		if (_header->_parent == nullptr) {
			//创建根节点
			Node* root = new Node(kv);

			_header->_parent = root;
			root->_parent = _header;
			_header->_left = _header->_right = root;
			//根节点是黑色
			root->_color = BLACK;
			return true;
		}
		
		//从根节点开始搜索
		//正常插入
		Node* cur = _header->_parent;
		Node* parent = nullptr;
		while (cur) {
			parent = cur;
			//和key值进行比较
			//kv: pair<K,V>, key:pair.first
			if (cur->_kv.first == kv.first) {
				//key值存在,不允许插入
				return false;
			}
			
			else if (cur->_kv.first > kv.first) {
			//向小的找,左子树去找
				cur = cur->_left;
			}
			else {
			//向大的找,右子树去找
				cur = cur->_right;
			}
		}//找到了
		
		//创建待插入节点
		cur = new Node(kv);
		if (parent->_kv.first > cur->_kv.first)
			parent->_left = cur;//比较大小,插在左右那个位置;
		else
			parent->_right = cur;
		cur->_parent = parent;

		//2.调整结构或者修改颜色
	//判断是否至少有三层;
		while (cur != _header->_parent && cur->_parent->_color == RED) {
			parent = cur->_parent;
			Node* gfather = parent->_parent;
			//可能出现情况6,情况3
			if (gfather->_left == parent) {
				Node* uncle = gfather->_right;
				//情况2:uncle存在,并且都是红色
				if (uncle && uncle->_color == RED) {
					parent->_color = uncle->_color = BLACK;
					gfather->_color = RED;
					//继续向上更新
					cur = gfather;
				}
				//
				else {
					cout << "Rotate" << endl;
					//双旋结构
					//情况6:
					if (cur == parent->_right) {
						RotateL(parent);
						swap(cur, parent);
					}
//经过第一次左旋后,情况6 与情况3 只有cur节点和父节点的指向不一样,
//经过swap两者后,接下来操作相同都为右旋;
					RotateR(gfather);
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
			else {
				//gfather->_right=parent的情况;
				//情况4,情况5,;
				//处理与情况3,情况6相反;
				Node* uncle = gfather->_right;
				//uncle存在,且都是红色,最简
				if (uncle && uncle->_color == RED) {
					parent->_color = uncle->_color = BLACK;
					gfather->_color = RED;
					cur = gfather;
				}
				//情况3:uncle不存在,或者uncle存在为黑色
				//双旋结构
				else {
					//		gfather
					//	uncle    	parent  
					//			 cur
					//
					if (cur == parent->_left) {
						RotateR(parent);
						swap(cur, parent);
					}
					RotateL(gfather);
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
		}
		//根节点的颜色改成黑的;
		_header->_parent->_color = BLACK;
		//更新header左右指向
		_header->_left = leftMost();
		_header->_right = rightMost();
	}

中序遍历进行打印:

//对中序遍历进行封装:
	void inorder() {
		_inorder(_header->_parent);
		cout << endl;
	}
	//使用递归从叶子节点开始;
	//1.打印最左节点,
	//2.打印此节点
	//3.打印右节点
	void _inorder(Node* root) {
		if (root) {
			_inorder(root->_left);
			cout << root->_kv.first << "--->" << root->_kv.second << endl;
			_inorder(root->_right);
		}
	}

判断是否满足红黑树的性质:

	//红黑树:
	//1.根:黑色
	//2.每条路径黑色个数相同
	//3.红色不能连续
	bool isBalance() {
		if (_header->_parent == nullptr)
			return true;//只有头节点也是满足红黑树
		Node* root = _header->_parent;
		//1.判断根节点的颜色
		if (root->_color == RED)
			return false;
		//统计一条路劲的黑色节点个数
		int bCount = 0;
		Node* cur = root;
		while (cur) {
			if (cur->_color == BLACK)
				++bCount;
			cur = cur->_left;
		}
		//遍历每一条路径
		int curBCount = 0;
		return _isBalance(root, bCount, curBCount);
	}
	bool _isBalance(Node* root, int& bCount, int curBCount) {
		//当root为空,一条路径遍历结束
		if(root == nullptr){
		//判断黑色节点个数是否相同
		if (bCount != curBCount)
			return false;
		else
			return true;
		}
		//判断是否为黑节点
		if (root->_color == BLACK)
			++curBCount;
		//判断红色节点是否有连续
		if (root->_parent && root->_color == RED
			&& root->_parent->_color == RED) {
			cout << "data: " << root->_kv.first << "--->" << root->_kv.second << endl;
			return false;
		}
		return _isBalance(root->_left, bCount, curBCount) 
			&& _isBalance(root->_right,bCount, curBCount);
	}

完整代码如下:可直接调试

#include<iostream>
#include<iostream>
#include<utility>
using namespace std;

//设置颜色属性
enum COLOR {
	BLACK,
	RED
};
template<class K,class V>
struct RBNode {
	//typedef bool color;
	//各个节点
	RBNode<K, V>* _parent;
	RBNode<K, V>* _left;
	RBNode<K, V>* _right;
	//key-value
	pair<K, V> _kv;
	//颜色
	COLOR _color;
	RBNode(const pair<K, V>& kv = pair<K, V>())
		:_parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		, _kv(kv)
		, _color(RED) {
	}
};
template<class K,class V>
class RBTree {
public:
	//类型定义
	typedef RBNode<K,V> Node;

	RBTree()
		:_header(new Node) {
		//创建空树
		_header->_left = _header->_right = _header;
	}
	//插入
	bool insert(const pair<K,V>& kv) {
		//1.搜索树颜色
		//空树:_header->parent:nullptr
		if (_header->_parent == nullptr) {
			//创建根节点
			Node* root = new Node(kv);

			_header->_parent = root;
			root->_parent = _header;
			_header->_left = _header->_right = root;

			//根节点是黑色
			root->_color = BLACK;
			return true;
		}
		//从根节点开始搜索
		//正常插入
		Node* cur = _header->_parent;
		Node* parent = nullptr;
		while (cur) {
			parent = cur;
			//和key值进行比较
			//kv: pair<K,V>, key:pair.first
			if (cur->_kv.first == kv.first) {
				//key值不允许重复
				return false;
			}
			else if (cur->_kv.first > kv.first) {
				cur = cur->_left;
			}
			else {
				cur = cur->_right;
			}
		}
		//创建待插入节点
		cur = new Node(kv);
		if (parent->_kv.first > cur->_kv.first)
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;

		//2.修改颜色或者调整结构
		while (cur != _header->_parent && cur面试还在被红黑树虐?看完这篇轻松搞定面试官

26 | 红黑树(下):掌握这些技巧,你也可以实现一个红黑树

为什么说spring让Java变得更好?秀出天际!

红黑树最全手撕教程(建议马!)

什么是红黑树?面试必问!

清华毕业程序员面试被吐槽:不会写红黑树,就敢要1万8?