AVL树/红黑树介绍及插入操作实现

Posted DR5200

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AVL树/红黑树介绍及插入操作实现相关的知识,希望对你有一定的参考价值。

一.AVL树概念

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家在1962年发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。

一棵AVL树或者是空树,或者是具有以下性质的二叉搜索树:
它的左右子树都是AVL树
左右子树高度之差(简称平衡因子)的绝对值不超过1( 可以为-1/0/1 )

二.AVL树模拟实现

AVL树接口总览

template<class K,class V>
struct AVLTreeNode

	AVLTreeNode(const pair<K,V>& kv)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_bf(0)
		,_kv(kv)
	
	AVLTreeNode* _left; // 指向左子树节点
	AVLTreeNode* _right; // 指向右子树节点
	AVLTreeNode* _parent; // 指向父节点
	int _bf;  // 平衡因子
	pair<K, V> _kv; // 节点数据
;
template<class K,class V>
class AVLTree

	typedef AVLTreeNode<K, V> Node;
public:
	AVLTree()
		:_root(nullptr)
	
	~AVLTree();
	Node* Find(const K& key);
	V& operator[](const K& key);
	bool IsAVLTree();
	void InOrder();
	bool Insert(const pair<K, V>& kv);
	void RotateL(Node* parent);
	void RotateR(Node* parent);
	void RotateLR(Node* parent);
	void RotataRL(Node* parent);
private:
	Node* _root;
;

当插入一个新节点的时候,首先根据二叉搜索树的性质,比根节点小的去左子树查找,比根节点大的去右子树查找,循环找到新节点的位置,如果是二叉搜索树的话,插入操作到此就完成了,但AVL树需要保证平衡因子的绝对值不超过1,所以我们还需要更新 新节点沿着父节点的路径上的平衡因子,如果有绝对值超过2的平衡因子,则需要进行旋转操作

右单旋 :

void RotateR(Node* parent)

	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	Node* parentParent = parent->_parent;

	parent->_left = subLR;
	subL->_right = parent;
	if (subLR)
		subLR->_parent = parent;
	if (parent == _root)
	
		_root = subL;
		_root->_parent = nullptr;
	
	else
	
		if (parentParent->_right == parent) parentParent->_right = subL;
		else parentParent->_left = subL;
		subL->_parent = parentParent;
	
	parent->_parent = subL;
	subL->_bf = parent->_bf = 0;

左单旋 :

void RotateL(Node* parent)

	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	Node* parentParent = parent->_parent;

	parent->_right = subRL;
	subR->_left = parent;
	if (subRL)
		subRL->_parent = parent;
	if (parent == _root)
	
		_root = subR;
		_root->_parent = nullptr;
	
	else
	
		if (parentParent->_right == parent) parentParent->_right = subR;
		else parentParent->_left = subR;
		subR->_parent = parentParent;
	
	parent->_parent = subR;
	subR->_bf = parent->_bf = 0;

左右双旋 :

void RotateLR(Node* parent)

	Node* subL = parent->_left;
	Node* subLR = subL->_right;
	int bf = subLR->_bf;
	RotateL(subL);
	RotateR(parent);
	if (bf == 1)
	
		parent->_bf = 0;
		subLR->_bf = 0;
		subL->_bf = -1;
	
	else if (bf == -1)
	
		parent->_bf = 1;
		subLR->_bf = 0;
		subL->_bf = 0;
	
	else if (bf == 0)
	
		parent->_bf = 0;
		subLR->_bf = 0;
		subL->_bf = 0;
	

右左双旋 :

void RotateRL(Node* parent)

	Node* subR = parent->_right;
	Node* subRL = subR->_left;
	int bf = subRL->_bf;
	RotateR(subR);
	RotateL(parent);
	if (bf == 1)
	
		parent->_bf = -1;
		subR->_bf = 0;
		subRL->_bf = 0;
	
	else if (bf == -1)
	
		parent->_bf = 0;
		subR->_bf = 1;
		subRL->_bf = 0;
	
	else if (bf == 0)
	
		parent->_bf = 0;
		subR->_bf = 0;
		subRL->_bf = 0;
	

