數(shù)據(jù)結(jié)構(gòu)——樹

樹形結(jié)構(gòu)是一種非常重要的非線性的數(shù)據(jù)結(jié)構(gòu)研侣。樹結(jié)構(gòu)與線性結(jié)構(gòu)不同之處:線性結(jié)構(gòu)中任意一個(gè)元素最多只有一個(gè)后繼元素谱邪,而樹形結(jié)構(gòu)中每個(gè)元素可以有多個(gè)后繼;線性結(jié)構(gòu)和樹形結(jié)構(gòu)中每個(gè)元素最多只有一個(gè)前驅(qū)元素庶诡。這篇文章為本人原創(chuàng)惦银,謝絕轉(zhuǎn)載。具體的內(nèi)容目錄如下:
一末誓、樹的屬性
二扯俱、二叉樹
三、二叉樹與樹喇澡、森林的轉(zhuǎn)換
四迅栅、二叉排序樹
五、平衡二叉樹

一晴玖、樹的屬性

樹又一個(gè)特定的稱為根的節(jié)點(diǎn)读存,這個(gè)節(jié)點(diǎn)無(wú)前驅(qū)節(jié)點(diǎn)。樹可以用遞歸的方式來定義呕屎,也就是說樹是由若干個(gè)子樹構(gòu)成让簿。如下圖所示,A是根節(jié)點(diǎn)秀睛,沒有前驅(qū)節(jié)點(diǎn)尔当,B、C是A的子節(jié)點(diǎn)琅催,也是一棵樹居凶。這里要簡(jiǎn)單說下樹的一些術(shù)語(yǔ)虫给。
1.節(jié)點(diǎn):表示樹中的一個(gè)數(shù)據(jù)元素藤抡。如下圖中侠碧,A、B缠黍、C、D、E乍炉、F责球、G、H贸典、I都是是節(jié)點(diǎn)视卢。
2.孩子節(jié)點(diǎn):表示樹中節(jié)點(diǎn)的直接后驅(qū)節(jié)點(diǎn)。如下圖中廊驼,A的孩子節(jié)點(diǎn)為B据过、C。
3.雙親節(jié)點(diǎn):表示樹中節(jié)點(diǎn)的直接前驅(qū)節(jié)點(diǎn)妒挎,如下圖中绳锅,D、E酝掩、F的雙親節(jié)點(diǎn)是B鳞芙。
4.兄弟節(jié)點(diǎn):表示具有相同雙親節(jié)點(diǎn)的節(jié)點(diǎn)。如下圖中期虾,D原朝、E、F是兄弟節(jié)點(diǎn)镶苞。
5.祖先節(jié)點(diǎn):表示從根節(jié)點(diǎn)到該節(jié)點(diǎn)的雙親節(jié)點(diǎn)都是祖先節(jié)點(diǎn)喳坠。如下圖中,I的祖先節(jié)點(diǎn)為A宾尚、B丙笋、E。
6.子孫節(jié)點(diǎn):表示該節(jié)點(diǎn)下所有孩子節(jié)點(diǎn)煌贴,包括子節(jié)點(diǎn)的子節(jié)點(diǎn)御板。如下圖中,A的子孫節(jié)點(diǎn)為B牛郑、C怠肋、D、E淹朋、F笙各、G钉答、H、I杈抢。
7.節(jié)點(diǎn)的度:該節(jié)點(diǎn)的子樹的個(gè)數(shù)数尿,可以理解為該節(jié)點(diǎn)后代的代數(shù)。如下圖中惶楼,A的度為3右蹦,D的度為0。
8.葉子節(jié)點(diǎn):度為0的節(jié)點(diǎn)歼捐,也就是沒有后驅(qū)節(jié)點(diǎn)何陆。如下圖中,D豹储、H贷盲、I、G都是葉子節(jié)點(diǎn)剥扣。
9.分支節(jié)點(diǎn):度不為0的節(jié)點(diǎn)巩剖,可以理解為樹枝,有后驅(qū)節(jié)點(diǎn)朦乏。B球及、C、E都是分支節(jié)點(diǎn)呻疹。
10.樹的層數(shù):樹的根節(jié)點(diǎn)所在的層樹為1吃引,其他節(jié)點(diǎn)的層數(shù)等于他的雙親節(jié)點(diǎn)的層數(shù)+1。如下圖中刽锤,A樹的層數(shù)為4镊尺,B樹的層數(shù)為3。
11.樹的深度:表示整棵樹的最大層數(shù)并思,也叫高度庐氮。如下圖中,深度為4宋彼。
12.森林:零棵樹或有限棵樹互不相交的樹的集合叫森林弄砍。如下圖中,將A節(jié)點(diǎn)去掉输涕,B樹與C樹可以構(gòu)成森林音婶。
13.有序樹與無(wú)序樹:樹中節(jié)點(diǎn)的子樹從左到右是有次序的稱為有序樹,否則為無(wú)序樹莱坎。


樹結(jié)構(gòu)

二衣式、二叉樹

二叉樹是一顆每個(gè)節(jié)點(diǎn)有不超過兩個(gè)孩子節(jié)點(diǎn)的樹。兩顆子樹有左右之分,稱為左子樹和右子樹碴卧,左子樹和右子樹都可以為空弱卡,如果不為空時(shí),子樹也是一顆二叉樹住册。二叉樹也有一種遞歸的概念婶博。二叉樹有五種基本形態(tài)
1.空樹:根節(jié)點(diǎn)為空的二叉樹
2.只有根節(jié)點(diǎn):只有根節(jié)點(diǎn),根節(jié)點(diǎn)沒有左右子樹
3.只有左子樹:只有左子樹界弧,沒有右子樹
4.只有右子樹:只有右子樹凡蜻,沒有左子樹
5.有左右子樹:該節(jié)點(diǎn)同時(shí)有左右子樹

