天天看點

python資料結構與算法 37 樹的實作樹的實作

記住上一節樹的定義,在定義的基礎上,我們用以下的函數建立并操作二叉樹:

binarytree() 建立一個二叉樹執行個體

getleftchild() 傳回節點的左孩子

getrightchild() 傳回節點的右孩子

setrootval(val) 把val變量值賦給目前節點

getrootval() 傳回目前節點對象。

insertleft(val) 建立一個新二叉樹作為目前節點的左孩子

insertright(val) 建立一個新二叉樹作為目前節點的右孩子。

實作樹的關鍵點是合适的存儲技術。python提供兩種可用方法,是以在做出決定之前,先查驗一下兩種技術。第一種叫“清單的清單”,第二種叫“節點與引用”

在清單的清單方法中,我們使用清單資料結構來實作上面的函數,雖然這種操作方法與我們以前的抽象資料結構很不相同,不過它很有意思的一點是,這是一個簡單的遞歸結構,我們可以直接檢視和檢查。

在清單的清單樹中,根節點的數值是清單的第一個元素,第二個元素是它的左子樹,第三個元素就是它的右子樹。為說明這個存儲結構,我們看一個例子。圖1所示為一個如此實作的簡單樹。

python資料結構與算法 37 樹的實作樹的實作

圖1 一棵小樹

有沒有發現我們可以直接用清單的索引來通路子樹。樹根是mytree[0],左子樹是mytree[1],右子樹是mytree[2]。下面的代碼就是用清單建立了一棵樹,完成之後,就可以通路它的根和子樹,它的好處在于清單中的“元素清單”就代表了子樹,與樹有相同的結構,是以它的結構是遞歸的。如果一個子樹有根節點,但是左右子樹都是空清單,那麼它就是葉子。另一個好處是這種方法産生的樹可以推廣到“多叉樹”而不僅是二叉樹,因為另一個子樹也不過是一個清單而已。

現在來為這種樹形資料結構定義一些函數以友善使用。注意我們沒有定義一個二叉樹類,這些函數隻是幫助操作一個清單,即使工作對象是一個樹。

def binarytree(r):

    return [r, [], []]

binarytree函數簡單地建立了一個清單,内中隻有一個根節點和兩個空清單作為孩子。要增加左子樹的話,就要插入一個新清單到根清單的第二個位置上。要小心一點,如果第二個位置上已經儲存了資料,需要跟蹤這個資料,并把它下壓一級,作為新插入清單的左孩子。listing1是插入左孩子的代碼。

listing 1

def insertleft(root,newbranch):

    t= root.pop(1)

    iflen(t)

>1:

       root.insert(1,[newbranch,t,[]])

    else:

       root.insert(1,[newbranch, [], []])

    return root

注意,要插入左孩子,先得到原來左孩子位置的清單(可能是空的),當加入新左孩子的時候,把原來的左孩子作為新節點的孩子。這樣,我們可以把新節點放在樹中任何位置。

插入右孩子的代碼也很相似,見下面listing 2.

listing 2

def insertright(root,newbranch):

    t= root.pop(2)

       root.insert(2,[newbranch,[],t])

       root.insert(2,[newbranch,[],[]])

下面listing 3是擷取和指派的函數,針對根節點和左右子樹。

listing 3

defgetrootval(root):

    return root[0]

defsetrootval(root,newval):

   root[0]

= newval

defgetleftchild(root):

    return root[1]

defgetrightchild(root):

    return root[2]

下面是完整的函數代碼。

第二種表示方法使用節點和引用。在這種情況下我們定義一個類,它的屬性值包括根,和左右子樹。因為這種方法更加面向對象,以後的章節中,我們都用這種表示方法。

使用節點和引用,我們認為樹形結構類似圖2所示。

python資料結構與算法 37 樹的實作樹的實作

開始時如linsting 4所示,隻是一個節點的定義和兩個子樹的引用。重要的是,左右子樹的引用的,也是這個類的執行個體。例如,如果要插入一個新的左子樹,那麼建立一個新的樹對象并且修改根節點的self.leftchild指向新樹

listing4

注意上面代碼中,構造函數需要一個對象來儲存在根節點。既然清單可以儲存任意對象,那麼樹的根可以是任意對象。在前面的例子中,我們在根節點中儲存節點的名字。在圖2中那樣的樹,我們要建立6個binarytree的對象。

下面看另外的函數。要在樹中增加一個左子樹,需要建立一個二叉樹對象,并把這個對象指派給leftchild。下面是實作代碼。

listing5

上面代碼中考慮了兩種情況。第一種是沒有左孩子,隻需把節點加到樹上。第二種是此節點已經有左孩子,這時要把現存的左孩子下移一級作為插入節點的左孩子。第二種情況是在上面代碼中第4行的else語句處理的。

插入右孩子的操作也要對稱地考慮,要麼沒有右孩子,否則把節點插入到根與現存右孩子之間。如下面的listing 6

listing6

同樣的,下面的函數是用來取值和指派的。

listing7

現在我們完成了建立和操作樹所需要的全部程式段,現在用它們來驗證一下他們樹的結構。我們先建立一個簡單的樹包含一個根和兩個節點,b和c。下面的代碼就是建立樹,并為鍵,左孩子和右孩子指派。注意左孩子和右孩子和根都是同一個類binarytree的不同對象,如同前面我們的遞歸定義一樣,這使得我們可以象處理二叉樹一樣處理它的子樹。