【資料結構學習筆記】二叉樹和其他樹
基礎定義
一個樹t是一個非空的有限元素的集合,其中一個元素為根(root),其餘的元素(如果有的話)組成t的子樹(subtree)
樹的另一常用術語為級(level)。樹根是1級,其孩子(如果有)是2級,孩子的孩子是3級,等等。
一棵樹的高度(height)或深度(depth)是樹中級的個數
一個元素的度(degree of an element)是指其孩子的個數。
一棵樹的度是其元素的度的最大值
二叉樹
定義
定義:一棵二叉樹(binary tree)t是有限個元素的集合(可以為空)。當二叉樹非空時,其中有一個元素稱為根,餘下的元素(如果有的話)被劃分為兩顆二叉樹,分別稱為t的左子樹和右子樹
特點:
1)二叉樹的每個元素都恰好有兩棵子樹(其中一個或兩個可能為空)
2)在二叉樹中,每個元素的子樹都是有序的,也就是有左子樹和右子樹之分
3)二叉樹可以為空,而樹不能為空
特性
1)一棵二叉樹有n個元素,n>0,它有n-1條邊
2)一棵二叉樹的高度為h,h>=0,它最少有h個元素,最多有2^h-1個元素
3)一棵二叉樹有n個元素,n>0,它的高度最大為n,最小高度為log2(n+1)向上取整
當高度為h的二叉樹恰好有2^h-1個元素時,稱其為滿二叉樹(full binary tree)
假設從滿二叉樹中刪除k個其編號為2^h-i(最後一層)元素,1<=i<=k<2^h,所得到的二叉樹被稱為完全二叉樹
設完全二叉樹的一元素編號為i,1<=i<=n,有以下關係:
1)如果i=1,則該元素是二叉樹的根。若i>1,則其父節點的編號為i/2向下取整
2)如果2i>n,則該元素無左孩子。否則,其左孩子的編號為2i
3)如果2i+1>n,則該元素無右孩子。否則,其右孩子的編號為2i+1
二叉樹的連結串列描述
每個元素用一個節點表示,節點有兩個指標域,分別稱為leftChile和rightChile。除此之外,還有一個element域。
template<class T> struct binaryTreeNode { T element; binaryTreeNode<T> *leftChild;//左子樹 binaryTreeNode<T> *rightChild;//右子樹 binaryTreeNode() {leftChild=rightChile=NULL;} binaryTreeNode(const T& theElement) { element(theElement); leftChild=rightChild=NULL; } binaryTreeNode(const T& theElement,binaryTreeNode *theLeftChild,binaryTreeNode *theRightChild) { element(theElement); leftChild=theLeftChile; rightChild=theRightChild; } };
二叉樹的遍歷
前序遍歷(先根遍歷)
顧名思義,先訪問根節點,然後前序遍歷左子樹,最後前序遍歷右子樹
遞迴程式:
template<class T>
void preOrder(binaryTreeNode<T> *t)
{
//遞迴前序遍歷
if(t!=NULL)
{
visit(t);//訪問根節點
preOrder(t->leftChild);//前序遍歷左子樹
preOrder(t->leftChild);//前序遍歷右子樹
}
}
非遞迴程式:用棧來模擬遞迴過程
template<class T>
void preOrder(binaryTreeNode<T> *t)
{
//非遞迴前序遍歷
stack<binaryTreeNode<T>*> s;
binaryTreeNode<T> *p=t;
while(!s.empty() || p!=NULL)
{
while(p)
{
s.push(p);
visit(p);
p=p->leftChild;
}
p=s.top();
s.pop();
p=p->rightChild;
}
}
中序遍歷(中根遍歷)
顧名思義,先中序遍歷左子樹,然後訪問根節點,最後中序遍歷右子樹
遞迴程式:
template<class T>
void inOrder(binaryTreeNode<T> *t)
{
//遞迴中序遍歷
if(t!=NULL)
{
inOrder(t->leftChild);//中序遍歷左子樹
visit(t);//訪問根節點
inOrder(t->rightChild);//中序遍歷右子樹
}
}
非遞迴程式:
template<class T>
void inOrder(binaryTreeNode<T> *t)
{
//非遞迴中序遍歷
stack<binaryTreeNode<T>*> s;
binaryTreeNode<T> *p=t;
while(!s.empty() || p!=NULL)
{
while(p)
{
s.push(p);
p=p->leftChild;
}
p=s.top();
visit(p);
s.pop();
p=p->rightChild;
}
}
後序遍歷(後根遍歷)
顧名思義:先後序遍歷左子樹,然後後序遍歷右子樹,最後訪問根節點
遞迴程式:
template<class T>
void postOrder(binaryTreeNode<T> *t)
{
//遞迴後序遍歷
if(t!=NULL)
{
postOrder(t->leftChild);//後序遍歷左子樹
postOrder(t->rightChild);//後序遍歷右子樹
visit(t);//訪問根節點
}
}
非遞迴程式:後序遍歷的非遞迴程式比較難,因為根節點要在最後訪問,如果用棧來模擬遞迴,根節點會被先出棧。我們可以考慮:只有當第二次將根節點出棧時,才訪問它。可以用一個輔助棧來同步計數
template<class T>
void postOrder(binaryTreeNode<T> *t)
{
//非遞迴後序遍歷
stack<binaryTreeNode<T>*> s;
stack<int> index;
binaryTreeNode<T> *p=t;
while(!s.empty() || p!=NULL)
{
while(p)
{
s.push(p);
index.push(0);//計數一次
p=p->leftChild;
}
if(index.top()==1)
{
//第二次出棧則訪問根節點
visit(s.top());
s.pop();
index.pop();
}
else
{
p=s.top();
p=p->rightChild;
index.top()=1;//計數兩次
}
}
}
層次遍歷
層次遍歷是從頂層到底層,在同一層中,從左到右,依次訪問樹的元素。因為層次遍歷需要佇列而不是棧,因此很難編寫遞迴程式。
非遞迴程式:
template<class T>
void levelOrder(binaryTreeNode<T> *t)
{
//層次遍歷
arrayQueue<binaryTreeNode<T>*> q;
while(t!=NULL)
{
visit(t);//訪問t
//將t的孩子插入佇列
if(t->leftChild!=NULL)
q.push(t->leftChild);
if(t->rightChild!=NULL)
q.push(t->rightChild);
//提取下一個要訪問的節點
try
{
t=q.front();
}
catch(queueEmpty)
{
return;
}
q.pop();
}
}