二叉樹的性質(zhì)
1.二叉樹第i(i>=1)層上最多有2^(i-1)個(gè)節(jié)點(diǎn)搭综。
2.深度為k(k>=1)的二叉樹最陡有2^k-1個(gè)節(jié)點(diǎn)垢箕。
3.滿二叉樹:深度為k并且含有2k-1個(gè)節(jié)點(diǎn)的二叉樹《医恚可以理解為条获,樹枝節(jié)點(diǎn)都有左右子樹。通俗地講蒋歌,在不增加樹的深度的前提下帅掘,無(wú)法再多添加一個(gè)節(jié)點(diǎn)的二叉樹稱為滿二叉樹。

滿二叉樹

4.完全二叉樹:深度為k堂油,有n個(gè)節(jié)點(diǎn)的二叉樹修档,當(dāng)且僅當(dāng)每個(gè)節(jié)點(diǎn)的編號(hào)與相應(yīng)滿二叉樹節(jié)點(diǎn)順序編號(hào)從1~n相對(duì)應(yīng)時(shí),則稱為完全二叉樹府框。通俗地講吱窝,完全二叉樹相當(dāng)于刪除滿二叉樹的最底層最右邊的連續(xù)若干個(gè)節(jié)點(diǎn)。
完全二叉樹

二叉樹的存儲(chǔ)結(jié)構(gòu)
1.順序存儲(chǔ)結(jié)構(gòu)
將二叉樹中所有節(jié)點(diǎn)元素存儲(chǔ)到一維數(shù)組中迫靖,這是最簡(jiǎn)單的順序存儲(chǔ)結(jié)構(gòu)院峡。設(shè)一維數(shù)組list,list[0]空置不用系宜,從第一個(gè)開始照激,每個(gè)數(shù)組元素存儲(chǔ)樹的每一個(gè)節(jié)點(diǎn)元素。按照完全二叉樹編號(hào)從上到下盹牧,從左到右的順序?qū)⒚總€(gè)節(jié)點(diǎn)元素存儲(chǔ)到數(shù)組中俩垃。如下圖所示,第i個(gè)節(jié)點(diǎn)的雙親節(jié)點(diǎn)為i/2汰寓,左右孩子節(jié)點(diǎn)分別為2i口柳、2i+1。

順序儲(chǔ)存結(jié)構(gòu)

如果二叉樹是一顆單邊二叉樹(只有左子樹或只有右子樹)踩寇,對(duì)于每個(gè)二叉樹而言啄清,有插入新元素的可以,所以需要保留存儲(chǔ)空間。如下圖中辣卒,紅色表示可以插入新元素掷贾,用虛線連接。

單邊二叉樹

下圖表示單邊二叉樹的順序存儲(chǔ)結(jié)構(gòu)荣茫,本來只有四個(gè)元素想帅,卻要分配8個(gè)存儲(chǔ)空間。如果一顆深度為k的單邊二叉樹啡莉,至少要2^k個(gè)存儲(chǔ)空間港准,則有2^k-k個(gè)空間為空,這樣造成資源浪費(fèi)咧欣。這種存儲(chǔ)結(jié)構(gòu)適合于滿二叉樹和完全二叉樹浅缸,一般的二叉樹很少采用這種存儲(chǔ)結(jié)構(gòu)。
單邊二叉樹順序存儲(chǔ)結(jié)構(gòu)

2.鏈表存儲(chǔ)結(jié)構(gòu)
二叉樹的鏈表結(jié)構(gòu)有常見的兩種:二叉鏈表魄咕、三叉鏈表衩椒。

  • 二叉鏈表:包含一個(gè)數(shù)據(jù)元素,左子樹指針哮兰,右子樹指針毛萌。可以很容易找到該節(jié)點(diǎn)的子孫節(jié)點(diǎn)喝滞,但是不容易找到其雙親節(jié)點(diǎn)阁将。
  • 三叉鏈表:包含一個(gè)數(shù)據(jù)元素,左子樹指針右遭,右子樹指針做盅,還有一個(gè)雙親節(jié)點(diǎn)指針,方便找出其雙親節(jié)點(diǎn)狸演。


    存儲(chǔ)結(jié)構(gòu)

一般樹的存儲(chǔ)結(jié)構(gòu)
1.雙親表示法:存儲(chǔ)父節(jié)點(diǎn)的序號(hào)言蛇,方便于查找父節(jié)點(diǎn)。下圖中的左邊為一般的樹形結(jié)構(gòu)宵距,右邊為存儲(chǔ)結(jié)構(gòu)腊尚。

雙親表示法

2.孩子表示法:存儲(chǔ)孩子節(jié)點(diǎn)的指針,方便于查找孩子節(jié)點(diǎn)满哪。下圖中的左邊為一般的樹形結(jié)構(gòu)婿斥,右邊為存儲(chǔ)結(jié)構(gòu)。


孩子表示法

3.雙親孩子表示法:結(jié)合上述兩種方式哨鸭∶袼蓿可方便查找雙親和孩子節(jié)點(diǎn)。


雙親孩子表示法

4.二叉樹表示法:將一個(gè)普通樹轉(zhuǎn)換為二叉樹像鸡,具體規(guī)則如下:
-左指針域指向該節(jié)點(diǎn)的第一個(gè)子節(jié)點(diǎn)活鹰;
-右指針域指向該節(jié)點(diǎn)的第一個(gè)兄弟節(jié)點(diǎn);


二叉樹表示法

二叉樹的遍歷
遍歷二叉樹就是以一定的順序訪問每個(gè)節(jié)點(diǎn),并且每個(gè)節(jié)點(diǎn)只能被訪問一次志群。
假設(shè)有一顆二叉樹有7個(gè)節(jié)點(diǎn)着绷,為了方便理解,為每個(gè)沒有同時(shí)存在左右子樹的節(jié)點(diǎn)補(bǔ)充一個(gè)空的節(jié)點(diǎn)锌云。如下圖所示荠医,圖中的紅色表示補(bǔ)充的空的節(jié)點(diǎn),外圍虛線表示搜索路徑桑涎。

