一、思路
1.创建AVL树:
每次输入一个数num,然后用Insert()函数,将num插入到AVL树中。
// 创建AVL平衡树
template <class T>
void AVLTree<T>::CreateAVLTree() {
T num;
cout << "请输入数(以-1结束输入)建立平衡二叉树:" << endl;
while(cin >> num && num != -1)
Insert(num); // 插入到树中
}
2.插入值x:
(1)寻找插入的位置:如果AVL树已经存在该x值,则不插入。如果x小于当前结点的值,则往左子树中找插入位置;否则,往右子树找插入位置。
// 1.寻找插入位置
while(p != NULL) { // 树不为空
if(x == p->data) // 已经存在该值,不用再插入
return false;
pre = p; // 保存当前结点
st.push(pre); // 用栈记忆查找路径
if(x < p->data) // 要插入的值小于当前结点的值,往左子树继续查找
p = p->left;
else // x大于当前结点的值
p = p->right; // 往右子树查找
}
(2)把x插入到AVL平衡树中:如果x小于父结点的值,则插入到父结点的左孩子位置;否则, 插入到父结点的右孩子上。
// 2.找到了准备插入的位置,把x插入到AVL平衡树中
p = new AVLNode<T>(); // 创建新结点
p->data = x; // 存储要插入的值
p->bf = 0; // 新结点,平衡因子为0
p->left = p->right = NULL;
if(p == NULL) {
cerr << "存储空间分配失败!" << endl;
exit(1);
}
if(pre == NULL) { // 空树,新结点成为根结点
root = p;
return true;
}
if(x < pre->data) // 插入到左子树
pre->left = p;
else
pre->right = p; // 插入到右子树
(3)重新平衡化AVL树:因为插入的新结点,可能导致AVL树平衡被破坏。
从插入的新结点的父结点开始往根结点调整。如果当前结点是父结点的左孩子,则父结点的平衡因子要减1,如果当前结点是父结点的右孩子,则父结点的平衡因子要加1。
然后,通过父结点parent的平衡因子来决定是否需要平衡化旋转:
A. 父结点parent的平衡因子为0。说明刚才是在parent的较矮的子树上插入了新结点,父结点处平衡,且高度没有增减。此时从parent到根的路径上各结点为根的子树高度不变,从而各个结点的平衡因子不变,可以结束重新平衡化的处理。
B.父结点parent的平衡因子的绝对值|bf|=1。说明插入前parent结点的平衡因子为0,插入新结点后,以parent为根的子树没有失去平衡,不需要平衡化旋转。但该子树的高度增加,还需从结点parent向根的方向回溯,继续考查结点parent的父结点的平衡状态。
C.父结点parent的平衡因子的绝对值|bf|=2。说明新结点在较高的子树上插入,造成了不平衡,需要做平衡化旋转。旋转要分情况讨论。旋转后以parent为根的子树高度降低,因此无需继续向上层回溯。
// 3.重新平衡化AVL树
while(st.empty() == false) { // 栈不空
pre = st.top(); // 取栈顶元素
st.pop();
if(p == pre->left) // 如果是左孩子结点,则父结点平衡因子减1
pre->bf--; // 调整父结点的平衡因子
else
pre->bf++; // 如果是右孩子结点,则父结点平衡因子加1
if(pre->bf == 0) // 第1种情况,平衡退出
break;
else if(pre->bf == 1 || pre->bf == -1) // 第2种情况,|bf|&