IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 平衡二叉树的删除 -> 正文阅读

[数据结构与算法]平衡二叉树的删除

目录

前言

一.结点定义

二.删除

2.1 按照二叉搜索树来删除结点

2.2 更新平衡因子

2.3 检测更新完的平衡因子

三.完整代码


前言

? ? ? ? 之前再C语言阶段学习了平衡二叉树,用C语言递归实现了一下平衡二叉树的删除与插入。实现如下:数据结构——平衡二叉树(AVL树)之插入数据结构——平衡二叉树之删除当时学习的时候感觉比较费劲,并且难以理解。

? ? ? ? 现在在C++阶段又重新学习了一下平衡二叉树,使用非递归实现,再加上结点引入平衡因子和父节点,感觉更好理解了。

? ? ? ? 但是学习的时候,主要时学习了平衡二叉树的插入进一步理解平衡二叉树(插入)。一开始觉得平衡二叉树的删除会和插入如出一辙,只是更新平衡因子会又主要的变化。但是当我实现起来,才发现,它并没有这么简单。

一.结点定义

含有父指针,平衡因子,保存的时键值对pair的结点。

//二叉树应用KV模型
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结构体
	pair<K, V> _kv;
};

二.删除

? ? ? ? 删除的步骤和插入的步骤时一样的只是细节上会有区别

  1. 按照二叉搜索树取删除一个结点
  2. 更新平衡因子
  3. 判断更新完的平衡因子,如果超过平衡界限,旋转处理。

????????2.1 按照二叉搜索树来删除结点

? ? ? ? 首先需要找到删除结点,按照搜索树的性质。当前结点键值key大于删除结点键值key,往左边找,当前结点键值key小于删除结点键值key,往右边找。

? ? ? ? 没找到删除失败,找到结点,按照二叉搜索树删除结点的四种情况删除结点(二叉搜索树C++版实现(建议看这)和应用)。

????????注意:1.先不要释放结点的空间。为了后面更新结点平衡因子。

? ? ? ? ? ? ? ? ? ?2.更新父节点。