搜索路徑

按照從左到右的方式彬向,可以分為三種遍歷方式
注意:遞歸算法實(shí)現(xiàn)簡(jiǎn)單,但效率較低攻冷,這是因?yàn)橄到y(tǒng)需要維護(hù)一個(gè)工作棧以保證遞歸方法的正確執(zhí)行娃胆,所有在實(shí)際使用中推薦非遞歸方式。
1.先序遍歷:先訪問根節(jié)點(diǎn)讲衫,再訪問左子樹缕棵,再訪問右子樹。如上圖中涉兽,第一次搜索經(jīng)過的節(jié)點(diǎn)為A、B篙程、D枷畏、G、C虱饿、E拥诡、F。具體的算法如下:

    /**
     * 二叉樹的先序遍歷(非遞歸)
     * @param root 根節(jié)點(diǎn)
     */
    public static void fristOrderTraversalByStack(WLTreeNode root) {
        Stack<WLTreeNode> stack = new Stack<>();
        WLTreeNode node = root;
        //將所有左孩子壓棧
        while (node != null || stack.size() > 0) {
            if(node != null) {
                printTreeNode(node);
                stack.push(node);
                node = node.getLeft();
            }else {
                node = stack.pop();
                node = node.getRight();
            }
        }
    }

    /**
     * 二叉樹的先序遍歷
     * @param root 根節(jié)點(diǎn)
     */
    public static void firstOrderTraversal(WLTreeNode root) {
        if(root != null) {
            printTreeNode(root);
            if(root.getLeft() != null) {
                firstOrderTraversal(root.getLeft());
            }
            if(root.getRight() != null) {
                firstOrderTraversal(root.getRight());
            }
        }
    }

2.中序遍歷:先訪問左子樹氮发,再訪問根節(jié)點(diǎn)渴肉,再訪問右子樹。如上圖中爽冕,第二次搜索經(jīng)過的節(jié)點(diǎn)為G仇祭、D、B颈畸、A乌奇、E、C眯娱、F礁苗。具體的算法如下:

    /**
     * 二叉樹的中序遍歷(非遞歸)
     * @param root 根節(jié)點(diǎn)
     */
    public static void inOrderTraversalByStack(WLTreeNode root) {
        Stack<WLTreeNode> stack = new Stack<>();
        WLTreeNode node = root;
        while (node != null || stack.size() > 0) {
            if(node != null) {
                stack.push(node);
                node = node.getLeft();
            }else {
                node = stack.pop();
                printTreeNode(node);
                node = node.getRight();
            }
        }
    }
    /**
     * 二叉樹的中序遍歷
     * @param root 根節(jié)點(diǎn)
     */
    public static void inOrderTraversal(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() != null) {
                inOrderTraversal(root.getLeft());
            }
            printTreeNode(root);
            if(root.getRight() != null) {
                inOrderTraversal(root.getRight());
            }
        }
    }

3.后序遍歷:先訪問左子樹,在訪問右子樹徙缴,在訪問根節(jié)點(diǎn)试伙。如上圖中,第三次搜索經(jīng)過的節(jié)點(diǎn)為G、D疏叨、B吱抚、E、F考廉、C秘豹、A。具體的算法如下:

/**
     * 二叉樹的后序遍歷(非遞歸)
     * @param root 根節(jié)點(diǎn)
     */
    public static void postOrderTraversalByStack(WLTreeNode root) {
        Stack<WLTreeNode> stack = new Stack<>();
        Stack<WLTreeNode> output = new Stack<>();
        WLTreeNode node = root;
        while (node != null || stack.size() > 0) {
            if(node != null) {
                stack.push(node);
                output.push(node);
                node = node.getRight();
            }else {
                node = stack.pop();
                node = node.getLeft();
            }
        }
        while (output.size() > 0) {
            printTreeNode(output.pop());
        }
    }
    /**
     * 二叉樹的后序遍歷
     * @param root 根節(jié)點(diǎn)
     */
    public static void postOrderTraversal(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() != null) {
                postOrderTraversal(root.getLeft());
            }
            if(root.getRight() != null) {
                postOrderTraversal(root.getRight());
            }
            printTreeNode(root);
        }
    }

