《圖說演算法-使用C#》要點摘錄 – 「二叉樹」
書名:《圖說演算法-使用C#》
作者:吳燦銘、胡昭民
所讀版本:博碩文化
二叉樹
滿二叉樹(Fully Binary Tree)
- 節點數 = 2^(高度) - 1,高度>=0
完全二叉樹(Complete Binary Tree)
- 節點數 < 2^(高度) - 1,但其最底層缺少右邊若干節點
歪斜樹(Skewed Binary Tree)
- 完全沒有左/右節點
嚴格二叉樹(Strictly Binary Tree)
- 每個非終端節點均有非空的左右子樹
數組實作二叉樹
- Left Index = Father Index * 2
- Right Index = Father Index * 2 + 1
- 以一維數組建立二叉樹 = 建立一個二叉搜尋樹
- 可以是空集合,但若不是,則節點上一定要有一個鍵值
- 左子節點值<根節點;右子節點值>根節點
- 左右子樹本身也是搜尋樹
- 樹的每個節點值都不相同
-
static void Main(string[] args) { int[] data = new int[8] { 6, 3, 5, 9, 7, 8, 4, 2 }; int[] result = ConstructBinaryTreeByArray(data); for (int i = 1; i < result.Length; i++) { Console.Write(result[i] + " "); } Console.WriteLine(); } public static int[] ConstructBinaryTreeByArray(int[] data) { int[] tree = new int[data.Length * 2]; int treeIdx; for (int i = 0; i < data.Length; i++) { for(treeIdx = 1; tree[treeIdx] != 0;) { if(data[i] > tree[treeIdx]) { //Bigger than father treeIdx = treeIdx * 2 + 1; } else { //Smaller than father treeIdx = treeIdx * 2; } } tree[treeIdx] = data[i]; } return tree; }
鏈表實作二叉樹
-
public class TreeNode { public int val; public TreeNode left; public TreeNode right; public TreeNode(int val) { this.val = val; left = null; right = null; } } public class BinaryTree { public TreeNode root; public BinaryTree(int root) { this.root = new TreeNode(root); } public void Add(int node) { TreeNode curr = root; TreeNode newNode = new TreeNode(node); while(true) { if(newNode.val >= curr.val) { if(curr.right == null) { curr.right = newNode; return; } curr = curr.right; } else { if(curr.left == null) { curr.left = newNode; return; } curr = curr.left; } } } }
二叉樹算法
前序/中序/後序遍歷
-
前:根 -> 左 -> 右
-
public void ForwardTraverse(TreeNode curr) { if (curr == null) { return; } Console.WriteLine(curr.val); ForwardTraverse(curr.left); ForwardTraverse(curr.right); }
-
-
中:左 -> 根 -> 右
-
public void MiddleTraverse(TreeNode curr) { if (curr == null) { return; } MiddleTraverse(curr.left); Console.WriteLine(curr.val); MiddleTraverse(curr.right); }
-
-
後:左 -> 右 -> 根
-
public void BackwardTraverse(TreeNode curr) { if (curr == null) { return; } BackwardTraverse(curr.left); BackwardTraverse(curr.right); Console.WriteLine(curr.val); }
-
二叉搜尋樹
-
一個二叉樹符合「每一何節點凡資料>左子節點且小於右小節點」,便可稱為二分樹。
- 二叉搜尋樹和二叉排序樹都是二分樹的一種
- 二叉搜尋樹和二叉排序樹是一體兩面,沒有分別,可以說是對二分樹的兩種操作方式
-
可以是空集合,但若不是,則節點上一定要有一個鍵值
-
左子節點值<根節點;右子節點值>根節點
-
左右子樹本身也是搜尋樹
-
樹的每個節點值都不相同
-
bool Finder(TreeNode curr, int target) { if(curr == null) { return false; } else if(target == curr.val) { return true; } else if(target > curr.val) { return Finder(curr.right, target); } else { return Finder(curr.left, target); } }
二叉樹插入節點
-
if (!tree.Find(10)) { tree.Add(10); tree.Traverse(TraversalType.InOrder); }
二叉樹刪除節點
- 如果刪除節點為葉節點,直接將其父樹指向它的引用置空
- 如果刪除節點只有一棵子樹,則將其父樹指向其子樹
- 如果刪除節點有兩個子樹,則可選擇令其父樹指向:
- 其左子樹中最右的葉節點
- 其右子樹中最左的葉節點
二叉排序樹
- 第一個輸入的資料作為樹根
- 之後的資料以遞歸的方式與樹根比較,比根節點小的置於左子樹,比根節點大的置於右子樹
- 對樹中序遍歷即可得出由小到大的結果
引線二叉樹
-
把節點中的空鏈結指向樹的其他節點,這些鏈結稱為「引線」
-
步驟:
- 中序遍歷二叉樹,將空鏈結改成引線
- 如果引線指向該節點的左鏈結,將該引線指到中序遍歷順序下的後一個節點
- 如果引線指向該節點的右鏈結,將該引線指到中序遍歷順序下的後一個節點
- 指向一個空節點,並將此空節點的右鏈結指向自己,空節點的左子樹是此引線二叉樹
-
引線二叉樹的結構
- LBIT:左控制位元
- LCHILD:左子樹鏈結
- DATA:節點資料
- RCHILD:右子樹鏈結
- RBIT:右控制位元
- 如果LCHILD為正常指標,LBIT = 1;如果為引線,則=0
- 如果RCHILD為正常指標,RBIT = 1;如果為引線,則=0
-
優缺點
- 優點
- 中序遍歷不需要用Stack,而一般二叉樹需要
- 中序遍歷速度較快
- 避免鏈結閒置
- 任一節點都容易找出他的中序後繼者(右子樹最小)和中序前行者(左子樹最大)。
- 缺點
- 加入/刪除節點比一般二叉樹慢
- 引線子樹間不能共享
- 優點
-
public class ThreadedNode { public int data, lbit, rbit; public ThreadedNode lchild, rchild; public ThreadedNode(int data) { this.data = data; this.lbit = 0; this.rbit = 0; this.lchild = null; this.rchild = null; } } public class Threaded_Binary_Tree { //引線二元樹根節點 public ThreadedNode root; public Threaded_Binary_Tree() { root = null; } public Threaded_Binary_Tree(int[] data) { foreach (var i in data) { AddNode(i); } } public void AddNode(int val) { ThreadedNode newNode = new ThreadedNode(val); ThreadedNode curr; ThreadedNode parent; ThreadedNode prev = new ThreadedNode(val); int pos; //設定起始節點 if(root == null) { root = newNode; root.lchild = root; root.rchild = null; root.lbit = 0; root.rbit = 1; return; } //設定起始節點所指的節點 curr = root.rchild; if(curr == null) { root.rchild = newNode; newNode.lchild = root; newNode.rchild = root; return; } parent = root; //父節點是起始節點 pos = 0; //設定二叉樹的行進方向 while(curr != null) { if(curr.data > val) { if(pos != 1) { pos = -1; prev = parent; } parent = curr; if(curr.lbit == 1) { curr = curr.lchild; } else { curr = null; } } else { if(pos != 1) { pos = 1; prev = parent; } parent = curr; if(curr.rbit == 1) { curr = curr.rchild; } else { curr = null; } } if(parent.data > val) { parent.lbit = 1; parent.lchild = newNode; newNode.lchild = prev; newNode.rchild = parent; } else { parent.rbit = 1; parent.rchild = newNode; newNode.lchild = parent; newNode.rchild = prev; } return; } } }
延伸二叉樹
- 任何一個二叉樹,若具有n個節點,則有n-1個非空鏈結,n+1個空鏈結。
- 在每一個空鏈結上加一個特定節點,稱為外節點。
- 其餘節點(不包括根節點)稱為內節點
- 該樹可被定義為「延伸二叉樹」。
- 外徑長 = 所有外節點到樹根距離的總和
- 內徑長 = 所有內節點到樹根距離的總和
- 如果每個外節點都有加權值(如搜尋機率等),則外徑長必須考慮相關加權值(加權外徑長)
霍夫曼樹
- 有n個權值(q1, q2 … qn),且構成一個有n個節點的二叉樹,每個節點外部節點權值為qi,則加權徑長度最小的稱為「最佳化二叉樹」(霍夫曼樹)
平衡樹(AVL樹)
- 二叉搜尋樹在加入資料後,有可能產生歪斜樹,使樹的高度增加,導致搜尋效率下降。
- 因此,需要通過保持二叉樹高度平衡,從而保證搜尋效率。
- 插入/刪除資料後,對二叉樹進行高度調整。
- 高度平衡樹條件:
- 左右子樹也是高度平衡樹
- 內部節點的左右子樹高度相差小於等於1