1. 程式人生 > >【資料結構學習筆記】二叉樹和其他樹

【資料結構學習筆記】二叉樹和其他樹

基礎定義

一個樹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,所得到的二叉樹被稱為完全二叉樹

(complete binary tree)

設完全二叉樹的一元素編號為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();
    }
}