二叉樹的其他操作

    /**
     * 按層遍歷二叉樹
     * 1.將二叉樹根節(jié)點(diǎn)入隊(duì)列
     * 2.將隊(duì)頭節(jié)點(diǎn)出隊(duì)列昌粤,并判斷此節(jié)點(diǎn)算法有左右孩子既绕,如果有,則將其左右孩子入隊(duì)列
     * @param root 根節(jié)點(diǎn)
     */
    public static void levelTraversal(WLTreeNode root) {
        if(root != null) {
            List<WLTreeNode> list = new ArrayList<>();
            //1.根節(jié)點(diǎn)入隊(duì)列
            list.add(root);
            while (list.size() > 0) {
                //取出隊(duì)頭節(jié)點(diǎn)
                WLTreeNode node = list.get(0);
                printTreeNode(node);
                
                list.remove(0);
                //判斷是否有左右孩子
                if(node.getLeft() != null) {
                    list.add(node.getLeft());
                }
                if(node.getRight() != null) {
                    list.add(node.getRight());
                }
            }
        }
    }

    /**
     * 按層遍歷二叉樹 
     * @param root 根節(jié)點(diǎn)
     * @param index 第幾個(gè)節(jié)點(diǎn)涮坐,從0開始
     * @return 節(jié)點(diǎn)
     */
    public static WLTreeNode findNodeByIndex(WLTreeNode root,int index) {
        if(root != null && index >= 0) {
            List<WLTreeNode> list = new ArrayList<>();
            //根節(jié)點(diǎn)入隊(duì)列
            list.add(root);
            while (list.size() > 0) {
                //取出隊(duì)頭節(jié)點(diǎn)
                WLTreeNode node = list.get(0);
                //==0凄贩,則找到
                if(index == 0) {
                    return node;
                }
                //彈出隊(duì)頭節(jié)點(diǎn)
                list.remove(0);
                index --;
                
                //如果該節(jié)點(diǎn)有左右節(jié)點(diǎn),則入隊(duì)列
                if(node.getLeft() != null) {
                    list.add(node.getLeft());
                }
                if(node.getRight() != null) {
                    list.add(node.getRight());
                }
            }
        }
        return null;
    }

    /**
     * 二叉樹的深度
     * @param root 根節(jié)點(diǎn)
     * @return 深度值 根節(jié)點(diǎn)深度為1
     */
    public static int depthOfBinaryTree(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() == null && root.getRight() == null) {
                return 1;
            }
            int leftDepth = depthOfBinaryTree(root.getLeft());
            int rightDepth = depthOfBinaryTree(root.getRight());
            return Math.max(leftDepth, rightDepth)+1;   
        }
        return 0;
    }
    
    /**
     * 二叉樹的寬度是指二叉樹各層結(jié)點(diǎn)個(gè)數(shù)的最大值
     * @param root 根節(jié)點(diǎn)
     * @return 深度值 根節(jié)點(diǎn)深度為1
     */
    public static int widthOfBinaryTree(WLTreeNode root) {
        if(root != null) {
            List<WLTreeNode>list = new ArrayList<>();
            list.add(root);
            int maxWidth = 1;//已有根節(jié)點(diǎn)
            int curWidth = 0;
            while (list.size() > 0) {
                curWidth = list.size();
                if(maxWidth < curWidth) {
                    maxWidth = curWidth;
                }
                
                for (int i = 0; i < curWidth; i++) {
                    WLTreeNode node = list.get(0);
                    list.remove(0);
                    if(node.getLeft() != null) {
                        list.add(node.getLeft());
                    }
                    if(node.getRight() != null) {
                        list.add(node.getRight());
                    }
                }
            }
            return maxWidth;
        }
        return 0;
    }
    
    
    /**
     * 二叉樹的節(jié)點(diǎn)個(gè)數(shù)
     * @param root 根節(jié)點(diǎn)
     * @return 全部節(jié)點(diǎn)個(gè)數(shù)
     */
    public static int numberOfBinaryTree(WLTreeNode root) {
        if(root != null) {
            int count = 0;
            List<WLTreeNode>list = new ArrayList<>();
            list.add(root);
            while (list.size() > 0) {
                WLTreeNode node = list.get(0);
                list.remove(0);
                
                if(node.getLeft() != null) {
                    list.add(node.getLeft());
                }
                if(node.getRight() != null) {
                    list.add(node.getRight());
                }
                count++;
            }
            return count;
        }
        return 0;
    }
    
    /**
     * 二叉樹某一層的全部節(jié)點(diǎn)個(gè)數(shù)
     * @param root 根節(jié)點(diǎn)
     * @return 節(jié)點(diǎn)個(gè)數(shù)
     */
    public static int numberOfNodeOnLevel(WLTreeNode root,int level) {
        if(root != null && level >= 0) {
            //根節(jié)點(diǎn)只有一個(gè)節(jié)點(diǎn)
            if(level == 1) {
                return 1;
            }
            return numberOfNodeOnLevel(root.getLeft(), level-1) + numberOfNodeOnLevel(root.getRight(), level-1);
        }
        return 0;
    }
    
    /**
     * 二叉樹的葉子節(jié)點(diǎn)個(gè)數(shù)
     * @param root 根節(jié)點(diǎn)
     * @return
     */
    public static int numberOfLeafNode(WLTreeNode root) {
        if(root != null) {
            if(root.getLeft() == null && root.getRight() == null) {
                return 1;
            }
            return numberOfLeafNode(root.getLeft()) + numberOfLeafNode(root.getRight());
        }
        return 0;
    }
    
    /**
     * 二叉樹中某個(gè)節(jié)點(diǎn)到根節(jié)點(diǎn)的路徑
     * 1.將根節(jié)點(diǎn)壓入棧袱讹,再?gòu)淖笞訕渲胁檎移T绻麤]找到,再?gòu)挠易訕渲胁檎医莸瘢绻麤]找到椒丧,則彈出根節(jié)點(diǎn),再遍歷棧中上一個(gè)節(jié)點(diǎn)
     * 2.如果找到救巷,則棧中存放的節(jié)點(diǎn)就是路徑經(jīng)過的節(jié)點(diǎn)
     * @param root 根節(jié)點(diǎn)
     * @param node 起點(diǎn)節(jié)點(diǎn)
     * @return 路徑數(shù)組
     */
    public static List<WLTreeNode> pathOfTreeNode(WLTreeNode root,WLTreeNode node){
        List<WLTreeNode>list = new ArrayList<>();
        boolean isFound = isFoundTreeNode(root, node, list);
        System.out.println("isFound = "+isFound);
        return list;
    }
    
    /**
     * 查找二叉樹中是否包含該節(jié)點(diǎn)
     * @param root 根節(jié)點(diǎn) 
     * @param node 待查找節(jié)點(diǎn)
     * @param list 路徑數(shù)組
     * @return 是否找到
     */
    public static boolean isFoundTreeNode(WLTreeNode root,WLTreeNode node,List<WLTreeNode>list) {

        if(root == null || node == null) {
            return false;
        }
        
        //判斷是否為當(dāng)前節(jié)點(diǎn)
        if(root.getValue() == node.getValue()) {
            return true;
        }
        System.out.println("find root value = "+root.getValue());
        //將根節(jié)點(diǎn)壓入棧
        list.add(root);
        //先從左子樹中查找
        boolean isFound = isFoundTreeNode(root.getLeft(),node,list);
        if(!isFound) {
            //左子樹中沒找到壶熏,則從右子樹中查找
            isFound = isFoundTreeNode(root.getRight(),node,list);
        }
        //左右子樹中都沒找到,則彈出此根節(jié)點(diǎn) 
        if(!isFound) {
            list.remove(list.size()-1);
        }
        return isFound;
    }
    
    /**
     * 是否為滿二叉樹
     * 除了葉子節(jié)點(diǎn)浦译,每個(gè)節(jié)點(diǎn)都有左右子節(jié)點(diǎn)
     * @param root 根節(jié)點(diǎn)
     * @return 
     */
    public static boolean isFullBinaryTree(WLTreeNode root) {
        if(root != null) {
            int depth = depthOfBinaryTree(root);
            int nodeCount = numberOfBinaryTree(root);
            if(nodeCount == Math.pow(2, depth)-1) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * 是否為平衡二叉樹
     * 定義:一顆空樹或左右子樹的高度差的絕對(duì)值不超過1棒假,并且左右子樹都是一個(gè)平衡二叉樹
     * @param root 根節(jié)點(diǎn)
     * @return
     */
    public static boolean isAVLBinaryTree(WLTreeNode root) {
        //一顆空樹
        if(root == null) {
            avlHeight = 0;
            return false;
        }
        //只有根節(jié)點(diǎn)
        if(root.getLeft() == null && root.getRight() == null) {
            avlHeight = 1;
            return true;
        }
        
        boolean isLeft = isAVLBinaryTree(root.getLeft());
        int leftHeight = avlHeight;
        boolean isRight = isAVLBinaryTree(root.getRight());
        int rightHeight = avlHeight;
        
        avlHeight = Math.max(leftHeight, rightHeight);
        if(isLeft && isRight && Math.abs(leftHeight-rightHeight) <= 1) {
            return true;
        }
        return false;
    }
    
    
    /**
     * 反轉(zhuǎn)二叉樹 非遞歸
     * @param root
     * @return
     */
    public static WLTreeNode invertBinaryTreeByQueue(WLTreeNode root) {
        if(root == null) {
            return root;
        }
        List<WLTreeNode>list = new ArrayList<>();
        list.add(root);
        while (list.size() > 0) {
            WLTreeNode node = list.get(0);
            list.remove(0);
            
            WLTreeNode tmpNode = node.getLeft();
            node.setLeft(node.getRight());
            node.setRight(tmpNode);
            
            if(node.left != null) {
                list.add(node.getLeft());
            }
            if(node.right != null) {
                list.add(node.getRight());
            }
        }
        
        return root;
    }
    /**
     * 打印樹形結(jié)構(gòu)
     */
    private static void printTreeNode(WLTreeNode node) {
        System.out.println(node.getValue());
    }

三、二叉樹與樹精盅、森林的轉(zhuǎn)換

1.樹轉(zhuǎn)換為二叉樹
(1)加線:在樹中所有相鄰的兄弟節(jié)點(diǎn)之間加一條線帽哑。
(2)抹線:對(duì)樹中的每個(gè)節(jié)點(diǎn),只保留與第一個(gè)孩子節(jié)點(diǎn)之間的連線叹俏,刪除與其他孩子節(jié)點(diǎn)之間的連線妻枕。
(3)調(diào)整:以每個(gè)節(jié)點(diǎn)為軸心,將其右側(cè)所有節(jié)點(diǎn)按順時(shí)針轉(zhuǎn)動(dòng)45度她肯,使之稱為一顆二叉樹佳头。


樹轉(zhuǎn)換為二叉樹

2.二叉樹轉(zhuǎn)換為樹
樹與二叉樹之間的轉(zhuǎn)換是可逆的,將二叉樹轉(zhuǎn)換為樹也分為三步:
(1)加線:若某個(gè)節(jié)點(diǎn)是其雙親節(jié)點(diǎn)的左孩子晴氨,則把該節(jié)點(diǎn)的所有右子孫節(jié)點(diǎn)都與該節(jié)點(diǎn)的雙親節(jié)點(diǎn)用線連起來康嘉。
(2)抹線:刪除二叉樹中所有雙親節(jié)點(diǎn)與右孩子節(jié)點(diǎn)之間的連線。
(3)調(diào)整:把虛線改為實(shí)線籽前,把節(jié)點(diǎn)按層次排列亭珍。


二叉樹轉(zhuǎn)換為樹

3.森林轉(zhuǎn)換為二叉樹
森林是樹的集合敷钾,樹可以和二叉樹相互轉(zhuǎn)換,森林也可以肄梨,只不過要麻煩一點(diǎn)點(diǎn)阻荒。
(1)轉(zhuǎn)換:將森林中的每棵子樹轉(zhuǎn)換為二叉樹,給每棵子樹編號(hào)為1,2,3,......n棵二叉樹众羡。
(2)加線:在所有的二叉樹的根節(jié)點(diǎn)之間加一條線侨赡,也就是從第二棵二叉樹開始,將第i+1棵二叉樹作為第i棵樹的右子樹粱侣。


森林轉(zhuǎn)換為二叉樹

4.二叉樹轉(zhuǎn)換為森林
(1)抹線:從二叉樹的根節(jié)點(diǎn)開始搜索所有的右子孫節(jié)點(diǎn)羊壹,將其之間的連線抹去,這樣就得到包含若干棵二叉樹的森林齐婴。
(2)還原:將每棵二叉樹還原為一般的樹油猫。


二叉樹轉(zhuǎn)換為森林

四、二叉排序樹

二叉排序樹柠偶,顧名思義就是用二叉樹實(shí)現(xiàn)排序功能情妖,是一種特殊的二叉樹,需要滿足一下性質(zhì)
1.若左子樹不為空诱担,則左子樹中所有節(jié)點(diǎn)的數(shù)據(jù)值均小于根節(jié)點(diǎn)的數(shù)據(jù)值毡证。
2.若右子樹不為空,則右子樹中所有節(jié)點(diǎn)的數(shù)據(jù)值均大于根接待你的數(shù)據(jù)值该肴。
3.左子樹和右子樹也分別是二叉排序樹情竹。
面試的時(shí)候可能會(huì)遇到這樣的問題,給出一組數(shù)字匀哄,按照順序插入,畫出二叉排序樹的結(jié)構(gòu)圖雏蛮。下面舉個(gè)例子涎嚼,畫出10,20,16,6,4,8,30,55這組數(shù)字的二叉排序樹的結(jié)構(gòu)。這里要說明一下插入的原理挑秉,插入的元素比上一個(gè)元素大法梯,則插入在元素的右邊,否則插入在元素的左邊犀概。


二叉排序樹添加過程

二叉排序樹的插入
二叉排序樹是動(dòng)態(tài)查找立哑,動(dòng)態(tài)插入的樹表,不是一次完成的姻灶。在查找的過程中铛绰,當(dāng)書中不存在待插入的值時(shí)才進(jìn)行插入操作。新插入的節(jié)點(diǎn)一定是葉子節(jié)點(diǎn)产喉。

    /**
     * 二叉排序樹的插入節(jié)點(diǎn)操作
     * @param root 樹的根節(jié)點(diǎn)
     * @param value 插入值
     * @return 插入的新節(jié)點(diǎn)
     */
    public static WLTreeNode binaryTreeInsertion(WLTreeNode root,int value) {
        //創(chuàng)建新節(jié)點(diǎn)
        WLTreeNode node = new WLTreeNode(null, null, value);
        if(root == null) {
            return node;
        }
        
        WLTreeNode pNode = root;
        WLTreeNode fNode = null;//找出新節(jié)點(diǎn)的直接父節(jié)點(diǎn)
        
        //遍歷二叉樹捂掰,查找新節(jié)點(diǎn)添加位置
        while (pNode != null) {
            fNode = pNode;
            if (value < pNode.getValue()) {
                pNode = pNode.getLeft();
            }else if(value > pNode.getValue()){
                pNode = pNode.getRight();
            }else {
                break;
            }
        }
        if(value < fNode.getValue()) {
            fNode.setLeft(node);
        }else if(value > fNode.getValue()){
            fNode.setRight(node);
        }
        return node;
    }

二叉排序樹的查找
二叉排序樹的查找就是遍歷每個(gè)節(jié)點(diǎn)敢会,比較關(guān)鍵字與每個(gè)節(jié)點(diǎn)的值是否相等,如果相等这嚣,則查找成功鸥昏;否則在根節(jié)點(diǎn)的左子樹或右子樹中繼續(xù)查找,這是一個(gè)遞歸的過程姐帚。
(1)如果二叉排序樹為空吏垮,則查找失敗,返回空指針罐旗。
(2)如果查找關(guān)鍵字和根節(jié)點(diǎn)值相等膳汪,則查找成功,返回根節(jié)點(diǎn)指針尤莺。
(3)如果查找關(guān)鍵字小于根節(jié)點(diǎn)值旅敷,則在根節(jié)點(diǎn)的左子樹中查找。
(4)如果查找關(guān)鍵字大于根節(jié)點(diǎn)值颤霎,則在根節(jié)點(diǎn)的右子樹中查找媳谁。

    /**
     * 二叉排序樹的查找
     * @param root 根節(jié)點(diǎn)
     * @param x 關(guān)鍵字
     * @return 關(guān)鍵字節(jié)點(diǎn)
     */
    public static WLTreeNode binaryTreeSearch(WLTreeNode root,int x) {
        if(root == null) {
            return null;
        }else if (x == root.getValue()) {
            return root;
        }else if (x < root.getValue()) {
            return binaryTreeSearch(root.getLeft(), x);
        }else {
            return binaryTreeSearch(root.getRight(), x);
        }
    }
    /**
     * 二叉排序樹的查找 非遞歸
     * @param root 根節(jié)點(diǎn)
     * @param x 關(guān)鍵字
     * @return 關(guān)鍵字節(jié)點(diǎn)
     */
    public static WLTreeNode binaryTreeSearchByStack(WLTreeNode root,int x) {
        if(root != null) {
            Stack<WLTreeNode> stack = new Stack<>();
            while (root != null) {
                if(root.getValue() == x) {
                    return root;
                }else if (root.getValue() > x) {
                    stack.push(root);
                    root = root.getLeft();
                }else {
                    stack.push(root);
                    root = root.getRight();
                }
            }
        }
        return null;
    }

二叉排序樹的刪除
二叉排序樹的刪除操作是在查找的基礎(chǔ)上進(jìn)行的,也就是在二叉排序樹中查找是否存在待刪除關(guān)鍵字的節(jié)點(diǎn)友酱,如果存在晴音,則刪除。二叉排序樹的刪除節(jié)點(diǎn)比較麻煩缔杉,可以分為一下四種情況:
(1)待刪除節(jié)點(diǎn)為葉子節(jié)點(diǎn)锤躁,則直接刪除

刪除葉子節(jié)點(diǎn)

(2)待刪除節(jié)點(diǎn)只有左子樹,沒有右子樹或详,則用左子樹代替該節(jié)點(diǎn)


刪除左子樹

(3)待刪除節(jié)點(diǎn)只有右子樹系羞,沒有左子樹,則用右子樹代替該節(jié)點(diǎn)


刪除右子樹

(4)待刪除節(jié)點(diǎn)有左右子樹霸琴,找出右子樹中最小值的節(jié)點(diǎn)椒振,將最小值節(jié)點(diǎn)替換該節(jié)點(diǎn),


刪除左右子樹

具體的實(shí)現(xiàn)方法如下:

    /**
     * 二叉排序樹刪除節(jié)點(diǎn)操作
     * 1.如果節(jié)點(diǎn)為葉子節(jié)點(diǎn)梧乘,直接刪除
     * 2.如果節(jié)點(diǎn)只有左子樹澎迎,無(wú)右子樹,則用左子樹代替該節(jié)點(diǎn)
     * 3.如果節(jié)點(diǎn)只有右子樹选调,無(wú)左子樹夹供,則用右子樹代替該節(jié)點(diǎn)
     * 4.如果節(jié)點(diǎn)有左、右子樹仁堪,則找出其右子樹最小值節(jié)點(diǎn)代替該節(jié)點(diǎn)
     * @param root 樹的根節(jié)點(diǎn)
     * @param value 刪除值
     * @return 刪除后的樹
     */
    public static WLTreeNode binaryTreeDelete(WLTreeNode root,int value) {
        if(root != null) {
            WLTreeNode pNode = root;//待刪除節(jié)點(diǎn)
            WLTreeNode fNode = null;//fNode為pNode的直接父節(jié)點(diǎn)
            //找出pNode節(jié)點(diǎn)和pNode的直接父節(jié)點(diǎn)
            while(pNode != null) {
                if(value == pNode.getValue()) {
                    break;
                }else if (value < pNode.getValue()) {
                    fNode = pNode;
                    pNode = pNode.getLeft();
                }else {
                    fNode = pNode;
                    pNode = pNode.getRight();
                }
            }
            
            //沒有該節(jié)點(diǎn)
            if(pNode == null) {
                return root;
            }
            
            if(pNode.getLeft() == null && pNode.getRight() == null) {//待刪除節(jié)點(diǎn)為葉子節(jié)點(diǎn)
                //判斷是否為根節(jié)點(diǎn)
                if(fNode != null) {
                    if (pNode == fNode.getLeft()) {
                        fNode.setLeft(pNode.getLeft());
                    }else if (pNode == fNode.getRight()) {
                        fNode.setRight(pNode.getRight());
                    }
                    fNode = null;
                }else {
                    root = null;
                }
                pNode = null;
            }else if (pNode.getLeft() != null && pNode.getRight() == null) {//待刪除節(jié)點(diǎn)只有左子樹哮洽,沒有右子樹 
                if(fNode != null) {
                    if(pNode == fNode.getLeft()) {
                        fNode.setLeft(pNode.getLeft());
                    }else if (pNode == fNode.getRight()) {
                        fNode.setRight(pNode.getLeft());
                    }
                    fNode = null;
                }else {
                    root = root.getLeft();
                }
                pNode = null;
            }else if (pNode.getLeft() == null && pNode.getRight() != null) {//待刪除節(jié)點(diǎn)只有右子樹,沒有左子樹
                if(fNode != null) {
                    if(pNode == fNode.getLeft()) {
                        fNode.setLeft(pNode.getRight());
                    }else if (pNode == fNode.getRight()) {
                        fNode.setRight(pNode.getRight());
                    }
                }else {
                    root = root.getRight();
                }
            }else {//待刪除節(jié)點(diǎn)同時(shí)有左右子樹
                
                //1.查找待刪除節(jié)點(diǎn)的直接后驅(qū)節(jié)點(diǎn)枝笨,也就是該節(jié)點(diǎn)右子樹中最小值節(jié)點(diǎn)qNode
                //2.將qNode節(jié)點(diǎn)替換待刪除節(jié)點(diǎn)
                
                WLTreeNode sNode = pNode.getRight();//直接后驅(qū)節(jié)點(diǎn)
                WLTreeNode qNode = sNode;//直接后驅(qū)節(jié)點(diǎn)的直接父節(jié)點(diǎn)
                while (sNode.getLeft() != null) {
                    qNode = sNode;
                    sNode = sNode.getLeft();
                }
                
                if(sNode == qNode) {
                    fNode.setRight(qNode);
                    qNode.setLeft(pNode.getLeft());
                }else {
                    qNode.setLeft(sNode.getLeft());
                    fNode.setRight(sNode);
                    sNode.setLeft(pNode.getLeft());
                    sNode.setRight(pNode.getRight());
                }
                
                pNode = null;
                qNode = null;
                fNode = null;
                sNode = null;
            }
            
             return root;
        }
        return root;
    }

五袁铐、平衡二叉樹

在說平衡二叉樹之前揭蜒,需要先講講二叉排序樹的查找的時(shí)間復(fù)雜度。對(duì)于含有n個(gè)節(jié)點(diǎn)的二叉排序樹剔桨,查找關(guān)鍵字節(jié)點(diǎn)是從根節(jié)點(diǎn)開始屉更,所需要比較次數(shù)就是該節(jié)點(diǎn)所在的層數(shù)。例如下圖中洒缀,分別查找關(guān)鍵字10瑰谜,6,16树绩,55萨脑,查找的次數(shù)分別為1,2饺饭,3渤早,4。假設(shè)每個(gè)節(jié)點(diǎn)的查找概率相等瘫俊,所以平均查找次數(shù)為(1+22+34+4)/8 ≈ 2.6鹊杖。如果是一顆單邊二叉排序樹(全部只有左子樹或只有右子樹),則平均查找次數(shù)為(1+2+3+4+6+7+8)/8 = 4.5扛芽。
總結(jié)一下骂蓖,在最好的情況下,一顆包含n個(gè)節(jié)點(diǎn)的二叉排序樹的查找復(fù)雜度為O(log2(n))川尖;在最壞的情況下登下,復(fù)雜度為O(n)。一般的情況下叮喳,復(fù)雜度介于O(log2(n))到O(n)之間被芳。因此才需要構(gòu)造一顆平衡的二叉排序樹。

二叉排序樹復(fù)雜度

平衡二叉樹的定義
平衡二叉樹又叫AVL樹馍悟,具有以下性質(zhì)
1.左子樹和右子樹的深度的差值的絕對(duì)值不大于1
2.左子樹和右子樹都是平衡二叉樹
以上的第二點(diǎn)很好理解筐钟,第一點(diǎn)怎么理解呢?如下圖中赋朦,左邊是平衡二叉樹,右邊是非平衡二叉樹李破。圖中節(jié)點(diǎn)右邊是該節(jié)點(diǎn)左右子樹深度的差值宠哄。

AVL樹

平衡二叉樹的調(diào)整
在構(gòu)建二叉排序樹過程中,每插入一個(gè)新的節(jié)點(diǎn)嗤攻,先判斷插入是否會(huì)破壞樹的平衡性毛嫉。如果會(huì),則找出最小不平衡子樹妇菱,在保持二叉排序樹的前提下承粤,調(diào)整最小平衡子樹中個(gè)節(jié)點(diǎn)之間的關(guān)系暴区。可以根據(jù)失衡的原因分為以下四種類型:
(1)LL型調(diào)整
假設(shè)在A節(jié)點(diǎn)左孩子B的左子樹上插入新的節(jié)點(diǎn)辛臊,導(dǎo)致A二叉排序樹失去平衡仙粱。如下圖中,插入值為2的新節(jié)點(diǎn)彻舰,A節(jié)點(diǎn)的值為10伐割,B節(jié)點(diǎn)的值為6,此時(shí)A樹的平衡差值從1變?yōu)?刃唤,從而失去了平衡隔心。
調(diào)整操作:向右順時(shí)針旋轉(zhuǎn)一次,就是將B作為根節(jié)點(diǎn)尚胞,A作為B的有孩子硬霍,同時(shí)將B的原來的右子樹作為A節(jié)點(diǎn)的左子樹。
LL型調(diào)整

(2)RR型調(diào)整
在A節(jié)點(diǎn)右孩子B的右子樹上插入新的節(jié)點(diǎn)笼裳,導(dǎo)致A二叉排序樹失去平衡唯卖。如下圖中,插入值為60的新節(jié)點(diǎn)侍咱,A節(jié)點(diǎn)的值為10耐床,B節(jié)點(diǎn)的值為20,此時(shí)A楔脯、B樹的平衡差值從1變?yōu)?撩轰,從而失去了平衡。
調(diào)整操作:向左逆時(shí)針旋轉(zhuǎn)一次昧廷,就是將B作為根節(jié)點(diǎn)堪嫂,A作為B的左孩子,同時(shí)B原來的左子樹調(diào)整為A子樹的右子樹木柬。


