數據結構之平衡二叉樹

1.從新平衡2子樹,體現了概括思想,以及簡單的建模思想。java

2.旋轉名字不太好理解。本身以爲 「沿襲」 更恰當。本身爲了進行沿襲這個動做。概括了2個定理:node

     1.父節點可垂直變爲其左孩子或者左孩子的左孩子。左右同理。函數

     2.子樹內部任意一支子樹可代替原子樹。測試

有這2個定理。就能夠不用理解書上的旋轉了,用本身的沿襲就能夠完成單旋轉和雙旋轉。畢竟大部分書,都只是告訴你如何旋轉,還不如本身建模,抽象,定義定理。來實現。spa

3.從新計算樹高度時,從變化的葉子開始要一直往父節點從新計算,因此很適合使用遞歸的方法,並保障從上往下時,是逐層往下,那麼就能夠保證插入和刪除之後,會逐層檢查子樹高度,以及對比是否須要旋轉日誌

  而旋轉後須要跟新子樹的父節點。因此參考書上的方法是有返回節點的,一邊遞歸回去的時候更新給父節點,而本身爲了方便裏面,沒喲使用返回值,而是多加參數的方法,不簡便,但好理解。code

 

概括和抽象建模思想體如今哪裏。第一步到第二步的轉變側重體現了概括,抽象,建模等基本思想。  第二步到第三步更體現了爲了解決實際問題,修改模型的能力。val是一個進行抽象思惟的練習的好例子,很是值得複習blog

 

 

 

 採用了2中不一樣的函數來完成 add .一個帶返回值,一個不帶返回值,而是使用參數。遞歸

package com.linson.datastrcture;





//本身的插入在遞歸中,並無和書上返回節點。本身感受本身無返回值的更好理解,代替方案就是放入一個頭節點的父節點。好理解,但也繁瑣點。
//遞歸返回根節點從代碼的簡潔上絕對更優,只是帶參數的方法更容易理解。
//avl這個例子很值得複習。1.遞歸時的問題模型的肯定  2.平衡樹時的建模思惟。 3.樹高的遞歸計算,遞歸影響。這個很不錯。4.compareble的系統庫接口使用。5.左小右大思路的利用。
//6,刪除時,須要平衡的時候會存在高的那一邊的左右子樹又同樣高。比父節點的兄弟都高2級。

public class MyAVL<T extends Comparable<T>>
{
    public static class MyAVLNode<E extends Comparable<E>>
    {
        public E mElement;
        public MyAVLNode<E> mLeftNode;
        public MyAVLNode<E> mRightNode;
        public int mHeight;
        
        public MyAVLNode(E _value,MyAVLNode<E> left,MyAVLNode<E> right)
        {
            mElement=_value;
            mLeftNode=left;
            mRightNode=right;
            mHeight=1;
        }
        
        public int compareTo(E other)
        {
            return mElement.compareTo(other);
        }
    }
    
    public MyAVL()
    {
        mRootNode=null;
    }
    
    //問題:插入節點到樹。組合:插入到節點,插入到左樹,插入到右樹. 基本值節點爲空。能夠插入。不須要再判斷是否繼續插入左或者右
    //節點高度默認是1,添加節點,必須逐層檢測父節點:左右子樹中最大值+1誰否大於現值? 大於要往上再檢查,一直到某上層沒變化。
    //因此返回值能夠改成返回是否須要檢查高度。可是想一下,又要增長返回值,又要判斷是否須要檢查,還不如每層都檢查,反正AVL的話,數據再大也不會很高。1024才11層。
    public void add(T element,MyAVLNode<T> subTreeNode,MyAVLNode<T> fatherNode,boolean isLeft)
    {
        if(subTreeNode==null)
        {
            MyAVLNode<T> TempNode=new MyAVLNode<T>(element, null, null);//節點和樹的泛型都實現了對比接口。因此樹的參數,能夠直接放入到節點中
            if(fatherNode!=null)
            {
                if(isLeft)
                {
                    fatherNode.mLeftNode=TempNode;
                }
                else 
                {
                    fatherNode.mRightNode=TempNode;
                }
            }
            else 
            {
                mRootNode=TempNode;
            }
        }
        else
        {
            boolean addIsBiger=subTreeNode.mElement.compareTo(element)<0;//泛型實現了對比接口
            if(addIsBiger)
            {
                add(element,subTreeNode.mRightNode,subTreeNode,false);
            }
            else 
            {
                add(element,subTreeNode.mLeftNode,subTreeNode,true);
            }
            RotationAndHeight(subTreeNode,fatherNode,isLeft);
        }
    }
    
