爆刷PAT(甲級)——之【1123】 Is It a Complete AVL Tree (30 分)——AVL樹建樹+層次遍歷+完全二叉樹的判斷

題意:給一個N以及序列(不用考慮元素重複情況)。然後根據這個建AVL樹,輸出AVL樹的層序遍歷,以及判斷此樹是否是完全二叉樹。

難點:這道題沒有什麼思路。題目很清晰,考點就是AVL建樹+層次遍歷+完全二叉樹的判斷。自己下手的時候才意識到AVL樹怎麼寫來着。。。就去記了一下。。。;層次遍歷很簡單的,就不多提了;如何判斷是否是完全二叉樹呢?對於完全二叉樹而言,有一個節點沒有孩子了,那之後的節點(層次遍歷順序上)肯定都沒有孩子了!寫代碼的時候,別忘了,按照先左後右的順序。而右子樹裏面的條件左子樹裏別忘了寫。。。忘了就直接一個點過不了,一個點8分呀,血虧!

 

參考博客有:

     1、柳神的寫法——https://blog.csdn.net/liuchuo/article/details/53561924

     2、另外兩位博主,一位有圖可以用來思考——https://blog.csdn.net/u014634338/article/details/42465089

          另一位有不錯的代碼總結表格,我也用來借鑑——https://blog.csdn.net/whucyl/article/details/17289841

 

AVL建樹的思考:

1、看了柳神的代碼以及別人的代碼,在節點的高度計算的方面主要是分兩個寫法——每個節點的h是用的時候迭代算還是在AVL發生旋轉更新的時候算出來並保存爲節點的一個參數。

我比較喜歡簡潔清晰的思維方式以及代碼書寫方式,但是不管哪個思路和寫法都差不多。。都是迭代更新。。。

對比了一下代碼量,emmm還不如直接用的時候迭代算呢。所以代碼是仿照柳神的代碼寫的。

2、AVL樹的旋轉上,不管是哪種旋轉,插入的位置和發生變動的根root並不是父子關係,而是爺孫關係!(高度差2!)這點比較重要,整個AVL樹的旋轉過程,我覺得可以理解成“新王交接,父承子業”的感覺

舉個栗子——以左旋和右旋來說,root節點是原來的老皇帝,它的兒子節點son是即將把它趕下去自己上的新皇帝,而我們插入的節點位置是新皇帝的孩子位置!而發生旋轉後呢,新王變成root,而原來的舊王(舊的root)就待在原來新王的位置(它還沒旋轉前的位置),接受了新王原來的孩子(也就是我們插入的節點)

   插入在root節點的左子樹的左側,就是右旋咯,我寫的函數是   ll();(就是左子樹的左子樹)

   插入在root節點的左子樹的右側,就和這個名字一樣了,先左旋再右旋咯,我寫的函數是   lr();(就是左子樹的右子樹)

   插入在root節點的右子樹的右側,就是左旋咯,我寫的函數是   rr();(就是右子樹的右子樹)

   插入在root節點的右子樹的左側,就是先右旋再左旋咯,我寫的函數是   rl();(就是右子樹的右子樹) 