RR型調(diào)整

(3)LR型調(diào)整
在A節(jié)點(diǎn)的左孩子B的右子樹C上插入新節(jié)點(diǎn)導(dǎo)致二叉排序樹A失去平衡皆串。如下圖中,插入值為9的新節(jié)點(diǎn)眉枕,A節(jié)點(diǎn)的值為10恶复,B節(jié)點(diǎn)的值為5,C節(jié)點(diǎn)值為7速挑,此時(shí)A谤牡、B、C樹的平衡差值從1變?yōu)?姥宝,從而失去平衡翅萤。
調(diào)整操作:進(jìn)行兩次選轉(zhuǎn),先順時(shí)針在逆時(shí)針腊满。就是將C作為根節(jié)點(diǎn)套么,A作為C的右孩子培己,B作為C的左孩子,同時(shí)調(diào)整C原來的兩棵子樹胚泌。將C原來的左子樹作為B的右子樹省咨,C的右子樹作為A的左子樹。


LR型調(diào)整

(4)RL型調(diào)整
在A的右孩子B的左子樹C上插入新的節(jié)點(diǎn)導(dǎo)致A樹失去平衡诸迟。如下圖中茸炒,插入值為9的新節(jié)點(diǎn),A節(jié)點(diǎn)的值為10阵苇,B節(jié)點(diǎn)的值為20壁公,C節(jié)點(diǎn)的值為16,此時(shí)A绅项、B樹的平衡差值從1變?yōu)?紊册,C樹的平衡差值從0變?yōu)?,從而失去平衡快耿。
調(diào)整操作:將C節(jié)點(diǎn)作為根節(jié)點(diǎn)囊陡,A作為C的左孩子,B作為C的右孩子掀亥,同時(shí)調(diào)整C原來的兩棵子樹撞反,將C原來的左子樹調(diào)整為A的右子樹,C原來的右子樹調(diào)整為B的左孩子搪花。


