1. 程式人生 > >Decision Tree(決策樹)

Decision Tree(決策樹)

決策樹基本思想:

所謂三思而後行,步步為營。
這裡寫圖片描述

決策樹無處不在,在生活很常見。就讓我繼續沿用好朋友這個比方吧。比如,我們如何認定一個人是我們的好朋友呢?別回答我:靠感覺啊!我覺得感覺這種東西不就是經過層層神經元,然後大腦產生的一個訊號?在一定程度上也是可以抽象出來的,比如神經網路就是模仿了人腦的某些機制。有關神經網路的部分之後總結,先回到主題,讓我們理智的,簡單的判斷一下好朋友的基準,比如TA首先得是個人?得長得好看?得和我們有共同愛好?得樂觀向上?得編的一手好程式?對於個人來講會有不同的選擇,也就是對上面這些IF條件進行yes or no的判斷,最終可以得到一個判斷標準,而這個判斷正是我們想要的一棵決策樹,然後我們就可以利用這個由老好朋友得出的判斷標準去尋找新的好朋友啦。

kNN篇提過它的最大問題就是無法給出資料的內在含義,而決策樹在處理多分類問題相較與kNN的優勢就是在於資料形式非常便於理解。為什麼?它是一種實現分治策略的層次資料結構。它的層次安排使涵蓋輸入的區域可以快速的確定,例如像我們舉的例子,好朋友分類的決策是二元的,那麼每次都可以去掉一半的例項,可以輕鬆的將決策說變成一組易理解的IF-THEN規則。

這裡寫圖片描述

具體的演算法,因為決策樹的內部由一些決策節點和終端樹葉組成,每個決策節點實現一個具有離散輸出的測試函式,標記分支。給定一個輸入,在每個節點應用一個測試,並根據測試的輸出確定一個分支,這一過程從根節點開始,並遞迴地重複,直至到達一個樹葉節點。
那麼用哪個條件特徵先做if可以使結果最優呢?1970年代,一個叫J Ross Quinlan的人用資訊理論中的熵(entropy)來度量決策樹的決策選擇過程得到了很好的效果,這就是決策樹最初的高效原型迭代Dichotomiser 3(Iterative Dichotomiser 3,ID3

)。熵描述了事物的不確定性,越不確定的事物熵就越高,所以用它來度量可以儘可能的提升劃分過程中分支結點的“純度”(purity)。對於變數X的熵表示為:

H(X)=i=1npilogpi
其中n代表X取值,p為概率。考慮到不同分支結點所包含的樣本樹不同,可以賦予一定的權重W(即某屬性a的個數S佔整個資料集的比率),算出某屬性a的資訊增益(Information gain):I(Xa)=Hxi=1nWHS
有了度量方法,我們就可以迴圈計算當前可選的所有屬性的資訊增益,每次選擇最大的那個特徵,並列舉該特徵的每一個可能值,對每個值都建立一顆子樹,然後將該特徵從待選特徵表裡刪除,直至資訊增益小於某個閾值或者已經沒有特徵可以用了。

演算法實現:(Python)

from math import log
import operator
import copy
def createDataSet():
    dataSet = [[1, 1, 'yes'],
               [1, 1, 'yes'],
               [1, 0, 'no'],
               [0, 1, 'no'],
               [0, 1, 'no']]
    labels = ['no surfacing','flippers']
    return dataSet, labels

def calcShannonEnt(dataSet):#計算熵
    numEntries = len(dataSet)
    labelCounts = {}
    for featVec in dataSet:#為所有可能的分類創造字典
        currentLabel = featVec[-1]
        if currentLabel not in labelCounts.keys(): #鍵值是最後一列的數值
            labelCounts[currentLabel] = 0
            labelCounts[currentLabel] += 1
    shannonEnt = 0.0
    for key in labelCounts:
        prob = float(labelCounts[key])/numEntries
        shannonEnt -= prob * log(prob,2) 
    return shannonEnt

def splitDataSet(dataSet, axis, value):#分割資料集。引數為資料集,屬性,屬性值
    retDataSet = []#為了不修改原來的資料集,新建一個
    for featVec in dataSet:
        if featVec[axis] == value:
            reducedFeatVec = featVec[:axis] 
            reducedFeatVec.extend(featVec[axis+1:])
            retDataSet.append(reducedFeatVec)
    return retDataSet