    //問題:匹配一個樹,組合:匹配根,匹配左樹,匹配右樹。基本問題:節點就是。
    //空:直接刪,副節點設空。 有單子樹。修改數據。左右樹都有,找高度更高的換數據。 被換節點必定是葉子,刪除葉子,父節點設空。
    //旋轉和高度問題。要求
    //1.傳過來的根節點參數的左右子樹高度是正確的。那麼RotationAndHeight函數就就能夠往上遞歸正確的計算高度和進行修正。
    //2.remove函數往下遞歸時,保證是逐層進行的。那麼才能保證RotationAndHeight會逐層往上。
    public void remove(T element,MyAVLNode<T> subTreeNode,MyAVLNode<T> fatherNode,boolean isLeft)
    {
        if(subTreeNode==null)
        {
            return;
        }
        int compareRet=subTreeNode.mElement.compareTo(element);
        if(compareRet==0)
        {
            if(subTreeNode.mLeftNode==null && subTreeNode.mRightNode==null)
            {
                if(fatherNode!=null)
                    if(isLeft)
                    {
                        fatherNode.mLeftNode=null;
                    }
                    else 
                    {
                        fatherNode.mRightNode=null;
                    }
                else 
                {
                    mRootNode=null;
                }
            }
            else if(subTreeNode.mLeftNode==null || subTreeNode.mRightNode==null)
            {
                if(fatherNode!=null)
                {
                    if(isLeft)
                    {
                        fatherNode.mLeftNode=subTreeNode.mLeftNode==null?subTreeNode.mRightNode:subTreeNode.mLeftNode;
                    }
                    else 
                    {
                        fatherNode.mRightNode=subTreeNode.mLeftNode==null?subTreeNode.mRightNode:subTreeNode.mLeftNode;
                    }
                }
                else 
                {
                    mRootNode=subTreeNode.mLeftNode==null?subTreeNode.mRightNode:subTreeNode.mLeftNode;
                }
            }
            else 
            {
                MyAVLNode<T> minnode= findMin(subTreeNode.mRightNode);
                subTreeNode.mElement=minnode.mElement;
                //能夠肯定是葉子,要效率高點。就能夠新寫個findMin,返回父節點。直接寫代碼刪除。不須要這裏一直遞歸。但對於修正平衡就沒有辦法往上遞歸了。
                remove(minnode.mElement, subTreeNode.mRightNode, subTreeNode, false);
                RotationAndHeight(subTreeNode,fatherNode,isLeft);
            }
        }
        else if(compareRet>0)//根節點更大。
        {
            remove(element, subTreeNode.mLeftNode,subTreeNode, true);
            RotationAndHeight(subTreeNode,fatherNode,isLeft);
        }
        else 
        {
            remove(element, subTreeNode.mRightNode,subTreeNode, false);
            RotationAndHeight(subTreeNode,fatherNode,isLeft);
        }
        
        
    }
    
    public MyAVLNode<T> findMax(MyAVLNode<T> subTreeNode)
    {

        MyAVLNode<T> tempRet=subTreeNode;
        while(tempRet!=null && tempRet.mRightNode!=null)
        {
            tempRet=tempRet.mRightNode;
        }
        return tempRet;
    }
    
    public MyAVLNode<T> findMin(MyAVLNode<T> subTreeNode)
    {

        MyAVLNode<T> tempRet=subTreeNode;
        while(tempRet!=null && tempRet.mLeftNode!=null)
        {
            tempRet=tempRet.mLeftNode;
        }
        return tempRet;
    }
    
