在我看来,在数据结构中,存在三个最经典的树型结构,他们分别是二叉排序树(BST),二叉平衡树(AVL) 和 红黑树(BRT)。
二叉排序树又叫做二叉搜索树,它可以是一棵空树,也可以是具有三个性质的一棵二叉树。
性质一:若它的左子树不为空,则左子树上的所有结点的值均小于它的根结点的值
性质二: 若它的右子树不为空,则右子树上的所有结点的值均大于它的根结点的值
性质三: 左右子树也是二叉排序树。
构造二叉搜索树的目的是提高查找和插入删除关键字的速度。因为二叉排序树是一个排序好的有序数据集,查找的速度肯定是快于无需数据集的,所以在考虑一些具体的问题时,一定要记得二叉排序树的特性,就比如在100w个数,找到一个给定的值,就非常的快,不过后面有更优化的方法。
这里只给出二叉搜索树的查找和插入代码
template<class T>
struct BSTNode
{
BSTNode(const T& data = T())
: _pLeft(nullptr) , _pRight(nullptr), _data(data)
{}
BSTNode<T>* _pLeft;
BSTNode<T>* _pRight;
T _data;
};
template<class T>
template<class T>
class BSTree
{
typedef BSTNode<T> Node;
typedef Node* PNode;
public:
BSTree(): _pRoot(nullptr)
{}
// 同学们自己实现,与二叉树的销毁类似
~BSTree();
PNode find(const T& key)
{
PNode cur = _pRoot;
while (cur != nullptr && cur->_data != key) //当查找元素不为空且与根节点不同
{
if (key < cur->_data) //小于根节点左子树找
{
cur = cur->_pLeft;
}
else //大于根节点右子树找
{
cur = cur-> _pRight;
}
}
if (cur == nullptr) //若cur=NULL则说明遍历完没有找到,
return nullptr;
return cur;
}
bool Insert(const T& data)
{
// 如果树为空,直接插入
if (nullptr == _pRoot)
{
_pRoot = new Node(data);
return true;
}
// 按照二叉搜索树的性质查找data在树中的插入位置
PNode pCur = _pRoot;
// 记录pCur的双亲,因为新元素最终插入在pCur双亲左右孩子的位置
PNode pParent = nullptr;
while (pCur)
{
pParent = pCur;
if (data < pCur->_data)
pCur = pCur->_pLeft;
else if (data > pCur->_data)
pCur = pCur->_pRight; // 元素已经在树中存在
else
return false;
}
// 插入元素
pCur = new Node(data);
if (data < pParent->_data)
pParent->_pLeft = pCur;
else
pParent->_pRight = pCur;
return true;
}
private:
PNode _pRoot;
};
二叉排序树是以链接的方式来存储,对于插入删除是非常方便的,操作时不需要移动元素的优点,插入删除的时间性能比较好。而对于查找的性能就取决于树的形状,问题就在二叉排序树的形状是不确定的。
例如要查找结点99,但是左图只需要两次比较,右图要比较10次。

所以对于这种情况,就需要更好的数据结构来进行查找操作。保证二叉排序的高度平衡,这时就出现了平衡二叉树。
如何保证高度平衡,肯定是有一个很关键的条件,被称为平衡因子BF。平衡因子的定义是二叉树上的结点的左子树的深度减去右子树的深度的值。平衡因子只可能是-1,0,1,即二叉树结点的平衡因子的绝对值不能大于1。
平衡二叉树构建的基本思想是在构建二叉排序树的过程中,每当插入一个新的节点,先检查是否因插入而破坏了树的平衡性,如果是,则找出最小不平衡子树。在保持二叉排序树特性的前提下,调整最小不平衡树中各结点之间链接关系,进行相应的旋转,使之变成新的平衡子树。
AVL树结点
template<class T>
struct AVLTreeNode
{
AVLTreeNode(const T& data)
: _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr)
, _data(data), _bf(0)
{}
AVLTreeNode<T>* _pLeft; // 该节点的左孩子
AVLTreeNode<T>* _pRight; // 该节点的右孩子
AVLTreeNode<T>* _pParent; // 该节点的双亲
T _data;
int _bf; // 该节点的平衡因子
};
旋转分为四种:左旋、右旋、先左旋再右旋、先右旋再左旋。

经过平衡因子的计算与比较和数次旋转,图1的不平衡的二叉排序树变成了图2的高度平衡的二叉排序树。
AVL树的查找效率非常高,时间复杂度为O(logN),但是如果要对AVL树做一些结构修改的操作,性能非常低下。
比如:
插入时要维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
因此如果需要一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修改,就不太适合。

本文探讨了二叉排序树、平衡二叉树的概念,强调了平衡因子在保持树平衡中的作用。AVL树作为高度平衡的二叉排序树,其查找效率高,但插入和删除操作可能导致频繁旋转,适用于静态数据结构。
463

被折叠的 条评论
为什么被折叠?