#發現出當前特徵相同的特徵值,就提取出來,這裡是把剩下的特徵值和結果單獨抽取!!!!Append是新增,extend是合併。
def chooseBestFeatureToSplit(dataSet):
    numFeatures = len(dataSet[0]) - 1 #最後一個數據是labels
    baseEntropy = calcShannonEnt(dataSet)
    bestInfoGain = 0.0; bestFeature = -1
    for i in range(numFeatures):   
        featList = [example[i] for example in dataSet]
        uniqueVals = set(featList)     
        newEntropy = 0.0
        for value in uniqueVals:
            subDataSet = splitDataSet(dataSet, i, value)
            prob = len(subDataSet)/float(len(dataSet))
            newEntropy += prob * calcShannonEnt(subDataSet)     
        infoGain = baseEntropy - newEntropy
        if (infoGain > bestInfoGain):
            bestInfoGain = infoGain         
            bestFeature = i
    return bestFeature

def majorityCnt(classList):#出現次數最多的,做投票表決
    classCount={}
    for vote in classList:
        if vote not in classCount.keys(): classCount[vote] = 0
        classCount[vote] += 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

def createTree(dataSet,labels):
    labels = copy.copy(labels)#由於傳址引數的問題。故增加一行程式碼
    classList = [example[-1] for example in dataSet]
    if classList.count(classList[0]) == len(classList): #巧用count(),統計分類列表中與第一個結果相同的個數,若該個數等於陣列總長度,則說明所有記錄歸屬同一類別,停止劃分。
        return classList[0]
    if len(dataSet[0]) == 1: 
        return majorityCnt(classList)
    bestFeat = chooseBestFeatureToSplit(dataSet)
    bestFeatLabel = labels[bestFeat]
    myTree = {bestFeatLabel:{}}
    del(labels[bestFeat])#這句本來是刪除已經劃分的屬性,但labels列表是可變物件,在PYTHON函式中作為引數時傳址引用,能夠被全域性修改,所以這行程式碼導致函式外的同名變數被刪除了元素,造成例句無法執行,提示'no surfacing' is not in list。解決方法就是增加一個copy。當然了,要import copy模組!!!
    featValues = [example[bestFeat] for example in dataSet]
    uniqueVals = set(featValues)
    for value in uniqueVals:
        subLabels = labels[:] #函式引數傳遞是列表型別時,引用傳遞。不改變原始資料。
        myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value),subLabels)
    return myTree

#演算法:1.類別完全相同就停止劃分2.遍歷完所有就返回出現次數最多的。
def classify(inputTree,featLabels,testVec):#輸入引數為決策樹,屬性,測試
    firstStr = list(inputTree.keys())[0]#獲得屬性名稱
    secondDict = inputTree[firstStr]#獲得該屬性下的字典
    featIndex = featLabels.index(firstStr)#反查屬性id
    key = testVec[featIndex]#獲得測試的值
    valueOfFeat = secondDict[key]#該值對應的子節點
    if isinstance(valueOfFeat, dict): #如果子節點是字典,則繼續劃分
        classLabel = classify(valueOfFeat, featLabels, testVec)
    else: classLabel = valueOfFeat
    return classLabel

myDat,labels=createDataSet()
myTree=createTree(myDat,labels)
print(classify(myTree,labels,[1,0]))

可是ID3演算法也有很多的問題

想用朋友的年齡值做特徵怎麼辦?不能處理連續特徵。
對於連續值怎麼計算資訊熵?將連續的特徵離散化就可以了。取相鄰兩樣本值的平均數,共取n-1個劃分點,對於這n-1個點,分別計算以該點作為二元分類點時的資訊增益。選擇資訊增益最大的點作為該連續特徵的二元離散分類點。這樣我們就做到了連續特徵的離散化。要注意的是,與離散屬性不同的是,如果當前節點為連續屬性,則該屬性後面還可以參與子節點的產生選擇過程。

某些型別的人多勢眾怎麼辦?結果容易偏向取值多的。
換一種度量方法。選用新的屬性選擇標準–資訊增益和特徵熵的比值。

