以A-B-E-K路徑爲例:python
兄弟結點:相同雙親的結點,E,F是兄弟結點。數組
葉子結點:度等於0的結點。數據結構
樹的高度(深度):結點的最大層數。post
森林:不相交樹的集合。ui
幾個特殊的二叉樹:編碼
存儲結構通常分兩種,順序或者鏈式。spa
typedef struct BNode{ int data; struct BNode *lchild; struct BNode *rchild; }BNode, *BTree;
遍歷有先序,中序,和後序三種方式,區別在於訪問根結點的順序。
遞歸遍歷比較簡單,這裏就舉一個前序的例子。假設visit
是對結點的操做。3d
void PreOrder(BTree T){ //先序遍歷 if(T==NULL) return; visit(T); //訪問根結點 PreOrder(T->lchild); //遞歸遍歷左子樹 PreOrder(T->rchild); //遞歸遍歷右子樹 }
時間複雜度O(n),空間複雜度O(n)。
以上圖爲例:指針
重點在於非遞歸的實現方式:
前序:
這裏要利用到棧的性質,咱們向左一直遍歷樹,而後保存這些左結點的,等遍歷到了左下角,開始彈棧,轉向遍歷右結點。
void PreOrder(BTree T){ InitStack(S); BTree p=T; while(p||!isEmpty(S)){ while(p){ visit(p); stack.push(p); p=p.lchild; } p=stack.pop(); p=p.rchild; } }
中序:
中序和後序惟一的區別就是:訪問根結點的順序不同。
void PreOrder(BTree T){ InitStack(S); BTree p=T; while(p||!isEmpty(S)){ while(p){ Push(S,p); p=p.lchild; } Pop(S,p); visit(p); // 彈棧後才訪問根結點 p=p.rchild; } }
後序:
後序的狀況稍微複雜一點。
void PreOrder(BTree T){ InitStack(S); BTree p=T; BTree last=NULL; while(p||!isEmpty(S)){ while(p){ Push(S,p); p=p.lchild; } GetTop(S, p); if(p.rchild==NULL && p==last){ visit(p); Pop(S); last=p; p=NULL; } else{ p=p.rchild; } } }
逐層遍歷二叉樹
void LevelOrder(BTree T){ InitQueue(Q); BTree p; EnQueue(Q,T); while(!IsEmpty(Q)){ DeQueue(Q, p); visit(p); if(p->lchild != NULL) EnQueue(Q, P->lchild); if(p->rchild != NULL) EnQueue(Q, P->rchild); } }
給定前序+中序或者後序+中序的遍歷序列,根據序列構造二叉樹。注意:前序和後序不必定惟一肯定二叉樹。
BNode* create(vector<int> &inorder, vector<int> &postorder, int is, int ie, int ps, int pe){ if(ps > pe){ return nullptr; } BNode* node = new BNode(postorder[pe]); int pos; for(int i = is; i <= ie; i++){ if(inorder[i] == node->val){ pos = i; break; } } node->left = create(inorder, postorder, is, pos - 1, ps, ps + pos - is - 1); node->right = create(inorder, postorder, pos + 1, ie, pe - ie + pos, pe - 1); return node; }
若是方便對數組進行切割的話,代碼會更簡單,舉個例子:
def buildTree(self, inorder, postorder): if not inorder or not postorder: return None root = TreeNode(postorder.pop()) inorderIndex = inorder.index(root.val) root.right = self.buildTree(inorder[inorderIndex+1:], postorder) root.left = self.buildTree(inorder[:inorderIndex], postorder) return root
注意若是是前序+中序的話,right和left的位置要調換。
在二叉樹中,存在大量空指針域,能夠利用這些空指針域來加快遍歷二叉樹。
線索規則:
ptr->lchild
爲空,則lchild
指向其中序遍歷的前驅結點。ptr->lchild
爲空,則rchild
指向其中序遍歷的後繼結點。typedef struct ThreadNode{ int data; struct ThreadNode *lchild, *rchild; int ltag, rtag; }ThreadNode, *ThreadTree
這裏的ltag和rtag用於指示指針指向的是子結點仍是線索。
在中序遞歸遍歷中插入線索:
void CreateInThread(ThreadTree T){ ThreadTree pre=NULL; InThread(T,pre); pre->rchild=NULL; pre->rtag=1; } void InThread(ThreadTree &p, ThreadTree &pre){ if(p!NULL){ InThread(p->lchild,pre); //線索化左子樹 // 線索化過程,除了線索化,其餘跟普通的遍歷二叉樹同樣 if(p->lchild==NULL){ p->lchild=pre; p->ltag=1; } if(pre!=NULL&&pre->rchild==NULL){ pre->rchild=p; pre->rtag=1; } pre=p; // 線索化結束 InThread(p->rchild,pre); //線索化右子樹 } }
這裏能夠看出,二叉樹被線索化以後近似於一個線性的結構。
//t指向頭結點,頭結點左鏈lchild指向根結點,頭結點右鏈rchild指向中序遍歷的最後一個結點。 //中序遍歷二叉線索樹表示二叉樹t int InOrder(BTree T) { BTree *p; *p = t->lchild; //p指向根結點 while(p != t) //空樹或遍歷結束時p == t { while(p->ltag == Link) //當ltag = 0時循環到中序序列的第一個結點 { p = p->lchild; } printf("%c ", p->data); //顯示結點數據,能夠更改成其餘對結點的操做 while(p->rtag == Thread && p->rchild != t) { p = p->rchild; printf("%c ", p->data); } p = p->rchild; //p進入其右子樹 } return OK; }
樹轉二叉樹
樹轉化爲二叉樹能夠理解爲使用一個二叉鏈表來存儲樹的結構,使得鏈表中的指針一個指向本身的孩子結點一個指向本身的兄弟結點,這樣這課樹就表示成了二叉樹。
這種存儲結構通常稱之爲孩子兄弟存儲結構。
過程以下:
二叉樹轉化樹
這個其實就是樹轉二叉樹的逆操做。
森林轉二叉樹
根據孩子兄弟表示法,根結點是隻有左孩子可是沒有右兄弟的,因此能夠把第二棵樹接到第一個棵樹的右孩上,第三棵樹接到第二課樹根結點的右孩上,以此類推。
二叉樹轉森林
樹的遍歷
遍歷分先序和後序,也叫先跟和後根。區別在於對跟結點的訪問在遍歷子樹以前仍是以後。
先序:ABEFCGDHIJ
後序:EFBGCHIJDA
當樹轉化爲二叉樹以後,樹的先序對應二叉樹的先序,樹的後序對應二叉樹的中序。
森林的遍歷
森林遍歷與樹同理。
對於樹與森林,中序遍歷和後序遍歷是一個意思。
哈夫曼樹是帶權路徑長度(WPL)最小的樹。
那麼首先明確帶權路徑長度(WPL)的概念。
\[WPL=\sum_{i=1}^nw_i\times l_i\]
w爲結點的權值,l爲路徑長度。
對於上圖有WPL:
a: 7x2+5x2+2x2+4x2=36
b: 7x3+5x3+2x1+4x2=46
c: 7x1+5x2+2x3+4x3=35
給定n個權值,利用這n個權值構造哈夫曼二叉樹。
因而能夠看出:
哈夫曼樹最經常使用的一個例子就是利用哈夫曼樹進行文件壓縮。
咱們能夠根據字符出現次序爲其進行哈夫曼編碼,次數越多越短,不然反之。
若是有一個文本,a出現了45次,b13,c12,f5,e9,d16。共100個。
能夠構造獲得哈夫曼樹及其編碼。
結點
計算WPL獲得是224,比起3x100來壓縮了76個字符的長度。
注意哈夫曼樹不必定是二叉樹,也有多是多叉樹,但有可能須要0權值的結點來補齊,構造過程與二叉樹區別在於從集合拿出樹的個數。
在一棵度爲4的樹T中,如有20個度爲4的結點,10個度爲3的結點,1個度爲2的結點,10個度爲1的結點,則樹T的葉結點的個數是():
答案:82
解析:
結點度數之和爲:\(20\times 4+10\times 3+1\times 2+10\times 1=122\)。
樹的結點數量爲結點度數之和+1,即123個結點。
葉結點即度數爲0的結點,度數大於0的結點數量爲:\(20+10+1+10=41\),總結點數量-度數大於0結點的數量,即82。