在寫AVL建樹過程中,一定要搞清楚傳入的參數root是旋轉變換中的哪個節點(答案:舊王~是插入節點的爺爺——爸爸的爸爸

3、爲什麼大家寫的AVL建樹函數insert上都有一個步驟——root->left=insert(root->left)或者是root->right=nsert(root->right)呢?可以不按照這個迭代的寫法,刪掉這句嗎?

我試了一下,並不行,這個樹就建錯了。

仔細想了一下,這是爲什麼呢?

是因爲,如果我們插入了一個新的節點,沒發生旋轉那就萬事OK,但如果新的節點會引起旋轉,那也並不是在插入位置進行旋轉呀!我們上面講了,要旋轉也是插入點的父親(新王)和父親的父親(舊王)進行旋轉呀引入迭代一個是可以更好的進行旋轉結點指針的使用指派,帶來優化;另一個是新王和舊王的交替,會導致舊王的父親(插入節點的曾爺爺)的右指針需要更改維護!好好品味一下~所以,insert中需要迭代更新的步驟,是爲了使得旋轉後的節點能夠更新到更高層的樹上,不更新這個樹就斷了

 

Code:

代碼的核心就是AVL怎麼建樹的。

以及如何判斷是否是完全二叉樹部分。

 

#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
#define inf 209
#define INF 0x3f3f3f3f
#define loop(x,y,z) for(x=y;x<z;x++)
int n;
struct Node
{
    int value;
    Node *left,*right;
    Node(int v)
    {
        value=v;
        left=right=NULL;
    }
}*root=NULL;

//左旋和右旋的靈魂就是——新王交接,父承子業
//轉一次的root,就是舊王,新王是他兒子
//轉兩次的root,他的兒子是轉第一次的舊王,root自己是轉第二次的舊王
//ll中左子無敵,rr中右子無敵
//lr中左子必死;rl中右子必死


Node* ll(Node *root)//插在左子樹左邊,是右旋——root是舊王,左子新王
{
    Node *son=root->left;//造反的新王
    root->left=son->right;//舊王貶官,交接
    son->right=root;//新王摸狗頭
    return son;//返回新王
}
Node* rr(Node *root)//插在右子樹右邊,是左旋——root是舊王,右子新王
{
    //同上噢
    Node *son=root->right;
    root->right=son->left;
    son->left=root;
    return son;
}

//插在左子樹的右邊,是先左旋再右旋
//則root爲舊王,但是root的左子卻是左旋的舊王
Node* lr(Node *root)
{
    root->left=rr(root->left);//先左旋
    return ll(root);//再右旋

}

//插在右子樹的左邊,是先右旋再左旋
//則root爲舊王,但是root的右子卻是右旋的舊王
Node *rl(Node *root)
{
    root->right=ll(root->right);
    return rr(root);
}

//1、因爲AVL樹在插入過程中會進行旋轉,
//所以對於任何root而言,其左右子樹都可能發生旋轉,並使得root的左右孩子更改
//所以要左右孩子要保持迭代更新
//2、旋轉過程中,判斷是否發生旋轉,就是在高度差爲2的root和root的孩子上,要求高
//綜上所述要按照遞歸寫法
int getH(Node *root)//得到節點高
{
    if(!root)return 0;
    int l=getH(root->left);
    int r=getH(root->right);
    return max(l,r)+1;
}
Node* insert(Node *root,int value)
{
    if(!root)root=new Node(value);
    else if(value<root->value)//左邊
    {
        root->left=insert(root->left,value);//防止孩子旋轉,物是人非
        if(getH(root->left)-getH(root->right)==2)//因爲插在左子樹,所以要旋轉也是左子樹變長了
        {
            if(value<root->left->value)//在左子樹的左子樹上,是ll
                root=ll(root);//新的王
            else
                root=lr(root);
        }

    }
    else //右邊
    {
        root->right=insert(root->right,value);//防止孩子旋轉,物是人非
        if(getH(root->right)-getH(root->left)==2)//因爲插在右子樹,所以要旋轉也是右子樹變長了
        {
            if(value>root->right->value)//在右子樹的右子樹上,是rr
                root=rr(root);//新的王
            else
                root=rl(root);
        }

    }
    return root;
}

//vector<int>ans;
bool isComplete(Node *root)
{
    queue<Node*>q;
    while(!q.empty())q.pop();
    q.push(root);
    bool flag1=true;
    bool flag2=true;
    int num=0;
    while(!q.empty())
    {
        Node *t=q.front();
        q.pop();
        //輸出
        num++;
        if(num==n)printf("%d\n",t->value);
        else printf("%d ",t->value);

        if(t->left)
        {
            q.push(t->left);
            if(!flag1)flag2=false;//不要漏了
        }
        else flag1=false;
        if(t->right)
        {
            q.push(t->right);
            if(!flag1)flag2=false;//不要漏了
        }
        else flag1=false;
    }
    return flag2;
}

int main()
{
    int i,j;
    cin>>n;
    loop(i,0,n)
    {
        cin>>j;
        root=insert(root,j);
    }
    if(isComplete(root))
        printf("YES\n");
    else printf("NO\n");

    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章