//按二叉搜索树删除,但是先不释放空间,为了后面作比较
		Node *cur = _root;
		Node *parent = nullptr;
		while (cur){
			//删除结点小于当前结点键值,往左边找
			if (cur->_kv.first > kv.first){
				parent = cur;
				cur = cur->_left;
			}
			//删除结点大于当前结点键值,往右边找
			else if (cur->_kv.first < kv.first){
				parent = cur;
				cur = cur->_right;
			}
			else{
				//找到,四种情况
				if (cur->_left == nullptr&&cur->_right == nullptr){//左右都空
					//为根节点
					if (parent == nullptr){
						delete cur;
						_root = nullptr;
						return true;
					}
					//直接删除
					if (parent->_left == cur){
						
						parent->_left = nullptr;
						
					}
					else{
						
						parent->_right = nullptr;
					}					
					
				}

				if (cur->_left == nullptr&&cur->_right){//左空,右不空
					//为根节点
					if (parent == nullptr){
						_root = cur->_right;
						delete cur;
						return true;
					}
					//父节点指向右边
					if (parent->_left == cur){
						cur->_right->_parent = parent;
						parent->_left = cur->_right;
					}
					else {
						cur->_right->_parent = parent;
						parent->_right = cur->_right;
					}
				}

				if (cur->_left&&cur->_right == nullptr){//右空,左不空
					//根节点
					if (parent == nullptr){
						_root = cur->_left;
						delete cur;
						return true;
					}
					//
					if (parent->_left == cur){
						cur->_left->_parent = parent;
						parent->_left = cur->_right;
					}
					else {
						cur->_left->_parent = parent;
						parent->_right = cur->_right;
					}
				}
				if (cur->_left&&cur->_right){//右不空,左不空
					Node *prev = cur;
					parent = cur;
					cur = cur->_right;

					while (cur->_left){
						parent = cur;
						cur = cur->_left;
					}
					K k = cur->_kv.first;
					V v = cur->_kv.second;
					prev->_kv.first = k;
					prev->_kv.second = v;

					if (parent->_left == cur){
						if (cur->_right){
							cur->_right->_parent = parent;
						}
						
						parent->_left = cur->_right;
					}
					else{
						if (cur->_right){
							cur->_right->_parent = parent;
						}
						parent->_right = cur->_right;
					}

				}

2.2 更新平衡因子

? ? ? ? 删除更新平衡因子和插入平衡因子相反。根据平衡因子的定义为,右子树子树的高度 - 左子树子树的高度。

? ? ? ? 当删除的平衡因子在左子树时,当前结点的平衡因子++,左子树高度减1。

? ? ? ? 当删除的平衡因子在右子树时,当前结点的平衡因子 --,右子树高度减1。

//更新平衡因子
if (cur->_kv.first < parent->_kv.first){
    parent->_bf++;
}
else{
    parent->_bf--;
}

? ? ? ? 为什么先释放掉删除的cur结点?

? ? ? ? 更新平衡因子时,根据键值大小来判断在左子树还是右子树。

2.3 检测更新完的平衡因子

? ? ? ? 平衡因子的更新时兄下往上更新的,并且只会影响到删除结点的父节点或者祖先结点的平衡因子。但是,不一定影响的全部。

? ? ? ? 此时平衡因子只会有三种情况。

  • 更新完结点的平衡因子等于0,此时需要继续往上更新。此时,没删除时,结点的平衡因子要不是1,要不是-1,更新完后,平衡因子等于0,说明高度减少,会对上面结点的平衡因子产生影响。
else if (parent->_bf == 0){//高度降低,继续更新
    cur = parent;
    parent = parent->_parent;
}
  • 更新完结点的平衡因子等于1或者-1,此时不需要继续往上更新。没删除结点时,结点的平衡因子等于0,这个结点肯定有儿子结点,并且一定左右儿子结点都存在,因为删除的就是这个结点的儿子结点。结点的高度并没有变化,对上面结点不会产生影响。
if (parent->_bf == 1 || parent->_bf == -1){//高度没变
    break;
}
  • 更新完结点的平衡因子等于2或者-2,此时超过平衡因子的界限,需要旋转处理。

重点说一下,更新完平衡因子等于2的情况。

????????平衡因子等于2或者-2,需要进行旋转处理。旋转是将高度相对另外一边高得一边的高度降下来。

? ? ? ? 我们需要通过当前结点高的一边的子节点来判断用什么旋转方式。

? ? ? ? 我们删除的是cur结点,更新的是parent结点。如下图。

?????????所以我们需要得到删除结点的另外一边结点,来判断用哪种旋转。此时cur不代表删除结点了。需要用一个结点保存cur结点,方便最后删除。

//最后删除
Node *tail = cur;

//cur所在子树高度降低,另外一边相对时升高了,看升高这边
if (parent->_kv.first > cur->_kv.first){
    cur = parent->_right;
}
else{
    cur = parent->_left;
}

此时使用的旋转,处理插入的四种情况外,还增加了两种情况。

if (parent->_bf == 2 && cur->_bf == 1){//需要继续往上更新
							
	SigelLeft(parent);
	cur = parent->_parent;
	parent = cur->_parent;
}
else if (parent->_bf == 2 && cur->_bf == 0){
    SigelLeft(parent);
    parent->_bf = 1;
    parent->_parent->_bf = -1;
    break;
}
else if (parent->_bf == 2 && cur->_bf == -1){//需要继续往上更新
							
    DoubleRightLeft(parent);
    cur = parent->_parent;
    parent = cur->_parent;
							
}

else if (parent->_bf == -2 && cur->_bf == -1){//需要继续往上更新
							
    SigelRight(parent);
    cur = parent->_parent;
    parent = cur->_parent;
}
else if (parent->_bf == -2 && cur->_bf == 0){
    SigelRight(parent);
    parent->_bf = -1;
    parent->_bf = 1;
    break;
}
else if (parent->_bf == -2 && cur->_bf == 1){//需要继续往上更新
							
    DoubleLeftRight(parent);
    cur = parent->_parent;
    parent = cur->_parent;
}

????????首先说明,插入的四种情况还需要继续往上更新。了解旋转就知道,插入的四种情况旋转完后,根节点的平衡因子从不平衡都变成了0,没更新前平衡因子肯定是-1或者1(不然怎么更新到2的),此时高度降低了。需要继续往上更新。

想了解这四种情况的可以看博客:进一步理解平衡二叉树

? ? ? ? 还多出两种情况:

  • parent->_bf == 2 && cur->_bf == 0,父结点为2,当前结点为0。

????????此时只需要进行左单旋即可,但是,注意要重新更新parent和cur的平衡因子。

? ? ? ? 由于结点没更新前的平衡因子就是1或者-1,旋转后,结点的平衡因子还是1或者-1,高度没变。不需要继续往上更新。

  • parent->_bf == -2 && cur->_bf == 0,父节点(更新的)平衡因子等于-2,当前结点的平衡因子等于0。

?????????此时只需要进行右单旋即可,但是,注意要重新更新parent和cur的平衡因子。

? ? ? ? 由于结点没更新前的平衡因子就是1或者-1,旋转后,结点的平衡因子还是1或者-1,高度没变。不需要继续往上更新。

三.完整代码

#pragma once
#include<iostream>
using namespace std;

//二叉树应用KV模型
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结构体
	pair<K, V> _kv;
};

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K,V> Node;
public:
	bool insert(const pair<K, V> kv)
	{
		if (_root == nullptr){
			_root = new Node(kv);
			return true;
		}
		Node *cur = _root;
		Node *parent = nullptr;
		while (cur)
		{
			if (kv.first > cur->_kv.first){
				parent = cur;
				cur = cur->_right;
			}
			else if (kv.first < cur->_kv.first){
				parent = cur;
				cur = cur->_left;
			}
			else{
				return false;
			}
		}
		//找到插入位置,现在进行插入
		cur = new Node(kv);
		//要更新父节点
		cur->_parent = parent;
		if (parent->_kv.first < kv.first){
			
			parent->_right = cur;
		}
		else{
			
			parent->_left = cur;
		}

		//更新平衡因子
		while (parent){
			//如果插入左边平衡因子--
			if (parent->_left == cur){
				parent->_bf--;
			}
			//如果插入右边,平衡因子++
			else{
				parent->_bf++;
			}

			//判断平衡因子
			if (parent->_bf == 0){//如果parent位置平衡因子等于0,不再往上更新,高度没变
				break;
			}
			else if (parent->_bf == 1 || parent->_bf == -1){//高度变了,但是没有不平衡,继续往上更新
				cur = parent;
				parent = parent->_parent;
			}
			else if (parent->_bf == 2 || parent->_bf == -2){//不平衡需要旋转
				if (parent->_bf == 2 && cur->_bf == 1){
					SigelLeft(parent);//左单旋
				}
				else if (parent->_bf == 2 && cur->_bf == -1){
					DoubleRightLeft(parent);//右左双旋
				}
				else if (parent->_bf == -2 && cur->_bf == 1){
					DoubleLeftRight(parent);//左右双旋
				}
				else if (parent->_bf == -2 && cur->_bf == -1){
					SigelRight(parent);//右单旋
				}
				break;//旋转完不需要更新平衡因子了。

			}
			else{
				
			}
		}
		return true;


	}
	bool erase(pair<K,V> kv){
		//按二叉搜索树删除,但是先不释放空间,为了后面作比较
		Node *cur = _root;
		Node *parent = nullptr;
		while (cur){
			//删除结点小于当前结点键值,往左边找
			if (cur->_kv.first > kv.first){
				parent = cur;
				cur = cur->_left;
			}
			//删除结点大于当前结点键值,往右边找
			else if (cur->_kv.first < kv.first){
				parent = cur;
				cur = cur->_right;
			}
			else{
				//找到,四种情况
				if (cur->_left == nullptr&&cur->_right == nullptr){//左右都空
					//为根节点
					if (parent == nullptr){
						delete cur;
						_root = nullptr;
						return true;
					}
					//直接删除
					if (parent->_left == cur){
						
						parent->_left = nullptr;
						
					}
					else{
						
						parent->_right = nullptr;
					}					
					
				}

				if (cur->_left == nullptr&&cur->_right){//左空,右不空
					//为根节点
					if (parent == nullptr){
						_root = cur->_right;
						delete cur;
						return true;
					}
					//父节点指向右边
					if (parent->_left == cur){
						cur->_right->_parent = parent;
						parent->_left = cur->_right;
					}
					else {
						cur->_right->_parent = parent;
						parent->_right = cur->_right;
					}
				}

				if (cur->_left&&cur->_right == nullptr){//右空,左不空
					//根节点
					if (parent == nullptr){
						_root = cur->_left;
						delete cur;
						return true;
					}
					//
					if (parent->_left == cur){
						cur->_left->_parent = parent;
						parent->_left = cur->_right;
					}
					else {
						cur->_left->_parent = parent;
						parent->_right = cur->_right;
					}
				}
				if (cur->_left&&cur->_right){、
					Node *prev = cur;
					parent = cur;
					cur = cur->_right;

					while (cur->_left){
						parent = cur;
						cur = cur->_left;
					}
					K k = cur->_kv.first;
					V v = cur->_kv.second;
					prev->_kv.first = k;
					prev->_kv.second = v;

					if (parent->_left == cur){
						if (cur->_right){
							cur->_right->_parent = parent;
						}
						
						parent->_left = cur->_right;
					}
					else{
						if (cur->_right){
							cur->_right->_parent = parent;
						}
						parent->_right = cur->_right;
					}

				}
				//最后删除
				Node *tail = cur;
				
				while (parent){
					//更新平衡因子
					if (cur->_kv.first < parent->_kv.first){
						parent->_bf++;
					}
					else{
						parent->_bf--;
					}

					//检测平衡因子
					if (parent->_bf == 1 || parent->_bf == -1){//高度没变
						break;
					}
					else if (parent->_bf == 0){//高度降低,据徐更新
						cur = parent;
						parent = parent->_parent;
					}
					else if (parent->_bf == 2 || parent->_bf == -2){
						//cur所在子树高度降低,另外一边相对时升高了,看升高这边
						if (parent->_kv.first > cur->_kv.first){
							cur = parent->_right;
						}
						else{
							cur = parent->_left;
						}

						if (parent->_bf == 2 && cur->_bf == 1){//需要继续往上更新
							
							SigelLeft(parent);
							cur = parent->_parent;
							parent = cur->_parent;
						}
						else if (parent->_bf == 2 && cur->_bf == 0){
							SigelLeft(parent);
							parent->_bf = 1;
							parent->_parent->_bf = -1;
							break;
						}
						else if (parent->_bf == 2 && cur->_bf == -1){//需要继续往上更新
							
							DoubleRightLeft(parent);
							cur = parent->_parent;
							parent = cur->_parent;
							
						}

						else if (parent->_bf == -2 && cur->_bf == -1){//需要继续往上更新
							
							SigelRight(parent);
							cur = parent->_parent;
							parent = cur->_parent;
						}
						else if (parent->_bf == -2 && cur->_bf == 0){
							SigelRight(parent);
							parent->_bf = -1;
							parent->_bf = 1;
							break;
						}
						else if (parent->_bf == -2 && cur->_bf == 1){//需要继续往上更新
							
							DoubleLeftRight(parent);
							cur = parent->_parent;
							parent = cur->_parent;
						}
						

					}

				}
				delete tail;
				return true;

			}
		}
		//没找到
		return false;


	}

	void SigelLeft(Node *parent){
		Node *SubR = parent->_right;
		Node *SubRL = SubR->_left;

		Node *Pparent = parent->_parent;
		//注意要更新父节点
		parent->_right = SubRL;
		if (SubRL){
			SubRL->_parent = parent;
		}

		SubR->_left = parent;
		parent->_parent = SubR;

		//更新新根节点SubR的父节点
		if (Pparent == nullptr){//当前结点为根节点
			//直接置空
			SubR->_parent = nullptr;
			//注意要更新根节点
			_root = SubR;
		}
		else{//当前树为子树
			//连接到上面结点
			if (Pparent->_kv.first < SubR->_kv.first){
				Pparent->_right = SubR;
				
			}
			else{
				Pparent->_left = SubR;
			}

			//更新父节点
			SubR->_parent = Pparent;
		}

		SubR->_bf = 0;
		parent->_bf = 0;
	}

	void SigelRight(Node *parent){
		Node *SubL = parent->_left;
		Node *SubLR = SubL->_right;

		Node *Pparent = parent->_parent;

		parent->_left = SubLR;
		if (SubLR){
			SubLR->_parent = parent;
		}

		SubL->_right = parent;
		parent->_parent = SubL;

		if (Pparent == nullptr)//根节点
		{
			
			SubL->_parent = nullptr;
			_root = SubL;
		}
		else//子树
		{
			if (Pparent->_kv.first > SubL->_kv.first){
				Pparent->_left = SubL;
			}
			else{
				Pparent->_right = SubL;
			}
			SubL->_parent = Pparent;
		}

		SubL->_bf = 0;
		parent->_bf = 0;
	}

	void DoubleRightLeft(Node *parent){
		Node *SubR = parent->_right;
		Node *SubRL = SubR->_left;

		//为了后面更新平衡因子,看插入左边还是右边
		int bf = SubRL->_bf;

		SigelRight(SubR);

		SigelLeft(parent);
		//画图理解,更新平衡因子
		if (bf == 1){//插入方向在右边
			SubR->_bf = 0;
			SubRL->_bf = 0;
			parent->_bf = -1;
		}
		else if (bf == -1){//插入方向在左边
			SubRL ->_bf= 0;
			parent->_bf = 0;
			SubR->_bf = 1;
		}
		else if (bf == 0){//SubRL就是插入结点
			SubR->_bf = 0;
			SubRL->_bf = 0;
			parent->_bf = 0;
		}
	}

	void DoubleLeftRight(Node *parent){
		Node *SubL = parent->_left;
		Node *SubLR = SubL->_right;

		int bf = SubLR->_bf;

		SigelLeft(SubL);

		SigelRight(parent);

		if (bf == 1){
			SubL->_bf = -1;
			parent->_bf = 0;
			SubLR->_bf = 0;
		}
		else if (bf == -1){
			parent->_bf = 1;
			SubL->_bf = 0;
			SubLR->_bf = 0;
		}
		else if (bf == 0){
			parent->_bf = 0;
			SubL->_bf = 0;
			SubLR->_bf = 0;
		}
	}


	int _Height(Node *root){
		if (root == nullptr){
			return 0;
		}
		int left = _Height(root->_left);
		int right = _Height(root->_right);

		return left > right ? left + 1 : right + 1;
	}

	bool _IsBalanceTree(Node *pRoot)
	{


		// 空树也是AVL树
		if (nullptr == pRoot) return true;
		// 计算pRoot节点的平衡因子:即pRoot左右子树的高度差
		int leftHeight = _Height(pRoot->_left);
		int rightHeight = _Height(pRoot->_right);
		int diff = rightHeight - leftHeight;
		// 如果计算出的平衡因子与pRoot的平衡因子不相等,或者
		// pRoot平衡因子的绝对值超过1,则一定不是AVL树
		if (diff != pRoot->_bf || (diff > 1 || diff < -1))
			return false;
		// pRoot的左和右如果都是AVL树,则该树一定是AVL树
		return _IsBalanceTree(pRoot->_left) && _IsBalanceTree(pRoot->_right);
	}

	bool Isbalance(){
		return _IsBalanceTree(_root);
	}
    //中序遍历
	void _InOrder(Node *root){
		if (root){
			_InOrder(root->_left);
			cout << root->_kv.first;
			_InOrder(root->_right);
		}
	}

	void InOrder(){
		_InOrder(_root);
		cout << endl;
	}

private:
	Node *_root = nullptr;
};

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-09-06 11:24:08  更:2021-09-06 11:25:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 0:44:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码