“巢狀列表”表示樹
在用巢狀列表表示樹時,我們使用 Python 的列表來編寫這些函式。雖然把介面寫成列表的一系列方法與我們已實現其他的抽象資料型別有些不同,但這樣做比較有意思,因為它為我們提供一個簡單、可以直接檢視的遞迴資料結構。在列表實現樹時,我們將儲存根節點作為列表的第一個元素的值。列表的第二個元素的本身是一個表示左子樹的列表。這個列表的第三個元素表示在右子樹的另一個列表。為了說明這個儲存結構,讓我們來看一個例子。圖 1 展示出一個簡單的樹以及相應的列表實現。
圖 1: 簡單樹
1 2 3 4 5 6 7 8 |
myTree = ['a', #root ['b', #left subtree ['d' [], []], ['e' [], []] ], ['c', #right subtree ['f' [], []], [] ] ] |
請注意,我們可以使用索引來訪問列表的子樹。樹的根是myTree[0]
,根的左子樹是myTree[1]
,和右子樹是myTree[2]
。下面的程式碼說明了如何用列表建立簡單樹。一旦樹被構建,我們可以訪問根和左、右子樹。巢狀列表法一個非常好的特性就是子樹的結構與樹相同,本身是遞迴的。子樹具有根節點和兩個表示葉節點的空列表。列表的另一個優點是它容易擴充套件到多叉樹。在樹不僅僅是一個二叉樹的情況下,另一個子樹只是另一個列表。
1 2 3 4 5 |
myTree = ['a', ['b', ['d',[],[]], ['e',[],[]] ], ['c', ['f',[],[]], []] ] print(myTree) print('left subtree = ', myTree[1]) print('root = ', myTree[0]) print('right subtree = ', myTree[2]) |
讓我們定義一些函式,使我們很容易像使用列表一樣操作樹。請注意,我們不會去定義一個二叉樹類。我們將編寫的函式將只是操作列表使之類似於樹。
1 2 |
def BinaryTree(r): return [r, [], []] |
該二叉樹只是構建一個根節點和兩個空子節點的列表。左子樹新增到樹的根,我們需要插入一個新的列表到根列表的第二個位置。我們必須注意,如果列表中已經有值在第二個位置,我們需要跟蹤它,將新節點插入樹中作為其直接的左子節點。Listing 1 顯示了插入左子節點。
Listing 1
1 2 3 4 5 6 7 |
def insertLeft(root,newBranch): t = root.pop(1) if len(t) > 1: root.insert(1,[newBranch,t,[]]) else: root.insert(1,[newBranch, [], []]) return root |
請注意,插入一個左子節點,我們首先獲取對應於當前左子節點的列表(可能是空的)。然後,我們新增新的左子節點,將原來的左子節點作為新節點的左子節點。這使我們能夠將新節點插入到樹中的任何位置。對於insertRight
的程式碼類似於insertLeft
,如Listing 2 中。
Listing 2
1 2 3 4 5 6 7 |
def insertRight(root,newBranch): t = root.pop(2) if len(t) > 1: root.insert(2,[newBranch,[],t]) else: root.insert(2,[newBranch,[],[]]) return root |
為了完善樹的實現(參見Listing3),讓我們寫幾個用於獲取和設定根值的函式,以及獲得左邊或右邊子樹的函式。
Listing 3
1 2 3 4 5 6 7 8 9 10 11 |
def getRootVal(root): return root[0] def setRootVal(root,newVal): root[0] = newVal def getLeftChild(root): return root[1] def getRightChild(root): return root[2] |
以下是完整的巢狀列表表示樹的程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
def BinaryTree(r): return [r, [], []] def insertLeft(root,newBranch): t = root.pop(1) if len(t) > 1: root.insert(1,[newBranch,t,[]]) else: root.insert(1,[newBranch, [], []]) return root def insertRight(root,newBranch): t = root.pop(2) if len(t) > 1: root.insert(2,[newBranch,[],t]) else: root.insert(2,[newBranch,[],[]]) return root def getRootVal(root): return root[0] def setRootVal(root,newVal): root[0] = newVal def getLeftChild(root): return root[1] def getRightChild(root): return root[2] r = BinaryTree(3) insertLeft(r,4) insertLeft(r,5) insertRight(r,6) insertRight(r,7) l = getLeftChild(r) print(l) setRootVal(l,9) print(r) insertLeft(l,11) print(r) print(getRightChild(getRightChild(r))) |
節點和引用
我們第二種表示樹的方式——節點和引用。在這種情況下,我們將定義具有根,以及左右子樹屬性的類。由於這種表示更緊密地結合了物件導向的方式,我們將繼續使用這種表示完成本章的其餘部分。
使用節點和引用,我們認為該樹的結構類似於圖 2 所示。
圖 2:使用節點和引用表示簡單樹
我們將開始用簡單的節點和引用的類定義如Listing 4 所示。重要的是要記住這種表示的是左右子樹引用的是其他二叉樹的例項。例如,當我們插入一個新的左子節點到樹上時,我們建立了二叉樹的另一個例項,修改了根節點的self leftChild
使之指向新的樹。
Listing 4
1 2 3 4 5 |
class BinaryTree: def __init__(self,rootObj): self.key = rootObj self.leftChild = None self.rightChild = None |
注意Listing 4 中,建構函式需要得到一些型別的物件儲存在根中。就像你可以在列表中儲存你喜歡的任何一種型別,樹的根物件可以指向任何一種型別。對於我們之前的例子中,我們將儲存節點設為根值的名稱。使用節點和引用來表示圖 2 中的樹,我們將建立二叉樹類的 6 個例項。
接下來讓我們看一下我們需要構建的根節點以外的函式。為了新增左子節點,我們將建立一個新的二叉樹,並設定根的左屬性以指向這個新物件。insertLeft
的程式碼Listing 5 所示。
Listing 5
1 2 3 4 5 6 7 |
def insertLeft(self,newNode): if self.leftChild == None: self.leftChild = BinaryTree(newNode) else: t = BinaryTree(newNode) t.leftChild = self.leftChild self.leftChild = t |
我們必須考慮兩種情況進行插入。第一種情況是,沒有左子節點。當沒有左子節點時,將新節點新增即可。第二種情況的特徵是,當前存在左子節點。在第二種情況下,我們插入一個節點並將之前的子節點降一級。第二種情況是由else
語句在Listing 5的第 4 行進行處理。
對於insertRight
的程式碼必須考慮一個對稱的情況。要麼沒有右子節點,要麼我們必須插入根和現有的右子節點之間。插入程式碼Listing 6 所示。
Listing 6
1 2 3 4 5 6 7 |
def insertRight(self,newNode): if self.rightChild == None: self.rightChild = BinaryTree(newNode) else: t = BinaryTree(newNode) t.rightChild = self.rightChild self.rightChild = t |
為了完成一個簡單的二叉樹資料結構的定義,我們寫出訪問(參見Listing 7)左右子節點和根值的方法。
Listing 7
1 2 3 4 5 6 7 8 9 10 11 |
def getRightChild(self): return self.rightChild def getLeftChild(self): return self.leftChild def setRootVal(self,obj): self.key = obj def getRootVal(self): return self.key |
既然我們已經有了所有建立和操作二叉樹的方法,讓我們再進一步檢查它的結構。讓我們把每一個節點比作一個簡單的樹的根,並新增節點 B 和 C 作為子節點。 下面的程式碼就是建立樹,並儲存一些鍵值,為左右子節點賦值。注意,左右子節點和根都是同一個二叉樹類的不同物件。正如我們之前樹的定義中說的,我們能夠把一個二叉樹的任何子節點當成二叉樹來做處理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
class BinaryTree: def __init__(self,rootObj): self.key = rootObj self.leftChild = None self.rightChild = None def insertLeft(self,newNode): if self.leftChild == None: self.leftChild = BinaryTree(newNode) else: t = BinaryTree(newNode) t.leftChild = self.leftChild self.leftChild = t def insertRight(self,newNode): if self.rightChild == None: self.rightChild = BinaryTree(newNode) else: t = BinaryTree(newNode) t.rightChild = self.rightChild self.rightChild = t def getRightChild(self): return self.rightChild def getLeftChild(self): return self.leftChild def setRootVal(self,obj): self.key = obj def getRootVal(self): return self.key r = BinaryTree('a') print(r.getRootVal()) print(r.getLeftChild()) r.insertLeft('b') print(r.getLeftChild()) print(r.getLeftChild().getRootVal()) r.insertRight('c') print(r.getRightChild()) print(r.getRightChild().getRootVal()) r.getRightChild().setRootVal('hello') print(r.getRightChild().getRootVal()) |