(PAT)1123.[平衡树旋转] Is It a Complete AVL Tree

PAT最后一题还是挺看运气的,考满分的确不容易(菜鸡的感叹!!!)

这是一道关于平衡二叉树旋转的问题,可以说非常基础,题目也没啥特别的变化,但奈何徒手码不粗来,大写的尴尬啊。

1123. Is It a Complete AVL Tree (30)

时间限制
400 ms
内存限制
65536 kB
代码长度限制
16000 B
判题程序
Standard
作者
CHEN, Yue

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.

    

    

Now given a sequence of insertions, you are supposed to output the level-order traversal sequence of the resulting AVL tree, and to tell if it is a complete binary tree.

Input Specification:

Each input file contains one test case. For each case, the first line contains a positive integer N (<= 20). 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, insert the keys one by one into an initially empty AVL tree. Then first print in a line the level-order traversal sequence of the resulting AVL tree. All the numbers in a line must be separated by a space, and there must be no extra space at the end of the line. Then in the next line, print "YES" if the tree is complete, or "NO" if not.

Sample Input 1:
5
88 70 61 63 65
Sample Output 1:
70 63 88 61 65
YES
Sample Input 2:
8
88 70 61 96 120 90 65 68
Sample Output 2:
88 65 96 61 70 90 120 68
NO
大致题意:依次插入几个节点,你判断最后是否成为了完全二叉树,如果是,依次输出每一层的节点和YES,否则依次输出每一层的节点和NO。

解题思路:一个”简单的“(强行不要脸)平衡树旋转问题。


我们先来看一下什么是平衡二叉树以及它的旋转。

二叉树大家都知道,二叉查找树就是满足左子节点小于父节点二右子节点大于父节点的二叉树(左小右大),很好理解,这样便于查找。但是有时候这种树会有不平衡的情况,如下图:


这时候,我们的查找效率就和不维护这棵树差不多了。这个时候我们就需要旋转来得到平衡二叉树。那什么是平衡二叉树大家应该也能猜到了吧!

摘自度娘百科:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

对于一个平衡的节点,由于任意节点最多有两个儿子,因此高度不平衡时,此节点的两颗子树的高度差2.容易看出,这种不平衡出现在下面四种情况:

  1、6节点的左子树3节点高度比右子树7节点大2,左子树3节点的左子树1节点高度大于右子树4节点,这种情况成为左左

  2、6节点的左子树2节点高度比右子树7节点大2,左子树2节点的左子树1节点高度小于右子树4节点,这种情况成为左右

  3、2节点的左子树1节点高度比右子树5节点小2,右子树5节点的左子树3节点高度大于右子树6节点,这种情况成为右左

  4、2节点的左子树1节点高度比右子树4节点小2,右子树4节点的左子树3节点高度小于右子树6节点,这种情况成为右右

稍微总结一下就是(左左)总体左边高子树里也是左边高,(左右)总体左子树高子树里右边高,后面同理。

单旋转:

单旋转是针对于左左和右右这两种情况的解决方案,这两种情况是对称的,只要解决了左左这种情况,右右就很好办了。图3是左左情况的解决方案,节点k2不满足平衡特性,因为它的左子树k1比右子树Z深2层,而且k1子树中,更深的一层的是k1的左子树X子树,所以属于左左情况。


  为使树恢复平衡,我们把k2变成这棵树的根节点,因为k2大于k1,把k2置于k1的右子树上,而原本在k1右子树的Y大于k1,小于k2,就把Y置于k2的左子树上,这样既满足了二叉查找树的性质,又满足了平衡二叉树的性质。

  这样的操作只需要一部分指针改变,结果我们得到另外一颗二叉查找树,它是一棵AVL树,因为X向上一移动了一层,Y还停留在原来的层面上,Z向下移动了一层。整棵树的新高度和之前没有在左子树上插入的高度相同,插入操作使得X高度长高了。因此,由于这颗子树高度没有变化,所以通往根节点的路径就不需要继续旋转了。

双旋转:

对于左右和右左这两种情况,单旋转不能使它达到一个平衡状态,要经过两次旋转。双旋转是针对于这两种情况的解决方案,同样的,这样两种情况也是对称的,只要解决了左右这种情况,右左就很好办了。图4是左右情况的解决方案,节点k3不满足平衡特性,因为它的左子树k1比右子树Z深2层,而且k1子树中,更深的一层的是k1的右子树k2子树,所以属于左右情况。

   为使树恢复平衡,我们需要进行两步,第一步,把k1作为根,进行一次右右旋转,旋转之后就变成了左左情况,所以第二步再进行一次左左旋转,最后得到了一棵以k2为根的平衡二叉树树。

最后上代码:(注意看注释)

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <string>
#include <set>
using namespace std;

const int maxn = 30;

typedef struct Node* AVLTree;
struct Node{
    int key;
    Node* left;
    Node* right;
    int height;
    Node(int k):key(k), left(NULL), right(NULL), height(0){}
    Node(){}
};
int n;

int height(Node *node){
    return node ? node->height : -1;
}

Node* LL(Node* k2){
    Node* k1 = k2->left;
    k2->left = k1->right;
    k1->right = k2;

    k2->height = max(height(k2->left), height(k2->right)) + 1;
    k1->height = max(height(k2->left), k2->height) + 1;

    return k1;
}
Node* RR(Node* k2){
    Node* k1 = k2->right;
    k2->right = k1->left;
    k1->left = k2;

    k2->height = max(height(k2->left), height(k2->right)) + 1;
    k1->height = max(height(k1->right), k2->height) + 1;

    return k1;
}
Node* LR(Node* k3){
    k3->left = RR(k3->left);
    return LL(k3);
}
Node* RL(Node* k3){
    k3->right = LL(k3->right);
    return RR(k3);
}

Node* insert(AVLTree root, int key){
    if(!root) root = new Node(key); //为第一个元素时
    else if(root->key < key){
		//往右节点插入
        root->right = insert(root->right, key);
		//"此时的根"的右节点的高度比左节点大2
        if(height(root->right) - height(root->left) == 2){
            if(key > root->right->key) root = RR(root);//多余的节点在右边 即符合右右的情况
            else root = RL(root);
        }
    }
	
    else if(root->key > key){
        root->left = insert(root->left, key);
        if(height(root->left) - height(root->right) == 2){
            if(key < root->left->key) root = LL(root);
            else root = LR(root);
        }
    }

    root->height = 1 + max(height(root->left), height(root->right));
    return root;
}
//遍历二叉树
void travel(AVLTree root){
    queue<Node*> Q;
    vector<int> levelSeq;
    Q.push(root);

    while(!Q.empty()){ //左右左右一级级放入vector中
        root = Q.front(); Q.pop();
        levelSeq.push_back(root->key);
        if(root->left)   Q.push(root->left);
        if(root->right)  Q.push(root->right);
    }

    for(int i = 0; i < levelSeq.size(); ++i){
        if(i) cout << " ";
        cout << levelSeq[i];
    }
}
//判断是否是平衡二叉树
bool isCompleteAVLTree(AVLTree root){
    queue<Node*> Q;
    Q.push(root);
    bool haveNULL = false;

    while(!Q.empty()){
        root = Q.front(); Q.pop();
        if(haveNULL){ //父节点无兄弟节点或已出现无子节点的父节点
            if(root->left || root->right) return false;
        }
        if(root->left && root->right){//该节点有两个子节点
            Q.push(root->left);
            Q.push(root->right);
        }else if(root->left && !root->right){//该节点只有一个左节点
            Q.push(root->left);
            haveNULL = true;
        }else if(!root->left && root->right) return false;//该节点只有一个右节点 肯定不是平衡二叉树
        else haveNULL = true; //该节点无父结点
    }
    return true;
}

int main()
{
    cin >> n;
    int key;
    AVLTree root = NULL;
    for(int i = 0; i < n; ++i){
        cin >> key;
        root = insert(root, key);
    }

    travel(root);
    printf("\n%s", isCompleteAVLTree(root) ? "YES" : "NO");

    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值