插入操作

pair<Node*, bool> Insert(const pair<K, V>& kv)

	// 完成插入操作
	if (_root == nullptr)
	
		_root = new Node(kv);
		return pair<Node*,bool>(_root,true);
	
	Node* parent = _root, * cur = _root;
	while (cur)
	
		if (kv.first < cur->_kv.first)
		
			parent = cur;
			cur = cur->_left;
		
		else if (kv.first > cur->_kv.first)
		
			parent = cur;
			cur = cur->_right;
		
		else
		
			return pair<Node*,bool>(cur, false);
		
	
	cur = new Node(kv);
	if (parent->_kv.first < cur->_kv.first) parent->_right = cur;
	else parent->_left = cur;
	cur->_parent = parent;
	Node* tmp = cur;
	// 更新平衡因子
	while (cur != _root)
	
		if (parent->_kv.first < cur->_kv.first) ++parent->_bf;
		else --parent->_bf;

		if (parent->_bf == 0) break;
		else if (parent->_bf == 1 || parent->_bf == -1)
		
			cur = parent;
			parent = cur->_parent;
		
		else if (parent->_bf == 2 || parent->_bf == -2)
		
			if (parent->_bf == -2)
			
				// 右单旋
				if (cur->_bf == -1) RotateR(parent);
				// 左右双旋
				else if (cur->_bf == 1) RotateLR(parent);
			
			else if (parent->_bf == 2)
			
				// 左单旋
				if (cur->_bf == 1) RotateL(parent);
				// 右左双旋
				else if (cur->_bf == -1) RotateRL(parent);
			
			break;
		
	
	return pair<Node*,bool>(tmp, true);

查找操作,根据key值进行查找即可

Node* Find(const K& key)

	Node* cur = _root;
	while (cur)
	
		if (key < cur->_kv.first) cur = cur->_left;
		else if (key > cur->_kv.first) cur = cur->_right;
		else return cur;
	
	return nullptr;

operator[]
如果AVL树中已经存在相同的key值,返回与之相同的key值的val,若不存在,则新插入一个节点,返回该节点的val值

V& operator[](const K& key)

	pair<Node*, bool> ret = Insert(make_pair(key,V()));
	return ret.first->_kv.second;

检验AVL树是否平衡
(1). 检验右子树高度 - 左子树高度是否等于平衡因子(bf)
(2). 检验右子树高度 - 左子树高度的绝对值是否小于2

// 求子树高度
int _Height(Node* root)

	if (root == nullptr) return 0;
	int LeftHeight = _Height(root->_left);
	int RightHeight = _Height(root->_right);
	// 左右子树中更高的树的高度 + 1
	return LeftHeight > RightHeight ? LeftHeight + 1 : RightHeight + 1;

bool _IsBalance(Node* root)

	if (root == nullptr) return true;

	int LeftHeight = _Height(root->_left);
	int RightHeight = _Height(root->_right);
	// 检验右子树高度 - 左子树高度是否等于平衡因子(bf)
	if (RightHeight - LeftHeight != root->_bf)
	
		cout << "平衡因子异常 : " << root->_kv.first << endl;
		return false;
	
	// 检验右子树高度 - 左子树高度的绝对值是否小于2并去递归检测左右子树
	return abs(RightHeight - LeftHeight) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);

bool IsAVLTree()

	return _IsBalance(_root);

AVLTree代码实现

三.红黑树概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍(最长路径不能超过最短路径的2倍),因而是接近平衡的。

(1). 每个结点不是红色就是黑色
(2). 根节点是黑色的
(3). 如果一个节点是红色的,则它的两个孩子结点是黑色的
(4). 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
(5). 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)
满足以上性质,最短路径为全为黑色节点的路径,最长路径为一黑一红节点交替的路径,保证了最长路径为最短路径的2倍

四.红黑树模拟实现

红黑树接口总览

enum Colour

	RED,
	BLACK,