    private void RotationAndHeight(MyAVLNode<T> subTreeNode,MyAVLNode<T> fatherNode,boolean isLeft)
    {
        //高度差=2 旋轉,從新計算高度。
        //高度差<2. 根節點是否須要更新高度。
        //高度差>2 .錯誤。
        //斷言對於快速開發和測試很是重要,並且減小正式版的編譯代碼和速度。不過若是有必要後期仍是要寫入到日誌中.
        int leftHeight=subTreeNode.mLeftNode==null?0:subTreeNode.mLeftNode.mHeight;
        int rightHeight=subTreeNode.mRightNode==null?0:subTreeNode.mRightNode.mHeight;
        int maxSubTreeHeight=Math.max(leftHeight, rightHeight);
        
        assert(Math.abs(leftHeight-rightHeight)<=2) : "why .left compare to right is error";
        if(Math.abs(leftHeight-rightHeight)==2)
        {
            int ll=0,lr=0,rl=0,rr=0;
            int treetype=0;
            if(leftHeight-rightHeight==2)
            {
                assert(subTreeNode.mLeftNode!=null):"no way";
                ll=subTreeNode.mLeftNode.mLeftNode==null?0:subTreeNode.mLeftNode.mLeftNode.mHeight;
                lr=subTreeNode.mLeftNode.mRightNode==null?0:subTreeNode.mLeftNode.mRightNode.mHeight;
                if(ll>=lr)//這裏用等號的話,刪除的時候,能夠用更簡單的單轉。
                {
                    MyAVLNode<T> newRoot= subTreeNode.mLeftNode;
                    subTreeNode.mLeftNode=newRoot.mRightNode;
                    newRoot.mRightNode=subTreeNode;
                    newRoot.mRightNode.mHeight=newRoot.mRightNode.mHeight-1;
                    if(isLeft && fatherNode!=null)
                    {
                        fatherNode.mLeftNode=newRoot;
                    }
                    else if (!isLeft && fatherNode!=null) {
                        fatherNode.mRightNode=newRoot;
                    }
                    else {
                        mRootNode=newRoot;
                    }
                }
                else 
                {
                    MyAVLNode<T> newRoot= subTreeNode.mLeftNode.mRightNode;
                    subTreeNode.mLeftNode.mRightNode=newRoot.mLeftNode;
                    newRoot.mLeftNode= subTreeNode.mLeftNode;
                    subTreeNode.mLeftNode=newRoot.mRightNode;
                    newRoot.mRightNode=subTreeNode;
                    newRoot.mHeight++;
                    newRoot.mLeftNode.mHeight--;
                    newRoot.mRightNode.mHeight--;
                    if(isLeft && fatherNode!=null)
                    {
                        fatherNode.mLeftNode=newRoot;
                    }
                    else if (!isLeft && fatherNode!=null) {
                        fatherNode.mRightNode=newRoot;
                    }
                    else {
                        mRootNode=newRoot;
                    }
                }
            }
            else 
            {
                assert(subTreeNode.mRightNode!=null):"no way";
                rl=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight;
                rr=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight;
                if(rr>=rl)//這裏用等號的話,刪除的時候,能夠用更簡單的單轉。
                {
                    MyAVLNode<T> newRoot= subTreeNode.mRightNode;
                    subTreeNode.mRightNode=newRoot.mLeftNode;
                    newRoot.mLeftNode=subTreeNode;
                    newRoot.mLeftNode.mHeight=newRoot.mLeftNode.mHeight-1;
                    if(isLeft && fatherNode!=null)
                    {
                        fatherNode.mLeftNode=newRoot;
                    }
                    else if (!isLeft && fatherNode!=null) {
                        fatherNode.mRightNode=newRoot;
                    }
                    else {
                        mRootNode=newRoot;
                    }
                }
                else 
                {
                    MyAVLNode<T> newRoot= subTreeNode.mRightNode.mLeftNode;
                    subTreeNode.mRightNode.mLeftNode=newRoot.mRightNode;
                    newRoot.mRightNode= subTreeNode.mRightNode;
                    subTreeNode.mRightNode=newRoot.mLeftNode;
                    newRoot.mLeftNode=subTreeNode;
                    newRoot.mHeight++;
                    newRoot.mRightNode.mHeight--;
                    newRoot.mLeftNode.mHeight--;
                    if(isLeft && fatherNode!=null)
                    {
                        fatherNode.mLeftNode=newRoot;
                    }
                    else if (!isLeft && fatherNode!=null) {
                        fatherNode.mRightNode=newRoot;
                    }
                    else {
                        mRootNode=newRoot;
                    }
                }
            }
        }
        else if(Math.abs(leftHeight-rightHeight)==1)
        {
            
            assert((subTreeNode.mHeight-maxSubTreeHeight==0 || subTreeNode.mHeight-maxSubTreeHeight==1)) : "top node's height was wrong compare with left and right substree"; 
            if(subTreeNode.mHeight-maxSubTreeHeight==0)
            {
                subTreeNode.mHeight++;
            }
        }
    }
    
    
    
    
    //add:主要是4個問題。1,正確插入位置。2,更新新插入點的父節點數據。3.更新新插入點的父節點數據的高度,4.新插入點的父節點是否平衡
    //                   1,通常處理。2,採用遞歸+方法帶返回值:新節點,那麼當遞歸返回上層時,可給返回的新插入點賦予正確的父節點。
    //   3.4,保證遞歸方法是逐層進行,並保證新插入點的左右子樹高度正確。那麼遞歸回來,會保證新插入點及其全部父節點左右子樹高度正確以及都檢測旋轉。
    public MyAVLNode<T> add(T element,MyAVLNode<T> addToThisNode)
    {
        MyAVLNode<T> ret=null;
        if(addToThisNode==null)//某條件下,成了基本問題
        {
            ret= new MyAVLNode<T>(element, null, null);
        }
        else//其餘條件下,用更小規模問題來組合
        {
            int addIsBigger=element.compareTo(addToThisNode.mElement);
            if(addIsBigger<0)
            {
                addToThisNode.mLeftNode= add(element, addToThisNode.mLeftNode);
            }
            else 
            {
                addToThisNode.mRightNode=add(element, addToThisNode.mRightNode);
            }
            ret=addToThisNode;

        }
        //檢查高度,檢查是否須要旋轉。
        ret=reHeightAndBalance(ret);
        if(addToThisNode==mRootNode)
        {
            mRootNode=ret;
        }
        return ret;
    }
    
