数据结构实现 10.1:AVL树(C++版)

这篇博客介绍了AVL树的基本概念和实现,包括增加、删除、查找、遍历等操作。AVL树是一种自平衡二分搜索树,通过旋转操作保持平衡,确保查找、插入和删除操作的时间复杂度为O(logn)。文章详细阐述了增加操作的四种修正情况(左左、右右、左右、右左)以及删除操作的四种情况,并提供了相应的C++代码实现。
摘要由CSDN通过智能技术生成

1. 概念及基本框架

AVL树 (即自平衡树)是一种二分搜索树,AVL 树要求每个结点的左右子树的树高度差不超过 1

AVL树

因为在二分搜索树中,不管二叉树左右子树的高度,所以二分搜索树的最坏情况可以退化成链表。所以这里我们需要对二分搜索树进行适当的修正,提高效率。
与二分搜索树类似,我们先给出AVL树的结点类:

template <typename K, typename V>
class AVLNode{
   
public:
	AVLNode(K key, V value){
   
		this->key = key;
		this->value = value;
		height = 1;
		left = NULL;
		right = NULL;
	}
public:
	K key;
	V value;
	int height;
	AVLNode<K, V> *left;
	AVLNode<K, V> *right;
};

这里我们增加了高度变量,用来记录每个结点的当前高度。
下面给出AVL树大体框架:

template <typename K, typename V>
class AVLTree{
   
public:
	AVLTree(){
   
		root = NULL;
		m_size = 0;
	}
	...
private:
	AVLNode<K, V> *root;
	int m_size;
};

与二分搜索树十分类似,所以这里不再赘述。
接下来我们就对AVL树的增加、删除、查找、遍历以及一些其他基本操作用代码去实现。

2. 基本操作程序实现

2.1 增加操作

既然AVL树是二分搜索树的一种,所以其基本实现代码与二分搜索树类似,我们直接给出:

template <typename K, typename V>
class AVLTree{
   
public:
	...
	//增加操作
	void add(K key, V value);
	...
private:
	AVLNode<K, V>* add(AVLNode<K, V>* node, K key, V value);
	...
};

类外实现代码:

template <typename K, typename V>
void AVLTree<K, V>::add(K key, V value){
   
	root = add(root, key, value);
}
template <typename K, typename V>
AVLNode<K, V>* AVLTree<K, V>::add(AVLNode<K, V>* node, K key, V value){
   
	if (node == NULL){
   
		m_size++;
		return new AVLNode<K, V>(key, value);
	}
	else if (key < node->key){
   
		node->left = add(node->left, key, value);
	}
	else if (key > node->key){
   
		node->right = add(node->right, key, value);
	}
	else if (key == node->key){
   
		node->value = value;
		return node;
	}
	//计算平衡因子
	node->height = max(getHeight(node->left), getHeight(node->right)) + 1;
	int balanceFactor = getBalanceFactor(node);
	//平衡维护
	if (balanceFactor > 1 && getBalanceFactor(node->left) >= 0){
   
		return rightRotate(node);
	}
	else if (balanceFactor < -1 && getBalanceFactor(node->right) <= 0){
   
		return leftRotate(node);
	}
	else if (balanceFactor > 1 && getBalanceFactor(node->left) < 0){
   
		node->left = leftRotate(node->left);
		return rightRotate(node);
	}
	else if (balanceFactor < -1 && getBalanceFactor(node->right) > 0){
   
		node->right = rightRotate(node->right);
		return leftRotate(node);
	}
	return node;
}

这里需要注意的是在进行完增加操作之后我们需要进行一定的修正。修正之前需要得到一些基础信息,用下面几个函数来实现。
max 返回其中的最大值。
getHeight 返回一个结点的高度。
getBalanceFactor 返回一个结点的平衡因子。(用左子树高度减去右子树高度)
leftRotaterightRotate 分别表示左旋、右旋,具体实现方法后面详细讲述。
前面三个函数代码如下:

template <typename K, typename V>
class AVLTree{
   
	...
private:
	...
	int max(int a, int b){
    return a > b ? a : b; }
	...
};
template <typename K, typename V>
int AVLTree<K, V>::getHeight(AVLNode<K, V>* node){
   
	if (node == NULL){
   
		return 0;
	}
	return node->height;
}
template <typename K, typename V>
int AVLTree<K, V>::getBalanceFactor(AVLNode<K, V>* node){
   
	int lHeight = 0, rHeight = 0;
	if (node == NULL){
   
		return 0;
	}
	if (node->left != NULL){
   
		lHeight = node->left->height;
	}
	if (node->right != NULL){
   
		rHeight = node->right->height;
	}
	return lHeight - rHeight;
}

接下来我们详细讲述修正的左旋和右旋函数。
首先我们分情况,当一个新增加的结点需要修正,一定是其祖父结点一脉单传到了新结点。那么,作为一个新增加的结点,有两种可能,在父结点的左/右,而父结点也可以在祖父节点的左/右,所以一共有四种。我们可以简称为左左、左右、右左、右右四种情况。

2.1.1 左左

左左

注:这里每个结点都挂有子树(子树可以为空),这些子树本身已经是平衡的了(下同,这里考虑的是修正自下而上递推到这里),我们可以忽略。
如上图,结点 C 左右子树平衡因子为 2 > 1 ,所以需要修正,修正过程如下:
1.新建一个临时结点 P 指向 B 的右子结点。
2.把 C 作为 B 的右子结点。
3.把 P 作为 C 的左子结点。
4.将结点 B 返回。
经过旋转,结点的数据依旧满足二分搜索树的基本性质,所以可以旋转,这里结点 B 和结点 C 发生了 右旋
图中虚线圈住的结点表示实际发生了旋转的结点,橙色(紫色)的线表示发生变化的支,黄色的线表示返回结点的指向,后面不再赘述。
右旋代码实现如下:

template <typename K, typename V>
AVLNode<K, V>* AVLTree<K, V>::rightRotate(AVLNode<K, V>* node){
   
	AVLNode<K, V>* res =
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值