演算法 與 資料結構 Algorithm and Data Structure
- Tree樹
Tree,樹
大家都知道種樹是非常有益於人體健康的,但在程式中卻不是這麼一回事……啊!我要說的 不是這個。總之樹是一種很特別的圖(acyclic connected graph),因此有很特殊的性質方便 我們對他做探討。除此之外,非常多資料結構也是以樹的型態呈現的,方便我們處理各種問 題。
這次探討的主要是有根樹(rooted tree)。
Definition
‧節點node:樹中的點(vertex)。
‧根(根節點)root node:在有根樹中特別指定的一個點,是「深度為0的點」。
‧有根樹rooted tree:有定出根節點的樹。
‧葉子leaf node:沒有任何子節點的節點。
‧子節點child:若u和v相連且v的深度比u的深度多1,則v是u的child
‧子樹subtree:在原本的tree中,以某一節點為根,並取所有在它之下的邊得到的樹。
‧父節點parent node:若v是u的child,則v是u的parent node。
‧兄弟節點sibling:v和u有相同的parent node則稱v和u互為sibling。
‧祖先ancestor:若v位於u到root的路徑上或v是root,則稱v為u的ancestor。
‧後代descendant:若u是v的ancestor,則v是u的descendant。
‧度數degree:Deg(v)代表v的child數。
‧深度depth:一個節點v深度就是root到v的距離。
‧高度height:所有節點的depth中最深的一個就是這棵樹的height。
‧k元樹k-ary tree:若一個樹每個non-leaf node的degree都k,則稱之k元樹。特別地,
2-ary tree就是我們所謂的binary tree。
‧叢林forest:若干個樹。(其實也就是acyclic graph)
‧tree的等價定義:
1. G是一tree。
2. G中的任兩個點恰有一條simple path相連。
3. G是connected graph,但若任一條邊被移除,G變為disconnected。
4. G是connected graph,且| E | = | V | 1。
5. G是acyclic graph,且| E | = | V | 1。
6. G是acyclic graph,但如果任一條邊被加到G中,則G變成cyclic。
樹的表示法 Representation
要儲存一棵tree的資料,一般會考慮這顆樹的特性,再決定要以什麼要的資料結構儲存他。
首先考慮最單純的二元樹binary tree,二元樹中每個點的degree都小於等於2,所以我們可
建中資訊培訓講義 (六) - by kelvin
以在每個點中用兩個指標*LeftChild和*RightChild來儲存binary tree的資料。
struct BinaryTreeNode { DataType Data;
struct BinaryTreeNode *ParentNode;
struct BinaryTreeNode *LeftChild;
struct BinaryTreeNode *RightChild;
};
除此以外,若知道這棵binary tree是complete binary tree,或只要是不歪斜的或者深度不深,
可以直接用陣列來模擬binary tree。可以以A[1]為root node,對於每個節點A[x],A[2x]是其 left child,A[2x+1]是其right child,A[ x/2 ]則是其parent node。
比較一般地,若已知是k-ary tree,可以用一個陣列儲存child的資料:
struct KaryTreeNode { DataType Data;
struct TreeNode *ParentNode;
struct TreeNode *Child[1…k];
};
更一般地,如果這棵樹沒有特別的規範,只好用left-child-right-sibling的方法來紀錄它,也 就是紀錄每個節點其最「左邊」的child node,以及緊鄰於其右邊的sibling。這樣雖然沒有這麼 方便,卻是紀錄任意的樹時最合理的方法(尤其就memory usage來說)。
struct TreeNode { DataType Data;
struct TreeNode *ParentNode;
struct TreeNode *LeftChild;
struct TreeNode *RightSibling;
};
至 於 若 不 是 rooted tree, 只 是 一 般 connected acyclic graph, 只 需 要 用 普 通 的 graph representation即可,而且通常用的是adjacency list,因為| E | = | V | 1的性質,使得使用 adjacency list時的一些O(m)複雜度,顯著地勝過使用adjacency matrix的O(n2)。
Traversing a Binary Tree 二元樹的走訪
對於binary tree,我們可以用以下三種特定方式來traverse(走訪、遍歷)這棵樹。
‧前序表示法pre-order:root -> left-subtree -> right-subtree
‧中序表示法in-order:left-subtree -> root -> right-subtree
‧後序表示法post-order:left-subtree -> right-subtree -> root 而有趣的是,給定任兩個表示法,我們可以
思考:1. 給定pre-order和in-order,如何求出post-order?
2. 給定post-order和in-order,如何求出pre-order?
建中資訊培訓講義 (六) - by kelvin
3. 給定pre-order和post-order,如何求出in-order?
Binary Search Tree 二元搜尋樹
Binary search tree(BST)是一種樹狀資料結構,每個node都存有一個元素的資料。它的特性
是對每個node,其left subtree中所有元素都小於等於該node中儲存的元素值,而right
subtree中的所有元素都大於該node中儲存的元素值。這樣的性質可以幫助它進行幾種操作:
‧搜尋 - O(h), h為BST高度
可以利用遞回的方式,(當然也是可以用while寫成),從root node開始搜尋,若所搜尋 元素比當前node中元素小便往右走,若比較大則往左走,若相等則搜尋完成。
‧新增資料 - O(h)
和搜尋時的方法類似,從root node開始往下traverse,並把新的node新增在最終的leaf下。
‧刪除資料 - O(h)
刪除節點比較麻煩一點,需要考慮三種情況(假設欲刪除節為x):
1. 當x為leaf node時,直接刪去即可。
2. 當x只有一個child,直接把x的parent連到x的child,並把x刪去。
3. 當x有兩個children時,我們選取x之左子樹中最右邊的一個元素y(亦可用右子樹中
最左邊的元素)替代x,並把原本的y刪除。(想想看為什麼可以這樣替換?)
但普通的BST有個很大的問題,就是假設不是預先對所有資料建立好的,而是把元素一個 一個新增到BST的,會造成BST的不平衡,變成skew tree,也就是各個葉子的深淺差距很 大,而最差的情況自然就是當輸入的資料是由小到大或由大到小,全部的點都會在同一條鏈 上,變成一個深度為n的樹。這樣會使得搜尋的複雜度變高,而失去原有的O(lgn)性質。
為了解決這個問題,除了先對要建立成BST的資料random過以外(可以證明random建立的 BST,其表現還算接近O(lgn)),有所謂的平衡樹balanced tree(例如AVL tree, red black
tree),可以保證各個葉子的深度相差不會太大,這樣可以保證資料結構的O(lgn)搜尋性質。
但這些資料結構較為困難,這邊就不介紹。(其實是我自己也不會寫哈哈哈哈……)
Binary Search Tree 二元計算樹
我們在做的四則運算其實可以用tree來表示,例如右圖就是計算式(1+
((2+3)*4))的表示方式。
能夠發現我們平常的表示方法其實是in-order tree walk得到的。但若要 方便計算的話可以把它改以表示為post-order。
例如這個例子中post-order表示就會是:123+4*+
想想看:給定一個in-order的算式,要怎麼表示為post-order呢?
啊啊
其實tree的應用非常多,很多資料結構為了能進行一些O(lgn)的操作,都會使用這樣的樹狀 資料結構,其中很重要的包括heap, segment tree, suffix trie, tournament tree等等……總之,
tree看似基本,其實卻有很多很實用(而且往往超難code = =)的應用!
建中資訊培訓講義 (六) - by kelvin
總之想要成為資料結構魔人的話,tree無疑是最重要的之一!(嘖嘖,好可怕的志向)
[完]
建中資訊培訓講義 (六) - by kelvin