無法知道某些朋友在某些特徵的取值怎麼辦?缺失值處理。
寧缺毋濫。先將他們劃入所有分支。

太貼近老好朋友的決策樹,無法判斷新好朋友?過擬合問題。
剪枝。一般有預剪枝(prepruning)或後剪枝(postpruning),預剪枝是在生成過程中,對每個結點在劃分前進行估計,若當前結點的劃分不能帶來決策樹泛化效能的提升,則停止劃分並將當前結點標記為葉結點;後剪枝是先生成決策樹後自底向上對非葉結點進行考察,若將該結點對應的子樹替換為葉結點能帶來決策樹泛化效能的提高,則將該子樹替換成葉結點。

其實以上都是C4.5演算法對ID3的改進,C4.5還提供了決策樹與等價規則集的轉換功能,便於人更好的理解。
這裡寫圖片描述
這裡寫圖片描述

雖然C4.5已經改進不少的問題,但是它還是有諸如選擇偏向,不能用於迴歸,多叉樹效率不高等的問題,所以分類和迴歸樹演算法(Classification and regression tree,CART)又進行了優化。
這裡寫圖片描述

CART首先對劃分標準進行了優化,採用基尼指數

基尼係數原本是經濟學裡的一個概念。基尼係數是1943年美國經濟學家阿爾伯特·赫希曼根據勞倫茨曲線所定義的判斷收入分配公平程度的指標。基尼係數是比例數值,在0和1之間,是國際上用來綜合考察居民內部收入分配差異狀況的一個重要分析指標。其具體含義是指,在全部居民收入中,用於進行不平均分配的那部分收入所佔的比例。基尼係數最大為“1”,最小等於“0”。前者表示居民之間的收入分配絕對不平均,即100%的收入被一個單位的人全部佔有了;而後者則表示居民之間的收入分配絕對平均,即人與人之間收入完全平等,沒有任何差異。但這兩種情況只是在理論上的絕對化形式,在實際生活中一般不會出現。因此,基尼係數的實際數值只能介於0~1之間,基尼係數越小收入分配越平均,基尼係數越大收入分配越不平均。國際上通常把0.4作為貧富差距的警戒線,大於這一數值容易出現社會動盪。

基尼值定義為:

Gini(p)=n=1npn(1pn)=1n=1np2n
同樣的可以得到基尼指數:Gini(D,A)=|D1||D|Gini(D1)+|D2||D|Gini(D2)
除此CART還固定了只建二叉樹,加快了建樹的速度。

幾個特徵之間有聯絡?多值偏向問題。
處理多值偏向的改進演算法,還有些有基於特徵間關聯度的,基於統計估計的,還有一種基於灰色關聯度(GDA)的改進演算法,其基本思想是根據數列的幾何關係或曲線的相似程度來判別因素間的關聯程度。
Med Gen演算法,它是基於C4.5的改進,它在選擇特徵之後到建立相應的決策樹的過程中間增加了對資料集的預處理。具體第一步是面向特徵規約的方法實現對資料集的水平壓縮,第二步進行屬性相關分析,根據分析結果從資料中去除分類屬性依賴較小的資料,實現讀資料集的垂直壓縮。
卡方自動互動檢測法(chi-squared automatic interaction detector,CHAID),是一種基於調整後的顯著性檢驗(邦費羅尼檢驗)決策樹技術。最早由Kass於1980年提出,其核心思想是:根據給定的反應變數和解釋變數對樣本進行最優分割,按照卡方檢驗的顯著性進行多元列聯表的自動判斷分組。利用卡方自動互動檢測法可以快速,有效地挖掘出主要的影響因素,它不僅可以處理非線性和高度相關的資料,而且可以將缺失值考慮在內,能克服傳統的引數檢驗方法在這些方面的限制,結果的解釋也簡單明瞭。
SLIQ演算法(Su-pervised Learning In Quest),是一個高速可擴充套件的演算法,採用特殊的資料結構,將輸入的資料分解成多個屬性表,在它的執行中,每次只需要保留一個屬性表,從而達到了處理海量資料的目的。

