「1066」Root of AVL Tree

An AVL tree is a self-balancing binary search tree. In an AVL tree, the heights of the two child subtrees of any node differ by at most one; if at any time they differ by more than one, rebalancing is done to restore this property. Figures 1-4 illustrate the rotation rules.

F1.jpg F2.jpg

 

Now given a sequence of insertions, you are supposed to tell the root of the resulting AVL tree.

Input Specification:

Each input file contains one test case. For each case, the first line contains a positive integer N (≤20) which is the total number of keys to be inserted. Then N distinct integer keys are given in the next line. All the numbers in a line are separated by a space.

Output Specification:

For each test case, print the root of the resulting AVL tree in one line.

Sample Input 1:

5
88 70 61 96 120

Sample Output 1:

70

Sample Input 2:

7
88 70 61 96 120 90 65

Sample Output 2:

88

Ω

AVL,平衡二叉搜索树,不仅拥有BST的性质,同时每个顶点左右两棵子树的高度差不超过1,从而使树的高度尽可能维持在一个较低水平,提高搜索效率。给出一组数据,输出最终AVL的根节点,注意每个元素插入后都应及时调整为AVL。

当初看树这一块的时候,AVL的旋转调整让我头疼不已,现在亦是如此。苦苦思索,原来这是第一次撞上AVL的题目,于是我又回去重温了一遍《数据结构与算法分析》里的相关内容。

每次插入一个数后我们都从该树开始向根节点回溯,找到第一个左右子树高度差>1的节点X,只要将这棵子树恢复至插入前的高度即可重新满足AVL。

接下来我们将插入后不满足平衡性的情形分为两种(每种都包含两个镜像对称的情况):

  1. 向X节点的左(右)子节点的左(右)子树插入后不满足

    alt
  2. 向X节点的左(右)子节点的右(左)子树插入后不满足

    alt

我们用 来表示子树 来表示子树 的高度,首先显然

那么 不可能是空树,因为 。因此我们还可以将 进行拆分:

alt

其次 ,否则在插入之前就已经不满足平衡性,另外由于节点X是第一个不满足平衡性的节点,因此

综上所述, ,且

接下来,我们通过两种不同的旋转方法在不破环BST的前提下使其重新符合平衡性(序号与上述情形相对应,只考虑镜像对称中的一种情况):

  1. 拎住绿点向上提, 成为节点X的左子树

    alt

    显然满足平衡性,且BST的有序性也未被破坏

  2. 拎住节点R向上提成为根节点,想象每条边的端点都是有磁性的,在上提的过程中脱落自动吸附到其他节点上,绿节点和节点X成为其左右子树,而 成为绿节点的右子树, 成为X节点的左子树

    alt

    的高度关系无法确定,但注意到 ,因此依然可以肯定是符合平衡性要求的。


🐎

#include <iostream>
#include <map>
#include <vector>
#define diff(x) height[child[x].first]-height[child[x].second]
using namespace std;

map<int, pair<int, int>> child;
map<int, int> height;

int insert(int &node, int &num)
{
    if (node == -1)
    {
        node = num;
        height[num] = 1;
        child[num] = pair(-1, -1);
        return num;
    }
    if (num > node)
        child[node].second = insert(child[node].second, num);
    else
        child[node].first = insert(child[node].first, num);
    height[node] = max(height[child[node].first], height[child[node].second]) + 1;
    int head = node;
    if (abs(diff(node)) > 1)
    {
        bool isLeft = diff(node) > 0;
        int &inSon = isLeft ? child[node].second : child[node].first,
            &outSon = isLeft ? child[node].first : child[node].second,
            &inGson = isLeft ? child[outSon].second : child[outSon].first,
            &outGson = isLeft ? child[outSon].first : child[outSon].second;
        if ((diff(node) ^ diff(outSon)) > 0)
        {
            height[node] = max(height[inSon], height[inGson]) + 1;
            height[outSon] = max(height[node], height[outGson]) + 1;
            head = outSon;
            outSon = inGson;
            inGson = node;
        }
        else
        {
            int &inGGson = isLeft ? child[inGson].second : child[inGson].first,
                &outGGson = isLeft ? child[inGson].first : child[inGson].second;
            height[outSon] = max(height[outGson], height[outGGson]) + 1;
            height[node] = max(height[inSon], height[inGGson]) + 1;
            height[inGson] = max(height[node], height[outSon]) + 1;
            head = inGson;
            inGson = outGGson;
            outGGson = outSon;
            outSon = inGGson;
            inGGson = node;
        }
    }
    return head;
}

int main()
{
    int n, head, m;
    cin >> n >> head;
    child[head] = pair(-1, -1);
    height[head] = 1;
    for (int i = 1; i < n; ++i)
    {
        cin >> m;
        head = insert(head, m);
    }
    cout << head;
}

Tips

  1. 用几个引用来替代内/外侧的儿子、孙子、曾孙节点,从而无需考虑镜像对称的情况

  2. 用一个map来存储每个节点子树的高度,在进行旋转调整时需要对改变的节点高度进行维护

  3. insert函数完成插入操作,并对回溯过程中第一个不满足平衡性的节点子树进行旋转调整,结束后返回新子树的根节点

  4. 注意节点关系的赋值次序,否则可能出现将赋值后的变量当作老变量使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值