AVL 树删除
定义
AVL树是一棵二叉查找树,平衡因子为-1,1,0.若平衡因子为2就必须进行平衡化。平衡化的过程就是左旋或者右旋,分LL,LR,RR,RL4种情况。
性能:
高度为:log(n),在最坏的情况下要比较O(logn)次。与其它的平衡树不同,当一个结点出现不平衡,可能会影响整棵树。
过程,比如一个数组为5,6,8,3,2,4,7。长度为7。那么插入的情况是
定义的头部为:
#define NULL 0
template<class T>
class AVL_Node//AVL树中节点结构
{
public:
T key;
AVL_Node<T>* leftChild;
AVL_Node<T> *rightChild;
AVL_Node<T> *parent;//为了平衡AVL树我们不得不加入父结点便于算法
AVL_Node(T key)
{
this->key=key;
leftChild=NULL;
rightChild=NULL;
parent=NULL;
}
};
template<class T>
class AVL_Tree//AVL树的数据结构
{
public:
int nodeNum;
AVL_Node<T> *root;
AVL_Tree()
{
nodeNum=0;
root=NULL;
}
void AVL_TREE_INSERT(AVL_Tree<T>*avlTree,T key);//插入
void AVL_TREE_DELETE(AVL_Tree<T>*avlTree,T key);//删除
bool RotateRight(AVL_Tree<T>*avlTree,AVL_Node<T>*node);//右旋
bool RotateLeft(AVL_Tree<T>*avlTree,AVL_Node<T>*node);//左旋
int AVL_TREE_HIGH(AVL_Node<T>*node);//计算树的高度
AVL_Node<T>* AVL_TREE_SEARCH(T key);
void AVL_TREE_BALANCE(AVL_Tree<T>*avlTree,AVL_Node<T>* node);
};
删除的代码:
最关键的是查找到待删除的结点。这个结点可能是原来的结点,也可能是前驱结点
找到待删除结点的孩子结点,将指针正确的复制。
具体代码:
template<class T>//删除结点
void AVL_Tree<T>::AVL_TREE_DELETE(AVL_Tree<T>*avlTree,T key)
{
//思想:1、找到这个结点
//2、通过拷贝删除法。进行值的替换。
//3、进行平衡化
AVL_Node<T>*index=avlTree->root;
AVL_Node<T>*delNodeChild=NULL;
while (index)//找到这个结点,并且获得该结点的父结点
{
if (key==index->key)
{
break;
}
else if (index->leftChild&&key<index->key)
{
index=index->leftChild;
}
else if(index->rightChild&&key>index->key)
{
index=index->rightChild;
}
}
AVL_Node<T>*delNode=index;//有可能这个就是叶子结点,进行直接删除。
if (index->leftChild==NULL||index->rightChild==NULL)
{
delNode=index;
}
else //若两棵子树都存在。
{ //利用左子树中最大的结点进行替换,复制删除
delNode=index->leftChild;
while(delNode->rightChild)//找到待删除的结点,利用二叉树的中序的前驱。
{
delNode=delNode->rightChild;
}
}
//下面主要是找的哦啊待删除结点的孩子结点,若是采用前驱找那么势必不会有右子树,但是如果整棵树只剩下右子树,
//这时就必须用右子树来替换
if (delNode->leftChild)
{
delNodeChild=delNode->leftChild;
}
if(delNode->rightChild)
{
delNodeChild=delNode->rightChild;
}
if (delNodeChild)//若deleteNode的父结点存在,将孩子结点父指针指向这个指针。
{
delNodeChild->parent=delNode->parent;
}
if (delNode->parent==NULL)//如果这个结点已经是跟结点,那么孩子结点也是没有赋值的,就等于将根结点设置为NULL
{
avlTree->root=delNodeChild;
}
else//设置删除结点孩子结点的父结点。若是这个孩子结点的值为空,那么父结点的孩子也是为空的,
{ //这样在后面删除delNode时,虽然delete,但是指针值还是存在的,这是一个野指针,如此就可以设置值为空
if (delNode==delNode->parent->leftChild)
{
delNode->parent->leftChild=delNodeChild;
}
else if (delNode==delNode->parent->rightChild)
{
delNode->parent->rightChild=delNodeChild;
}
}
if (index!=delNode)//若是这个结点就是删除结点不需要进行交换
{
index->key=delNode->key;//对值进行交换。
}
if (nodeNum>2)//只有当数目大于2的时候才会有平衡化步骤
{
AVL_TREE_BALANCE(avlTree,delNode->parent);//待平衡化的结点为待删除节点的父结点
}
//delete delNode;
nodeNum--;
}
int main()
{
AVL_Tree<int>*avlTree=new AVL_Tree<int>();
int testArray[7]={5,6,8,3,2,4,7};
for (int i=0;i<7;i++)
{
avlTree->AVL_TREE_INSERT(avlTree,testArray[i]);
}
for (int i=0;i<7;i++)
{
avlTree->AVL_TREE_DELETE(avlTree,testArray[i]);
}
return 0;
}
平衡化的代码见前面的插入代码中
小结:
1、考虑的必须有可能待删除结点的孩子结点为空,这个时候父亲结点的孩子结点就是为NULL
2、待删除结点的父亲结点为空。那么孩子结点为根结点
3、平衡结点为父结点依旧要考虑当前只有一个结点的情况
4、虽然使用的前驱作为待删除结点,但是会出现只有右子树的情况,所以待删除结点中若左子树不存在,它指向的应该是右子树
最开始的时候使用一个prev的指针,并没有使用delNode,发现要考虑的情况更多
首当其冲的是野指针,没有delNode,指针指向的空间已经被删除,但是指针的值是存在的,如下面的代码中
int * pp = new int;
int *ppp=pp;
*pp = 16;
int *&p=pp;
delete p;
p=NULL;
这个时候将p设置为空,不存在野指针,但是pp呢?所以,*&ppp=pp,才能正确的删除。其次还有设置为Null
这就是只有指针与指针引用的不同。但是在我们的程序中,因为我们只是得到指针A中的值,虽然可以将所指向空间的内容清空,但是A中的值并没有为空。这个时候A就是野指针。