;
template<class K, class V>
struct RBTreeNode

	RBTreeNode(const pair<K, V>& kv)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		,_col(RED)
		, _kv(kv)
	
	RBTreeNode<K, V>* _left;
	RBTreeNode<K, V>* _right;
	RBTreeNode<K, V>* _parent;
	pair<K, V> _kv;
	Colour _col;
;
template<class K, class V>
class RBTree

	typedef RBTreeNode<K, V> Node;
public:
	pair<Node*, bool> Insert(const pair<K, V>& kv);
	bool IsBalance();
private:
	Node* _root;

插入操作 :

首先我们默认插入的节点是红色的,这是因为如果插入黑色节点的话,会影响某条路径上的黑色节点数量,而红黑树要求每条路径上的黑色节点数量一致,所以插入黑色节点操控起来难度更大

(1). 按照二叉搜索树的规则找到节点应该插入的位置
(2). 如果插入节点的父亲是黑色节点,并没有违反红黑树的规定,不用进行操作
(3). 如果插入节点的父亲是红色节点,说明祖父节点一定是黑色节点(如果祖父不是黑色节点,那在插入之前就已经不是红黑树了,因为祖父节点和父亲节点都是红色节点),所以此时就需要看叔叔节点了,叔叔节点分为以下3种情况

一. uncle节点存在且为红

将parent节点和uncle节点变为黑色节点,grandfather节点变为红色节点,然后循环 cur = parent,grandfather = parent->_parent,直到parent为空

二. uncle节点不存在/存在且为黑(单旋转 + 变色)

以grandfather为根进行左单旋/右单旋,parent变为黑,grandfather变为红

三. uncle节点不存在/存在且为黑(双旋转 + 变色)

以grandfather为根进行左右双旋/右左双旋,cur变为黑,grandfather变为红

插入操作实现

pair<Node*, bool> Insert(const pair<K, V>& kv)

	// 插入第一个节点
	if (_root == nullptr)
	
		_root = new Node(kv);
		_root->_col = BLACK;
		return make_pair(_root, true);
	
	// 找到对应的位置进行插入
	Node* parent = _root, * cur = _root;
	while (cur)
	
		if (kv.first < cur->_kv.first)
		
			parent = cur;
			cur = cur->_left;
		
		else if (kv.first > cur->_kv.first)
		
			parent = cur;
			cur = cur->_right;
		
		else
		
			return make_pair(cur, false);
		
	
	cur = new Node(kv);
	if (parent->_kv.first < cur->_kv.first) parent->_right = cur;
	else parent->_left = cur;
	cur->_parent = parent;
	// parent是红色节点的几种情况
	while (parent && parent->_col == RED)
	
		Node* grandfather = parent->_parent;
		if (parent == grandfather->_left)
		
			Node* uncle = grandfather->_right;
			// 情况1 : uncle存在且为red
			if (uncle && uncle->_col == RED)
			
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				cur = grandfather;
				parent = cur->_parent;
			
			// 情况2 + 3 : uncle不存在/uncle存在且为黑
			else
			
				if (cur == parent->_left)
				
					RotateR(grandfather);
					grandfather->_col = RED;
					parent->_col = BLACK;
				
				else
				
					RotateL(parent);
					RotateR(grandfather);
					cur->_col = BLACK;
					grandfather->_col = RED;
				
				break;
			
		
		else
		
			Node* uncle = grandfather->_left;
			if (uncle && uncle->_col == RED)
			
				parent->_col = uncle->_col = BLACK;
				grandfather->_col = RED;

				cur = grandfather;
				parent = cur->_parent;
			
			else
			
				if (cur == parent->_right)
				
					RotateL(grandfather);
					parent->_col = BLACK;
					grandfather->_col = RED;
				
				else
				以上是关于AVL树/红黑树介绍及插入操作实现的主要内容,如果未能解决你的问题,请参考以下文章

AVL树/红黑树介绍及插入操作实现

AVL树/红黑树介绍及插入操作实现

特化的AVL树之红黑树学习及原理解析

AVL树红黑树以及B树介绍

红黑树的插入与删除

C++之红黑树