    //remove 和add基本思路同樣。稍微複雜一點.
    //有目標點有3種狀況,1,無左右子樹,那麼刪除葉子,2,左右一個爲空。那麼跳過要刪除點,和左右子樹想相連。
    //3.左右都不爲空,本質和1是同樣。刪除右子樹的最小值節點,也就是一個葉子。並把葉子的數據給目標點。
    //返回當前子樹最高頂點。
    public MyAVLNode<T> remove(T element,MyAVLNode<T> removeThisNode)
    {
        MyAVLNode<T> ret=null;
        if(removeThisNode==null)
        {
            //空樹或者沒找到要刪除的點,什麼都不作,並返回null,由於原本就是null,返回給這個節點的父節點NULL,等於什麼都沒作。
            ret=null;
        }
        else 
        {
            int removeIsBigger=element.compareTo(removeThisNode.mElement);
            if(removeIsBigger==0)////某條件下,成了基本問題
            {
                if(removeThisNode.mLeftNode==null && removeThisNode.mRightNode==null)
                {
                    removeThisNode=null;//其實這句沒用,棧內的一個變量賦值爲空而已,也沒達到手動釋放的堆內存,並且是java了。不必勞心內存問題。
                    ret=null;
                }
                else if(removeThisNode.mLeftNode==null || removeThisNode.mRightNode==null)//不能夠寫成 != || != .由於會包含 != && !=.而如今這樣寫,額外包含的,在上面已經被排除了.
                {
                    ret =removeThisNode.mLeftNode==null?removeThisNode.mRightNode:removeThisNode.mLeftNode;
                }
                else
                {
                    MyAVLNode<T> minNode= findMin(removeThisNode.mRightNode);
                    removeThisNode.mElement= minNode.mElement;
                    removeThisNode.mRightNode=remove(minNode.mElement, removeThisNode.mRightNode);
                    ret=removeThisNode;
                    //return remove(minNode.mElement, removeThisNode.mRightNode);//原寫代碼 嚴重錯誤。
                }
            }
            else if(removeIsBigger<0)
            {
                removeThisNode.mLeftNode= remove(element, removeThisNode.mLeftNode);
                ret=removeThisNode;
            }
            else 
            {
                removeThisNode.mRightNode= remove(element, removeThisNode.mRightNode);
                ret=removeThisNode;
            }
        }
        ret=reHeightAndBalance(ret);
        if(removeThisNode==mRootNode)
        {
            mRootNode=ret;
        }
        return ret;
    }
    
    
    //rotation: add .可重新加入點算起,add方法已是逐步升入,那麼會原路返回
    //remove:1. 雙枝或空枝,逐步過來,最低點有null狀況。返回nlll就好。 2.單枝狀況,還好, 檢查返回值的高度和平衡。再從新返回.
    private MyAVLNode<T> reHeightAndBalance(MyAVLNode<T> subTreeNode)
    {
        MyAVLNode<T> ret=subTreeNode;
        //null:返回 。 要平衡:平衡。算高度。
        if(subTreeNode==null)
        {
            ret=null;
        }
        else 
        {
            int leftHeight=subTreeNode.mLeftNode==null?0:subTreeNode.mLeftNode.mHeight;
            int rightHeight=subTreeNode.mRightNode==null?0:subTreeNode.mRightNode.mHeight;
            int maxSubTreeHeight=Math.max(leftHeight, rightHeight);
            
            assert(Math.abs(leftHeight-rightHeight)<=2) : "why .left compare to right is error";
            if(Math.abs(leftHeight-rightHeight)==2)
            {
                int ll=0,lr=0,rl=0,rr=0;
                int treetype=0;
                if(leftHeight-rightHeight==2)
                {
                    assert(subTreeNode.mLeftNode!=null):"no way";
                    ll=subTreeNode.mLeftNode.mLeftNode==null?0:subTreeNode.mLeftNode.mLeftNode.mHeight;
                    lr=subTreeNode.mLeftNode.mRightNode==null?0:subTreeNode.mLeftNode.mRightNode.mHeight;
                    if(ll>=lr)//這裏用等號的話,刪除的時候,能夠用更簡單的單轉。
                    {
                        MyAVLNode<T> newRoot= subTreeNode.mLeftNode;
                        subTreeNode.mLeftNode=newRoot.mRightNode;
                        newRoot.mRightNode=subTreeNode;
                        newRoot.mRightNode.mHeight=newRoot.mRightNode.mHeight-1;
                        ret=newRoot;
                    }
                    else 
                    {
                        MyAVLNode<T> newRoot= subTreeNode.mLeftNode.mRightNode;
                        subTreeNode.mLeftNode.mRightNode=newRoot.mLeftNode;
                        newRoot.mLeftNode= subTreeNode.mLeftNode;
                        subTreeNode.mLeftNode=newRoot.mRightNode;
                        newRoot.mRightNode=subTreeNode;
                        newRoot.mHeight++;
                        newRoot.mLeftNode.mHeight--;
                        newRoot.mRightNode.mHeight--;
                        ret=newRoot;
                    }
                }
                else 
                {
                    assert(subTreeNode.mRightNode!=null):"no way";
                    rl=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight;
                    rr=subTreeNode.mRightNode.mLeftNode==null?0:subTreeNode.mRightNode.mLeftNode.mHeight;
                    if(rr>=rl)//這裏用等號的話,刪除的時候,能夠用更簡單的單轉。
                    {
                        MyAVLNode<T> newRoot= subTreeNode.mRightNode;
                        subTreeNode.mRightNode=newRoot.mLeftNode;
                        newRoot.mLeftNode=subTreeNode;
                        newRoot.mLeftNode.mHeight=newRoot.mLeftNode.mHeight-1;
                        ret=newRoot;
                    }
                    else 
                    {
                        MyAVLNode<T> newRoot= subTreeNode.mRightNode.mLeftNode;
                        subTreeNode.mRightNode.mLeftNode=newRoot.mRightNode;
                        newRoot.mRightNode= subTreeNode.mRightNode;
                        subTreeNode.mRightNode=newRoot.mLeftNode;
                        newRoot.mLeftNode=subTreeNode;
                        newRoot.mHeight++;
                        newRoot.mRightNode.mHeight--;
                        newRoot.mLeftNode.mHeight--;
                        ret=newRoot;
                    }
                }
            }
            else if(Math.abs(leftHeight-rightHeight)==1)
            {
                assert((subTreeNode.mHeight-maxSubTreeHeight==0 || subTreeNode.mHeight-maxSubTreeHeight==1)) : "top node's height was wrong compare with left and right substree"; 
                if(subTreeNode.mHeight-maxSubTreeHeight==0)
                {
                    subTreeNode.mHeight++;
                }
                ret=subTreeNode;
            }
        }
        
        return ret;
    }

