# 二元樹
[toc]
## binary tree 概念
二元樹(Binary Tree)是最廣泛被使用的樹狀資料結構,簡單來說即為每個節點最多只能有兩個子節點。
## 樹與二元樹不同之處
- 樹不能是空集合,二元樹可以是空集合。
- 樹的分支度是 d ≥ 0,二元樹的分支度是 0 ≤ d ≤ 2。就是最多兩個子樹
- **樹的左右沒有次序關係,二元樹的左右有次序關係 從左到右填入**
**樹的左右沒有次序關係,二元樹的左右有次序關係 從左到右填入**
衍生出complete binary tree and full binary tree
![](https://i.imgur.com/0oPAIhs.png)
## 三種搜尋方法在binary tree
The main traversal operations of a binary tree are as follows.
**Pre-order traversal** – Traverse the root node first and then the left subtree and right subtree. This process applies to each subtree recursively.
**In order traversal** – Traverse the left subtree first, then the root node and right subtree. This process applies to each subtree recursively.
**Post-order traversal** – Traverse the left subtree, then the right and the root node. This process applies to each subtree recursively.
## 二元樹的種類(most important )
1. **完整二元樹(Complete Binary Tree)一棵二元樹中,除了最後一層不是滿的,其餘層都是滿的。**
![](https://i.imgur.com/KO0pOb9.png)
2. 非完整二元樹
就是中間不一定都被填滿,所以用array 去儲存會浪費空間,用linked list 比較好。
![](https://i.imgur.com/avZcN1E.jpg)
在非完整二元樹中,可能存在一些浪費空間的情況,這是因為在使用陣列表示樹時,需要為每個節點分配固定大小的空間,即使節點的度數較低或存在空缺的位置也需要保留。這種情況下,部分陣列中的位置被浪費掉,沒有有效地利用起來。
浪費空間的主要原因如下:
1. 空節點:在非完整二元樹中,某些位置可能沒有節點存在,但在陣列中仍然需要保留空間。這樣就導致了一些位置被浪費掉,沒有被實際使用。
2. 低度節點:非完整二元樹中的某些節點的度數可能較低,即該節點的子節點數量較少。然而,在陣列表示中,為了保持結構的一致性,仍然需要為這些節點分配固定大小的空間。這就造成了一些空間的浪費。
浪費空間對於陣列表示的非完整二元樹來說是一種不可避免的情況,因為陣列需要確保節點之間的連續性和結構的一致性。然而,這種浪費通常是可接受的,特別是當樹的大小不是非常龐大時。對於非常大的樹,可能需要考慮其他的數據結構表示方法,以節省空間。
3. 完滿二元樹(Fully Binary Tree)每一層節點數都是最大節點數量。
![](https://i.imgur.com/R3kQGbf.png)
4. 歪斜樹(Skewed Binary Tree)一棵二元樹中,完全沒有左邊或右邊節點,如果集中左邊為「左歪斜樹」,集中右邊為「右歪斜樹」。
![](https://i.imgur.com/o7B7B1I.png)
## 常見二元樹的實作方式
1. **陣列表示法**根節點放在陣列索引位置 0。若父節點索引為 i:
- 父節點的左子節點,放在陣列索引位置 (2 * i) + 1。
- 父節點的右子節點,放在陣列索引位置 (2 * i) + 2。
- 若節點不為根節點,節點的父節點放在陣列索引位置 (i - 1) / 2(取商)。
- 每一層的節點數量為 2^(n-1) n=節層數
* 陣列的儲存方法 在左右歪斜 以及delete or insert 時效率比不上Linked list
![](https://i.imgur.com/p1mYLd4.png)
2. 鏈結串列表示法節點含有兩個指標,左右指標分別指向左邊的子節點與右邊的子節點。
![](https://i.imgur.com/B0Kg1yu.png)
## 二元樹建立程式碼
```python=
class TreeNode:
def __init__(self,data):
self.data=data
self.left=None
self.right=None
new=TreeNode("Drink")
hot=TreeNode("hot")
cold=TreeNode("cold")
new.left=hot
new.right=cold
```
# 二元樹追蹤(前中後序)
**記得前中後序的定義,是以你的中間node的順序為定(中文)**
**記得你travel 的function跟class 是同層級,並不是類別內函數**
![](https://i.imgur.com/GPJOdfs.png)
**圖二(a)-(c) 依序為: (a)pre-order:VLR、(b)in-order:LVR、(c)post-order:LRV**
**pre-order(VLR)**:當CurrentNode移動到A時,會先對A進行Visiting,接著前往left child進行Visiting,再前往right child進行Visiting。(若child指向NULL則忽略。)
**in-order(LVR)**:當CurrentNode移動到A時,會先對A的left child(B)進行Visiting,接著回到A進行Visiting,再前往right child(C)進行Visiting。(若child指向NULL則忽略。)
**post-order(LRV)**:當CurrentNode移動到A時,會先對A的left child(B)進行Visiting,再前往right child(C)進行Visiting,接著回到A進行Visiting。(若child指向NULL則忽略。)
## pre-order python 實作
```python=
class TreeNode:
def __init__(self,data):
self.data=data
self.left=None
self.right=None
new=TreeNode("Drink")
hot=TreeNode("hot")
cold=TreeNode("cold")
new.left=hot
new.right=cold
def preTravel(node):
if not node : # 到最末端葉子還是會呼叫一次,變成是none 傳入下一次的recursion
return
print(node.data) # 中間先取出了 因為順序是中->左->右
preTravel(node.left)
preTravel(node.right)
preTravel(new)
```
## 三種travel 方式
```python=
class TreeNode:
def __init__(self,data):
self.data=data
self.left=None
self.right=None
new=TreeNode("Drink")
hot=TreeNode("hot")
cold=TreeNode("cold")
lette=TreeNode("lette")
cola=TreeNode("cola")
new.left=hot
new.right=cold
hot.left=lette
cold.left=cola
def preTravel(node):
if not node : # 到最末端葉子還是會呼叫一次,變成是none
return
print(node.data)
preTravel(node.left)
preTravel(node.right)
def inOrder(node):
if not node:
return
inOrder(node.left) # 左邊地回到底
print(node.data)
inOrder(node.right)
def postOrder(node):
if not node:
return
inOrder(node.left) # 走到末端葉子再往下發現沒有東西就回直接return 然後執行下一行
inOrder(node.right)#發現也沒有 所以也return
print(node.data)# 最後就執行這一行
preTravel(new)
print("====")
inOrder(new)
print("====")
postOrder(new)
```
# 二元樹走訪(level order )
就是按照每個階層由左到右印出
1→2→3→4→5
![](https://i.imgur.com/eoQcRWV.png)
# 二元樹走訪全部code (linked list 版本)
## 簡潔版本
```python=
from collections import deque
class TreeNode:
def __init__(self,data):
self.data=data
self.right=None
self.left=None
root=TreeNode("A")
left_1=TreeNode("B")
right_1=TreeNode("C")
root.left=left_1
root.right=right_1
def preOrder(root):
if not root :
return
print(root.data)
preOrder(root.left)
preOrder(root.right)
def levelOrderTravel(root):
if not root :
return
q=deque()
q.append(root)
while q :
root=q.popleft()
print(root.data)
if root.left:
q.append (root.left)
if root.right:
q.append(root.right)
def searchBT(root,target):
if not root:
return "not exist"
q=deque()
q.append(root)
while q:
root=q.popleft()
if root.data==target:
print("found",root.data)
return root
else:
if root.left:
q.append(root.left)
else:
q.append(root.right)
return "not exist "
def insert(root,new):
if not root :
return
q=deque()
q.append(root)
while q:
root=q.popleft()
if root.left:
q.append(root.left)
else :
root.left=new
#print(root.left.data)
return "insert done "
if root.right:
q.append(root.right)
else:
root.right=new
return "insert done "
return
def getDeepNode(root):
if not root :
return
q=deque()
q.append(root)
while q:
root=q.popleft()
if root.left:
q.append(root.left)
if root.right:
q.append(root.right)
print("deep=",root.data)
return
def deleteLast(root):
if not root :
return
q=deque()
q.append(root)
while q:
root=q.popleft()
if root.right:
if
preOrder(root)
print(" ")
levelOrderTravel(root)
searchBT(root,"B")
insert(root,TreeNode("D"))
insert(root,TreeNode("E"))
levelOrderTravel(root)
getDeepNode(root)
```
**用queue 去完成,因為我們要維持node 先進先出**
```python=
# Created by Elshad Karimov
# Copyright © 2020 AppMillers. All rights reserved.
import QueueLinkedList as queue
class TreeNode:
def __init__(self, data):
self.data = data
self.leftChild = None
self.rightChild = None
newBT = TreeNode("Drinks") # 自己去設定每個node 的左右子樹
leftChild = TreeNode("Hot")
tea = TreeNode("Tea")
coffee = TreeNode("Coffee")
leftChild.leftChild = tea
leftChild.rightChild = coffee
rightChild = TreeNode("Cold")
newBT.leftChild = leftChild
newBT.rightChild = rightChild
def preOrderTraversal(rootNode):
if not rootNode:
return
print(rootNode.data)
preOrderTraversal(rootNode.leftChild)
preOrderTraversal(rootNode.rightChild)
def inOrderTraversal(rootNode):
if not rootNode:
return
inOrderTraversal(rootNode.leftChild)
print(rootNode.data)
inOrderTraversal(rootNode.rightChild)
def postOrderTraversal(rootNode):
if not rootNode:
return
postOrderTraversal(rootNode.leftChild)
postOrderTraversal(rootNode.rightChild)
print(rootNode.data)
def levelOrderTraversal(rootNode):
if not rootNode:
return
else:
customQueue = queue.Queue()
customQueue.enqueue(rootNode) # 把整個樹的最上節點放進queue
while not(customQueue.isEmpty()):
root = customQueue.dequeue() #有點像是慢慢解開這個樹,一層一層剝開
print(root.value.data)
if (root.value.leftChild is not None):# 如果這個樹有左子樹,就再放進queue等待下一次的解析
customQueue.enqueue(root.value.leftChild)
if (root.value.rightChild is not None):
customQueue.enqueue(root.value.rightChild)
def searchBT(rootNode, nodeValue):
if not rootNode:
return "The BT does not exist" #如果跟節點不在,就直接return
else:
customQueue = queue.Queue() #使用queue資料型態
customQueue.enqueue(rootNode) #把root 加下這個queue
while not(customQueue.isEmpty()): # 如果這個queue 不為空 就要繼續找下去,代表沒有到底
root = customQueue.dequeue() #把queue 第一個值取出,去比對
if root.value.data == nodeValue:
return "Success"
if (root.value.leftChild is not None):#從目前取出的這個點往下看左右子樹,如果有就在加入
customQueue.enqueue(root.value.leftChild)
if (root.value.rightChild is not None):
customQueue.enqueue(root.value.rightChild)
return "Not found"
def insertNodeBT(rootNode, newNode):# 很像上面的search tree 這個插入是指在complete tree的情況下插入 所以不能亂插在中間 不然這個二元樹要重新排列
if not rootNode:
rootNode = newNode
else:
customQueue = queue.Queue()
customQueue.enqueue(rootNode)
while not(customQueue.isEmpty()):
root = customQueue.dequeue()
if root.value.leftChild is not None: #如果又節點沒有東西,我們就插入
customQueue.enqueue(root.value.leftChild)
else:
root.value.leftChild = newNode
return "Successfully Inserted"
if root.value.rightChild is not None:
customQueue.enqueue(root.value.rightChild)
else:
root.value.rightChild = newNode
return "Successfully Inserted"
def getDeepestNode(rootNode):
if not rootNode:
return
else:
customQueue = queue.Queue()
customQueue.enqueue(rootNode)
while not(customQueue.isEmpty()):
root = customQueue.dequeue() # 跟上面的差不多,我們把整個樹看成很多個子樹
if (root.value.leftChild is not None): # 如果左子樹不為空,我們就加入queue等待下一次的while
customQueue.enqueue(root.value.leftChild)
if (root.value.rightChild is not None):
customQueue.enqueue(root.value.rightChild)
deepestNode = root.value # 最後就會得到,因為是complete tree 所以會從左邊開始,如果左邊沒有子樹他就直接跳離while 不會在執行右邊了子樹判斷
return deepestNode
def deleteDeepestNode(rootNode, dNode):
if not rootNode:
return
else:
customQueue = queue.Queue()
customQueue.enqueue(rootNode)
while not(customQueue.isEmpty()):
root = customQueue.dequeue()
if root.value is dNode:
root.value = None
return
if root.value.rightChild:
if root.value.rightChild is dNode:
root.value.rightChild = None
return
else:
customQueue.enqueue(root.value.rightChild)
if root.value.leftChild:
if root.value.leftChild is dNode:
root.value.leftChild = None
return
else:
customQueue.enqueue(root.value.leftChild)
def deleteNodeBT(rootNode, node):
if not rootNode:
return "The BT does not exist"
else:
customQueue = queue.Queue()
customQueue.enqueue(rootNode) # queue 放入目前的樹根
while not(customQueue.isEmpty()):
root = customQueue.dequeue()
if root.value.data == node: #如果樹根就是我們要刪掉的點
dNode = getDeepestNode(rootNode)
root.value.data = dNode.data
deleteDeepestNode(rootNode, dNode)
return "The node has been successfully deleted"
if (root.value.leftChild is not None): #如果還有子樹 就丟進queue 然後下一輪再去訓練一次
customQueue.enqueue(root.value.leftChild)
if (root.value.rightChild is not None):
customQueue.enqueue(root.value.rightChild)
return "Failed to delete"
def deleteBT(rootNode):
rootNode.data = None
rootNode.leftChild = None
rootNode.rightChild = None
return "The BT has been successfully deleted"
inOrderTraversal(newBT)
```
使用到的queue檔案
```python=
class Node:
def __init__(self, value=None):
self.value = value
self.next = None
def __str__(self):
return str(self.value)
class LinkedList:
def __init__(self):
self.head = None
self.tail = None
def __iter__(self):
curNode = self.head
while curNode:
yield curNode
curNode = curNode.next
class Queue:
def __init__(self):
self.linkedList = LinkedList()
def __str__(self):
values = [str(x) for x in self.linkedList]
return ' '.join(values)
def enqueue(self, value):
newNode = Node(value)
if self.linkedList.head == None:
self.linkedList.head = newNode
self.linkedList.tail = newNode
else:
self.linkedList.tail.next = newNode
self.linkedList.tail = newNode
def isEmpty(self):
if self.linkedList.head == None:
return True
else:
return False
def dequeue(self):
if self.isEmpty():
return "There is not any node in the Queue"
else:
tempNode = self.linkedList.head
if self.linkedList.head == self.linkedList.tail:
self.linkedList.head = None
self.linkedList.tail = None
else:
self.linkedList.head = self.linkedList.head.next
return tempNode
def peek(self):
if self.isEmpty():
return "There is not any node in the Queue"
else:
return self.linkedList.head
def delete(self):
self.linkedList.head = None
self.linkedList.tail = None
custQueue = Queue()
custQueue.enqueue(1)
custQueue.enqueue(2)
custQueue.enqueue(3)
print(custQueue)
print(custQueue.peek())
print(custQueue)
```
# 用list 實作二元樹
```python=
class BinaryTree:
def __init__(self, size):
self.customList = size * [None] # 因為是陣列,所以我們要預先設定大小
self.lastUsedIndex = 0 # 我們實際有在用的index 會和原本的index 差一 因為第一個index 不使用
self.maxSize = size
def insertNode(self, value):
if self.lastUsedIndex + 1 == self.maxSize: # 判斷是否滿了要+1 印為第一個index 不使用
return "The Binary Tree is full"
self.customList[self.lastUsedIndex+1] = value # 直接插入在陣列最後,
self.lastUsedIndex += 1
return "The value has been successfully inserted"
def searchNode(self, nodeValue):
for i in range(len(self.customList)):
if self.customList[i] == nodeValue: # 幹這個就是直接search 整個list==
# linked list 比較複雜是因為他要遞迴,且他的linked 的連接方式不是線性的
return "Success"
return "Not found"
def preOrderTraversal(self, index):
if index > self.lastUsedIndex:
return
print(self.customList[index]) # 因為是中左右 所以先印出中,然後依照index 印出左子樹 然後右子樹==就這樣
self.preOrderTraversal(index*2)
self.preOrderTraversal(index*2 + 1)
def inOrderTraversal(self, index):
if index > self.lastUsedIndex:
return
self.inOrderTraversal(index*2)
print(self.customList[index])
self.inOrderTraversal(index*2+1)
def postOrderTraversal(self, index):
if index > self.lastUsedIndex:
return
self.postOrderTraversal(index*2)
self.postOrderTraversal(index*2+1)
print(self.customList[index])
def levelOrderTraversal(self, index): # 幹這個更簡單,因為本來二元樹就是造著complete tree 建造的
for i in range(index, self.lastUsedIndex+1):
print(self.customList[i])
def deleteNode(self, value):
if self.lastUsedIndex == 0:
return "There is not any node to delete"
for i in range(1, self.lastUsedIndex+1):
if self.customList[i] == value:
self.customList[i] = self.customList[self.lastUsedIndex]
self.customList[self.lastUsedIndex] = None
self.lastUsedIndex -= 1
return "The node has been successfully deleted"
def deleteBT(self):
self.customList = None
return "The BT has been successfully deleted"
newBT = BinaryTree(8)
newBT.insertNode("Drinks")
newBT.insertNode("Hot")
newBT.insertNode("Cold")
newBT.insertNode("Tea")
newBT.insertNode("Coffee")
print(newBT.deleteBT())
newBT.levelOrderTraversal(1)
```