進一步探究過擬合問題。
構建決策樹過程是一個不斷遞迴直到最後的過程,而這樣做的結果是樹深度變深,葉節點過多,從而造成過擬合,預測結果會很差,特別是在訓練的資料中一般摻雜噪音的情況下。除了採用剪枝來解決外,還可以用k-fold cross validation來計算使分類誤差最小化時提前停止,或者不純度下降差閾值來限制,提前停止建樹。比如scikit-learn就設定了葉結點數的閾值,設定限制樹的深度等,詳見下面的應用舉例。
造成樹深度太大的另一個可能的原因是由於每次都選用最好的特徵進行分類,但還是可能存在一些特徵對分類很有用,儘管不是像最好的特徵那樣有用,所以採用一次性對多個特徵進行分類的多變數決策樹就出現了,代表OC1演算法,它先貪心地尋找每個屬性的最優權值,在區域性優化的基礎上再對分類的邊界進行隨機的擾動以試圖找到更好的邊界。還可以使用MBDT度量資料相似性。
這裡寫圖片描述
(線性多變數決策樹,節點可安放任意超平面。)

老好朋友某些性格無明顯偏向?正反比例問題。
比如說恰好這些朋友中是否喜歡機器學習的人數是一半一半的比例,那麼結果誤差會比較大。可以看出決策樹是很依賴資料集中的正反比例,如果正反比例相同,原先的方法可能就不那麼適用了。改進的方法有比如IBLE演算法,它利用通道容量的概念作為度量方法,所以它不依賴於正反比例,而是依賴於正,反的特徵取值的選擇量。各特徵的的正例標準值由譯碼函式決定。判斷正例,反例的閾值是由例項中權值的變化規律來確定的。

一旦有新好朋友的加入,標準大變化?容易被幹擾的決策樹。
正如你不可能一直之和那幾個朋友交往一樣,資料不一定是不變的,隨著新資料的產生,決策樹可能會劇烈動盪。而且在實際運動決策樹演算法時,為了小數量的新資料再重新生成一棵龐大的決策樹在效率上是不可取的,所以科學家們提出了增量學習(incremental),即在接收到新樣本後可對已學得的模型進行調整,而不用重新開始學習,主要機制是通過調整分支路徑上的劃分屬性次序來對樹進行部分重構,代表演算法有ID4,ITI,ID5R等,增量學習可有效的降低每次接收到新樣本後的訓練時間的開銷,只不過在多步增量學習後的模型會與基於全部資料訓練而得的模型有較大的差別。
不過雖然決策樹是很容易被幹擾,但是這個特性非常適合整合學習(ensemble learning)。它倆的組合使準確率得到了很大的提升。除了和整合學習結合,還可以引入線性分類器的最小二乘法,也就是在決策樹的葉結點上嵌入神經網路。比如RLSE(遞迴最小二乘估計器),在新資料和新引數適應過程中遞迴得到最小二乘估計器,最後再用隨機森林的方法。

DT應用:
sklearn DecisionTreeClassifier引數說明:
DecisionTreeClassifier(class_weight=None,criterion=‘gini,max_depth=None,max_features=None,max_leaf_nodes=None,min_impurity_split=1e-07,min_samples_leaf=1,min_samples_split=2,min_weight_fraction_leaf=0.0,presort=False,,random_state=None,splitter=’best’)

class_weight=None:類別權重。
criterion=‘gini:"gini"代表基尼係數,"entropy"代表資訊增益。預設gini,即CART演算法。
max_depth=None:最大深度。
max_features=None:"None"表所有,"log2"log2N個;"sqrt","auto"最多N特徵。整數即整數,小數即分數。
max_leaf_nodes=None:最大葉結點。
min_impurity_split=1e-07:最小不純度。
min_samples_leaf=1:最小葉子結點數。
min_samples_split=2:最小劃分樣本數。
min_weight_fraction_leaf=0.0:葉子節點最小權重和,若小於則全部被剪。
presort=False:是否排序預處理。
random_state=None:隨機狀態。
splitter='best':"best"每次都找出最優的特徵,"random"是隨機的尋找區域性最優特徵。

繼續使用Iris資料集。在全部使用DecisionTreeClassifier函式預設引數時:
這裡寫圖片描述

正如我們分析的那樣,過擬合很嚴重,但現在只要稍稍設定一個屬性情況就會好很多。