    //type:0:first print parent node .1 left children ,parent, right children. 2:left children ,right children parent.
    public void printTree(int type)
    {
        printTree(mRootNode, type,"");
    }
    
    //打印樹:組合:打印左樹,打印節點,打印右樹。基本狀況:葉子。
    private void printTree(MyAVLNode<T> node,int type,String space)
    {
        if(node==null)
        {
            System.out.println("");
            return;
        }
        
        String leafInfo=space+node.mElement.toString()+"["+node.mHeight+"]";
        String newSpace=space+"     ";
        
        if(node.mLeftNode==null && node.mRightNode==null)
        {
            System.out.println(leafInfo);
        }
        else
        {
            
            if(type==0)
            {
                System.out.println(leafInfo);
                if(node.mLeftNode!=null)
                {
                    printTree(node.mLeftNode, type,newSpace);
                }
                if(node.mRightNode!=null)
                {
                    printTree(node.mRightNode, type,newSpace);
                }
            }
            
            if(type==1)
            {
                
                if(node.mLeftNode!=null)
                {
                    printTree(node.mLeftNode, type,newSpace);
                }
                System.out.println(leafInfo);
                if(node.mRightNode!=null)
                {
                    printTree(node.mRightNode, type,newSpace);
                }
            }
            
            if(type==2)
            {
                if(node.mLeftNode!=null)
                {
                    printTree(node.mLeftNode, type,newSpace);
                }
                if(node.mRightNode!=null)
                {
                    printTree(node.mRightNode, type,newSpace);
                }
                System.out.println(leafInfo);
            }
            if(type==3)
            {
                if(node.mRightNode!=null)
                {
                    printTree(node.mRightNode, type,newSpace);
                }
                System.out.println(leafInfo);
                if(node.mLeftNode!=null)
                {
                    printTree(node.mLeftNode, type,newSpace);
                }
                
                
                
            }
        }
    }
    
    
    public MyAVLNode<T> mRootNode=null;
}

 

 