RL型調(diào)整
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末遏片,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子撮竿,更是在濱河造成了極大的恐慌吮便,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幢踏,死亡現(xiàn)場(chǎng)離奇詭異髓需,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)房蝉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門僚匆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人搭幻,你說我怎么就攤上這事白热。” “怎么了粗卜?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)纳击。 經(jīng)常有香客問我续扔,道長(zhǎng)攻臀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任纱昧,我火速辦了婚禮刨啸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘识脆。我一直安慰自己设联,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布灼捂。 她就那樣靜靜地躺著离例,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悉稠。 梳的紋絲不亂的頭發(fā)上宫蛆,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音的猛,去河邊找鬼耀盗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛卦尊,可吹牛的內(nèi)容都是我干的叛拷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼岂却,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼忿薇!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起淌友,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤煌恢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后震庭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瑰抵,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年器联,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了二汛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拨拓,死狀恐怖肴颊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情渣磷,我是刑警寧澤婿着,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響竟宋,放射性物質(zhì)發(fā)生泄漏提完。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一丘侠、第九天 我趴在偏房一處隱蔽的房頂上張望徒欣。 院中可真熱鬧,春花似錦蜗字、人聲如沸打肝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)粗梭。三九已至,卻和暖如春担神,著一層夾襖步出監(jiān)牢的瞬間楼吃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工妄讯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留孩锡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓亥贸,卻偏偏與公主長(zhǎng)得像躬窜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子炕置,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355