from sklearn import datasets
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import numpy as np
from sklearn import tree#匯入分類器
from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei'] 
mpl.rcParams['axes.unicode_minus'] = False 

iris=datasets.load_iris()
x=iris.data[:,:2]
y=iris.target

x_min,x_max=x[:,0].min()-0.5,x[:,0].max()+0.5
y_min,y_max=x[:,1].min()-0.5,x[:,1].max()+0.5

cmap_light=ListedColormap(['#AAAAFF','#AAFFAA','#FFAAAA'])
h=0.02
xx,yy=np.meshgrid(np.arange(x_min,x_max,h),np.arange(y_min,y_max,h))

DTC = tree.DecisionTreeClassifier(max_depth=3)#設定最大深度為3
DTC = DTC.fit(x, y)
z=DTC.predict(np.c_[xx.ravel(),yy.ravel()])
z=z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx,yy,z,cmap=cmap_light)

plt.title('基於Iris資料集的決策樹分類')
plt.scatter(x[:,0],x[:,1],c=y)
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
plt.show()

這裡寫圖片描述
這裡寫圖片描述

決策樹迴歸
DecisionTreeRegressor引數:
DecisionTreeRegressor(criterion=’mse’, max_depth=None, max_features=None,max_leaf_nodes=None, min_impurity_split=1e-07,min_samples_leaf=1, min_samples_split=2,min_weight_fraction_leaf=0.0, presort=False, random_state=None, splitter=’best’)

import numpy as np
from sklearn.tree import DecisionTreeRegressor
import matplotlib.pyplot as plt

rng = np.random.RandomState(1)
X = np.sort(5 * rng.rand(80, 1), axis=0)
y = np.sin(X).ravel()
y[::5] += 3 * (0.5 - rng.rand(16))

regr_1 = DecisionTreeRegressor(max_depth=2)
regr_2 = DecisionTreeRegressor(max_depth=5)
regr_1.fit(X, y)
regr_2.fit(X, y)

X_test = np.arange(0.0, 5.0, 0.01)[:, np.newaxis]
y_1 = regr_1.predict(X_test)
y_2 = regr_2.predict(X_test)

plt.figure()
plt.scatter(X, y, s=20, edgecolor="black",
            c="darkorange", label="data")
plt.plot(X_test, y_1, color="cornflowerblue",
         label="max_depth=2", linewidth=2)
plt.plot(X_test, y_2, color="yellowgreen", label="max_depth=5", linewidth=2)
plt.xlabel("data")
plt.ylabel("target")
plt.title("Decision Tree Regression")
plt.legend()
plt.show()

這裡寫圖片描述

使用Weka做分類決策
Weka(Waikato Environment for Knowledge Analysis)基於JAVA環境下開源的機器學習軟體,很方便使用。預處理,分類,迴歸、聚類、關聯規則,視覺化都能做。下面簡單的用一下J48分一下類。

這裡寫圖片描述

開啟Exprorer,匯入天氣的資料。

這裡寫圖片描述

從這個資料可以看到play由四個方面決定。下面選擇J48(也可以選用別的,J48是類似C4.5的基於JAVA的變體)進行應用分類操作就行了,當然了在這一步之前進行一些資料預處理也是可以的,也在Exprorer上面,選用不同的過濾器,加噪音測試效能都很方面,在這就不多說了,可以動手嘗試一下。

這裡寫圖片描述

很方便結果就出來了。當然還可以調整一下上面提到過的剪枝等優化的方法。

這裡寫圖片描述

視覺化最後的決策樹。

這裡寫圖片描述

主要參考:
Peter Harrington《Machine learning in action》
Fabio Nelli《Python Data Analytics》

相關推薦

Decision Tree決策

決策樹基本思想: 所謂三思而後行,步步為營。 決策樹無處不在,在生活很常見。就讓我繼續沿用好朋友這個比方吧。比如,我們如何認定一個人是我們的好朋友呢?別回答我:靠感覺啊!我覺得感覺這種東西不就是經過層層神經元,然後大腦產生的一個訊號?在一定程度上也是可以

Python_sklearn機器學習庫學習筆記decision_tree決策

min n) 空間 strong output epo from 標簽 ict # 決策樹 import pandas as pd from sklearn.tree import DecisionTreeClassifier from sklearn.