測試代碼接口

public static class AVL
    {
        public static void test()
        {
            MyAVL<Integer> mytreeAvl=new MyAVL<Integer>();
//            mytreeAvl.add(20, mytreeAvl.mRootNode,null,true);
//            mytreeAvl.add(7, mytreeAvl.mRootNode,null,true);
//            //mytreeAvl.add(56, mytreeAvl.mRootNode,null,true);
//            //mytreeAvl.add(5, mytreeAvl.mRootNode,null,true);
//            //mytreeAvl.add(9, mytreeAvl.mRootNode,null,true);
//            //mytreeAvl.add(3, mytreeAvl.mRootNode,null,true);
////            mytreeAvl.add(12, mytreeAvl.mRootNode,null,true);
////            mytreeAvl.add(64, mytreeAvl.mRootNode,null,true);
////            mytreeAvl.add(33, mytreeAvl.mRootNode,null,true);
////            mytreeAvl.add(15, mytreeAvl.mRootNode,null,true);
            
            
            mytreeAvl.add(20, mytreeAvl.mRootNode);
            mytreeAvl.add(7, mytreeAvl.mRootNode);
            mytreeAvl.add(56, mytreeAvl.mRootNode);
            mytreeAvl.add(5, mytreeAvl.mRootNode);
            mytreeAvl.add(9, mytreeAvl.mRootNode);
            mytreeAvl.add(3, mytreeAvl.mRootNode);
            mytreeAvl.add(12, mytreeAvl.mRootNode);
//            mytreeAvl.add(64, mytreeAvl.mRootNode,null,true);
//            mytreeAvl.add(33, mytreeAvl.mRootNode,null,true);
//            mytreeAvl.add(15, mytreeAvl.mRootNode,null,true);
            
            
            //mytreeAvl.remove(20, mytreeAvl.mRootNode, null, true);
            //mytreeAvl.remove(5, mytreeAvl.mRootNode, null, true);
            
            mytreeAvl.printTree(3);
        }
    }
相關文章
相關標籤/搜索