POJ 題目3321 Apple Tree線段

nes num ons source 每一個 number autumn script ise Apple Tree Time Limit: 2000MS Memory Limit: 65536K Total Submission

luoguP3690 【模板】Link Cut Tree 動態[LCT]

格式 %d getch logs cstring name flag -1 處理 題目背景 動態樹 題目描述 給定N個點以及每個點的權值,要你處理接下來的M個操作。操作有4種。操作從0到3編號。點從1到N編號。 0:後接兩個整數(x,y),代表詢問從x到y的路徑上的

luogu P3690 【模板】Link Cut Tree 動態

clu pda col make class getchar() root 動態樹 pan https://www.luogu.org/problemnew/show/3690 這大概還是一道模板題目 #include<cstdio> #include

luogu3690 【模板】Link Cut Tree 動態

pre class HR name print () OS 模板 pushd 參考there和there #include <iostream> #include <cstdio> using namespace std; int n, m, val

Trie tree字典

pri table main radix gcc編譯器 out 字典 name dia   Trie tree有時被稱為(digital tree)或(radix tree or prefix tree)。   可能是編譯器問題,我的實現方法用gcc編譯器,debug沒問

1123 Is It a Complete AVL Tree AVL

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 a

決策(Decision Tree) | 繪製決策

01 起 在這篇文章中,我們講解了如何訓練決策樹,然後我們得到了一個字典巢狀格式的決策樹結果,這個結果不太直觀,不能一眼看著這顆“樹”的形狀、分支、屬性值等,怎麼辦呢? 本文就上文得到的決策樹,給出決策樹繪製函式,讓我們對我們訓練出的決策樹一目瞭然。 在繪製決

【Kaggle筆記】預測泰坦尼克號乘客生還情況決策

資料集 程式碼 # -*- coding: utf-8 -*- """ 泰坦尼克號乘客生還情況預測 模型 決策樹 """ # 匯入pandas用於資料分析。 import panda

Symmetric Tree對稱

題目描述 Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center). For example, this binary tree is symmetric

什麼是B-Tree平衡

B-Tree就是我們常說的B樹,一定不要讀成B減樹,否則就很丟人了。B樹這種資料結構常常用於實現資料庫索引,因為它的查詢效率比較高。 磁碟IO與預讀 磁碟讀取依靠的是機械運動,分為尋道時間、旋轉延遲、傳輸時間三個部分,這三個部分耗時相加就是一次磁碟IO的時間,大概9ms左右。這個成本是訪問記

NOI級別的超強資料結構——Link-cut-tree動態學習小記

前言   其實LCT這種東西,我去年就接觸過並且打過,只不過一直沒調出來。最近優化了我那又醜又長的splay打法,並且用LCT切了道題。在此做一個小結。 簡介   如果有一道題,讓我們維護一棵樹,支援以下操作:   1.鏈上求和;   2.鏈上求最

【洛谷 P3690】 【模板】Link Cut Tree 動態

shu root org www getch .com std void swap 題目鏈接 \(RT\)。 FlashHu巨佬的博客 #include <cstdio> #define R register int #define I inline void

【101-Symmetric Tree對稱

Given a binary tree, check whether it is a mirror of itself (ie, symmetric around its center).   For example, this binary tree is symmet

洛谷P3690 【模板】Link Cut Tree 動態

不開o2優化就要TLE,不知道為啥。 //#include <bits/stdc++.h> #pragma GCC optimize(2) #include<stdio.h> #include<string.h> #include&l

HDU 6035 Colorful Tree

Colorful Tree Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others) Total Submission(s): 443    Accepte

P3690 【模板】Link Cut Tree 動態

rev access cst href pan == www wap swa P3690 【模板】Link Cut Tree (動態樹) 註意:不 要 把 $fa[x]$和$nrt(x)$ 混 在 一 起 ! #include<cstdio> void

226. Invert Binary Tree翻轉

war alt boa origin original sta rsquo png oar Invert a binary tree. Example: Input: 4 / 2 7 / \ / 1 3 6 9 Ou

572. Subtree of Another Tree

init self example side bsp urn ould 復雜 eth Given two non-empty binary trees s and t, check whether tree t has exactly the same structure