資料結構-二叉樹

極厭發表於2022-03-27

樹是一種非常重要的非線性資料結構。

樹的樹形圖表示法規定在用直線連線起來的兩端結點中,處在上端的結點是前驅,處在下端的結點是後繼。

樹的邏輯結構可表示為T=(D,R);

資料元素集合:D={A,B,C,D,E,F,G,H,I,J,K,L}

各資料元素之間的前後關係:R = {<A,B>,<A,C>,<A,D>,<B,E>,<B,F>,<C,G>,<D,H>,<F,I>,<H,J>,<H,K>,<H,L>}

樹的定義

樹是由n(n≥0)個結點組成的有限集T。當n=0時,稱為空樹;當n>0時,集合T需滿足如下條件:

 1、有且僅有一個沒有前驅的結點,該結點稱為樹的根節點。

 2、將根節點去除後,其餘結點可分為m(m≥0)個互不相交的子集T1、T2、...、Tm,其中每個子集Ti(i=1,2,3...m)又是一棵樹,並稱其為根的子樹。

樹的定義採用的是遞迴定義方式。

樹的表示方法

1、樹形圖

資料結構-二叉樹

 

 

2、巢狀集合表示法

巢狀集合表示法是通過包含的形式體現結點之間的關係,後繼結點集合包含在前驅結點集合中。

資料結構-二叉樹

 

 

3、凹入表表示法

凹入表表示法是利用樹的目錄形式表示結點之間的關係,後繼結點位於前驅結點的下一層目錄中。

資料結構-二叉樹

 

4、廣義表表示法

廣義表表示法是利用廣義表的多層次結構來表示樹,後繼結點位於前驅結點的下一層次。

資料結構-二叉樹

 

樹的基本術語

度:一個結點的後繼的數目稱為該結點的度。

樹的度:樹中各結點度的最大值稱為樹的度。

結點的層:樹的根結點所在的層為第一層,其餘結點的層等於其前驅結點的層加1。

樹的深度:樹中各結點的層的最大值稱為樹的深度。

分支:從一個結點到其後繼結點之間的連線稱為一個分支。

路徑:從一個結點x到另一個結點Y所經歷的所有分支構成結點x到結點Y的路徑。

路徑長度:一條路徑上的分支數目稱為路徑長度。

樹的路徑長度:從樹的根結點到其他各個結點的路徑長度之和稱為樹的路徑長度。

葉子結點(終端結點):樹中度為0的結點稱為葉子結點。

分支結點(非終端結點):度不為0的結點稱為分支結點。

內部結點:除根結點以外的分支結點也稱為內部結點。

結點間的關係:在樹中,一個結點的後繼結點稱為該結點的孩子,一個結點的前驅結點稱為該結點的雙親;同一個雙親結點的孩子結點稱為兄弟,不同雙親但在同一層的結點之間胡稱為堂兄弟;從樹的根結點到某一個結點X的路徑上經過的所有結點(包括根結點但不包括x結點)稱為x結點的祖先。以某一結點x為根的子樹上的所有非根結點(除結點x外)稱為結點x的子孫。

有序樹和無序樹:對於樹中任一結點,如果其各子樹的相對次序被用來表示資料之間的關係,即交換位置會改變樹表示的內容,稱為有序樹,否則稱為無序樹。

森林:m(m≥0)棵互不相交的樹的集合構成森林。

二叉樹

二叉樹是一種特殊的樹結構。

二叉樹的定義也採用遞迴定義方式。

二叉樹是有n(n≥0)個結點組成的有限集T。當n=0時,稱為空二叉樹;當n>0時,集合T需要滿足如下條件:

  1.  有且僅有一個沒有前驅的結點,該結點稱為二叉樹的根結點;
  2. 將根結點去除後,其餘結點可分為兩個互不相交的子集T1、T2,其中每個子集Ti(i=1,2)又是一棵二叉樹,並分別稱為根結點的左子樹和右子樹。

二叉樹定義:二叉樹是每個結點的度都小於等於2的有序樹。

二叉樹的5種基本形態:

資料結構-二叉樹

 

二叉樹的順序編號法:對二叉樹中的結點可以按照“自上而下,自左至右”的順序進行連續編號,稱為順序編號法。

資料結構-二叉樹

 

滿二叉樹和完全二叉樹是兩種特殊形態的二叉樹。

滿二叉樹:是指除了最後一層的結點為葉子結點外其它結點都有左、右兩棵子樹的二叉樹。

完全二叉樹:是指其結點與相同深度的滿二叉樹中的結點編號完全一致的二叉樹。對於深度為k的完全二叉樹,其前k-1層與深度為k的滿二叉樹的前k-1層完全一樣,只是在第k層有可能缺少右邊若干個結點。

滿二叉樹必然是完全二叉樹,而完全二叉樹不一定是滿二叉樹。

二叉樹的性質:

  1.  在二叉樹的第i層上至多有2^(i-1)個結點。
    • 資料結構-二叉樹
  2. 深度為k的二叉樹至多有(2^k)-1個結點。
    • 資料結構-二叉樹
  3. 在二叉樹中,若度為0的結點(即葉子結點)數為n0,度為2的結點數為n2,則 n0=n2+1;
    • 資料結構-二叉樹
  4. 具有n個結點的完全二叉樹其深度為⌊㏒2ⁿ⌋+1 (其中⌊㏒2ⁿ⌋表示不大於㏒2ⁿ的最大整數)。
    • 資料結構-二叉樹
  5. 採用順序編號的完全二叉樹具有如下性質
    1. 若一個分支結點的編號為i,則其左子樹(即左孩子結點)的根節點編號為2xi,右子樹(即右孩子結點)的根節點編號為2xi+1;
    2. 若一個非根結點的編號為i,則其雙親結點的編號為⌊i/2⌋ (其中⌊i/2⌋表示不大於i/2的最大整數)。

二叉樹的基本操作

  1. 建立一棵空二叉樹;
  2. 刪除一棵二叉樹;
  3. 先序遍歷二叉樹;
  4. 中序遍歷二叉樹;
  5. 後序遍歷二叉樹
  6. 逐層遍歷二叉樹;
  7. 判斷二叉樹是否為空;
  8. 清空二叉樹;
  9. 以指定元素值建立根節點;
  10. 將一個結點作為指定結點的左孩子插入;
  11. 將一個結點作為指定結點的右孩子插入;
  12. 刪除以指定結點為根的子樹;
  13. 按關鍵字查詢結點;
  14. 修改指定結點的元素值;
  15. 獲取指定結點的雙親結點;
  16. 計算二叉樹的深度;
  17. 計算二叉樹的葉子結點數;

二叉樹的抽象資料型別

ADT BinTree
{
    Data:
    具有二叉樹型結構的0或多個相同型別資料元素的集合
    Operations:
       BinTree();           //建立空二叉樹
       ~BinTree();             //刪除二叉樹
       PreOrderTraverse();     //先序遍歷
       InOrderTraverse();      //中序遍歷
       PostOrderTraverse(); //後序遍歷
       LevelOrderTraverse();   //逐層遍歷
       IsEmpty();           //判斷二叉樹是否為空
       CreateRoot();        //以指定元素值建立根結點
       Clear();             //清空二叉樹
       InsertLeftChild();      //將一個結點作為指定結點的左孩子插入
       InsertRightChild();     //將一個結點作為指定結點的右孩子插入
       DeleteSubTree();     //刪除以指定結點為根的子樹
       SearchByKey();          //按關鍵字查詢結點
       ModifyNodeValue();      //修改指定結點的元素值
       GetParent();         //獲取指定結點的雙親結點
}ADT BinTree;

二叉樹的順序表示

把二叉樹的結點按照完全二叉樹的順序編號規則自上而下、自左至右依次存放在一組地址連續的儲存單元裡構成了二叉樹的順序儲存,樹中結點的編號就可以唯一地反映出結點之間的邏輯關係。

通常通過定義一個一維陣列來表示二叉樹的順序儲存空間。

為了使陣列元素的下標值與其對應的結點編號一致,將下標為0的空間空閒不用或用作其他用途。

二叉樹的順序表示法操作方便,但缺點是容易造成儲存空間浪費。

資料結構-二叉樹

資料結構-二叉樹

 

 

二叉樹順序表示適用於完全二叉樹而不適用於非完全二叉樹。

二叉樹的鏈式表示

鏈式表示通常具有更高的空間利用率,因此在實際應用中一般會使用鏈式表示來儲存二叉樹。

根據一個結點中指標域數量的不同,二叉樹的鏈式表示又可以分為二叉連結串列表示和三叉連結串列表示。

二叉連結串列表示

在二叉連結串列表示中,雙親結點有指向其孩子結點的指標,而孩子結點不包括指向其雙親結點的指標。

二叉樹中每個結點最多有兩個孩子,因此在一個結點中設定兩個指標域leftchild和rightchild分別指向其左孩子和右孩子,資料域data用於存放每個結點中資料元素的值。

如果一個結點沒有左孩子,leftchild指標為空(用NULL或0表示),如果一個結點沒有右孩子,rightchild指標為空(用NULL或0表示)。

資料結構-二叉樹

 

三叉連結串列表示

在三叉連結串列表示中,雙親結點有指向其孩子結點的指標,而孩子結點也包含指向其雙親結點的指標。

在用三叉連結串列表示的二叉樹的每個結點中,除了具有二叉連結串列中的兩個指向孩子結點的指標域leftchild和rightchild外,還有一個指向雙親結點的指標域parent。

根結點沒有雙親,所以它的parent指標為空(用0或NULL表示)。

資料結構-二叉樹

二叉連結串列結點類别範本

template<class T>
class LinkedNode     //結點類
{
    template<class T>
    friend class LinkedBinTree;   
public:
    LinkedNode()//無參建構函式
    {
        m_pLeftChild=m_pRightChild=NULL;
    }
     LinkedNode(const T &x)//有參建構函式
    {
        m_pLeftChild=m_pRightChild=NULL;
        m_data=x;
    }
private:
    T m_data;               //結點資料域
    LinkedNode<T> *m_pLeftChild, *m_pRightChild;     //左右孩子指標
}
資料結構-二叉樹

二叉樹二叉連結串列類别範本

template<class T>
class LinkedBinTree
{
    public:
       LinkedBinTree();               //建構函式,建立空二叉樹
       ~LinkedBinTree();              //解構函式,刪除二叉樹
       bool IsEmpty() const;       //判斷二叉樹是否為空
       LinkNode<T>* CreateRoot(const T& x);          //以指定元素值建立根節點
       void clear();               //清空二叉樹
       LinkedNode<T>* GetRoot();                 //獲取根結點
       LinkedNode<T>* InsertLeftChild(LinkedNode<T> *pNode,const T &x);         //將一個結點作為指定結點的左孩子插入
       LinkedNode<T>* InsertRightChild(LinkedNode<T> *pNode,const T &x);     //將一個結點作為指定結點的右孩子插入
       bool ModifyNodeValue(LinkedNode<T> *pNode,const T &x);  //修改指定結點的元素值
       bool GetNodeValue(LinkedNode<T> *pNode,T &x);           //獲取指定結點的元素值
       LinkNode<T>* GetLeftChild(LinkedNode<T> *pNode);    //獲取指定結點的左孩子結點
       LinkNode<T>* GetRightChild(LinkedNode<T> *pNode);       //獲取指定結點的右孩子結點
       void PreOrderTraverse(LinkedNode<T> *pNode);        //按遞迴方式先序遍歷
       void InOrderTraverse(LinkedNode<T> *pNode);         //按遞迴方式中序遍歷
       void PostOrderTraverse(LinkedNode<T> *pNode);       //按遞迴方式後序遍歷
       void PreOrderTraverse();       //按非遞迴方式先序遍歷
       void InOrderTraverse();        //按非遞迴方式中序遍歷
       void PostOrderTraverse();          //按非遞迴方式後序遍歷
       void LevelOrderTraverse();         //按非遞迴方式逐層遍歷
       LinkedNode<T>* GetParent(LinkedNode<T> *pNode);     ////按非遞迴方式獲取指定結點的雙親結點
       void DeleteSubTree(LinkedNode<T> *pNode);        //刪除以指定結點為根的子樹
       void DeleteSubTreeNode(LinkedNode<T> *pNode);    //由DeleteSubTree函式呼叫按非遞迴方式刪除以指定結點為根的子樹
       LinkedNode<T>* SearchByKey(const T &x);       //按非遞迴方式根據關鍵字查詢關鍵結點
    private:
       LinkedNode<T> *m_pRoot;        //指向根結點的指標
};
資料結構-二叉樹

二叉連結串列的實現

//實現建立空二叉樹
template<class T>
LinkBinTree<T>::LinkBinTree()
{
    m_pRoot=NULL;    //將指向根結點的指標置為空
}
//實現以指定元素值建立根結點
template<class T>
LinkedNode<T>* LinkBinTree<T>::CreateRoot(const T& x)
{
    if(m_pRoot!=NULL)
        m_pRoot->m_data=x;          //若原先存在根結點,則直接將根結點的值置為x
    else
        m_pRoot = new LinkedNode<T>(x);       //否則建立一個新結點作為根節點
    return m_pRoot;
}
//判斷二叉樹是否為空
template<class T>
bool LinkBinTree<T>::IsEmpty()
{
    if(m_pRoot==NULL)
        return true;
    return false;
}
//獲取根結點
template<class T>
LinkedNode<T>* LinkBinTree<T>::GetRoot()
{
    return m_pRoot;
}
//將一個結點作為指定結點的左孩子插入
template<class T>
LinkedNode<T>* LinkBinTree<T>::InsertLeftChild(LinkedNode<T> *pNode,const T &x)
{
    LinkedNode<T> *pNewNode;
    if(pNode==NULL)
        return NULL;
    pNewNode = new LinkedNode<T>(x);
    if(pNewNode==NULL)   return NULL;
    pNode->m_pLeftChild = pNewNode;
    return pNewNode;
}
//將一個結點作為指定結點的右孩子插入
template<class T>
LinkedNode<T>* LinkBinTree<T>::InsertRightChild(LinkedNode<T> *pNode,const T &x)
{
    LinkedNode<T> *pNewNode;
    if(pNode==NULL)
        return NULL;
    pNewNode = new LinkedNode<T>(x);
    if(pNewNode==NULL)   return NULL;
    pNode->m_pRightChild = pNewNode;
    return pNewNode;
}
//修改指定結點的元素值
template<class T>
bool LinkBinTree<T>::ModifyNodeValue(LinkNode<T> *pNode,const T &x)
{
    if(pNode==NULL)
        return false;
    pNode->m_data = x;
    return true;
}
//獲取指定結點的元素值
template<class T>
bool LinkBinTree<T>::GetNodeValue(LinkNode<T> *pNode,T &x)
{
    if(pNode==NULL)
        return false;
    x = pNode->m_data;
    return true;
}
//獲取指定結點的左孩子結點
template<class T>
LinkedNode<T>* LinkBinTree<T>::GetLeftChild(LinkNode<T> *pNode)
{
    if(pNode==NULL)
        return NULL;
    return pNode->m_pLeftChild;
}
//獲取指定結點的右孩子結點
template<class T>
LinkedNode<T>* LinkBinTree<T>::GetRightChild(LinkNode<T> *pNode)
{
    if(pNode==NULL)
        return NULL;
    return pNode->m_pRightChild;
}
//獲取指定結點的雙親結點
//二叉連結串列中,結點沒有指向其雙親結點的指標,要獲取雙親結點則需要從根結點開始遍歷二叉樹直至找到指定結點的雙親結點
template<class T>
LinkedNode<T>* LinkBinTree<T>::GetParent(LinkNode<T> *pNode)
{
    LinkQueue<LinkedNode<T>*> q;
    LinkedNode<T> *pCurNode = NULL;
    if(pNode==m_pRoot)   //若指定結點pNode為根結點,則返回空
        return NULL;
    if(m_pRoot==NULL) //若二叉樹是空樹,則返回空
        return NULL;
    q.Insert(m_pRoot);//將根結點入隊
    while(!q.IsEmpty())  //當佇列不為空時迴圈
    {
        q.Delete(pCurNode);
        if(pCurNode->m_pLeftChild==pNode || pCurNode->m_pRightChild==pNode)
            return pCurNode;
        if(pCurNode->m_pLeftChild)
            q.Insert(pCurNode->m_pLeftChild);
        if(pCurNode->m_pRightChild)
            q.Insert(pCurNode->m_pRightChild);
    }
    return NULL;
}
//刪除以指定結點為根的子樹
//刪除以指定結點為根的子樹,一方面要將子樹從二叉樹中刪除,另一方面要將子樹中的結點釋放
//通過將指定結點的雙親結點的指標值置空來將子樹從二叉樹中刪除(刪除整棵二叉樹,應將根結點指標值置空)
//將子樹中的結點釋放,就是遍歷子樹中所有結點,將各結點佔據記憶體釋放。
template<class T>
void LinkBinTree<T>::DeleteSubTree(LinkNode<T> *pNode)
{
    LinkNode<T> *pParentNode = NULL;
    if(pNode==NULL)      //若指定結點為空,則返回
        return;
    if(m_pRoot==pNode)      //若將整棵二叉樹刪除,則令根結點為空
        m_pRoot = NULL;
    else if((pParentNode=GetParent(pNode))!=NULL)    //否則若指定結點存在雙親結點,則將雙親結點的左孩子或右孩子置空
    {
        if(pParentNode->m_pLeftChild==pNode)
            pParentNode->m_pLeftChild=NULL;
        else
            pParentNode->m_pLeftChild=NULL;
    }
    else      //否則指定結點不是二叉樹中的結點,直接返回
        return;
    DeleteSubTreeNode(pNode);   //呼叫DeleteSubTreeNode刪除以pNode為根的子樹
}
//由DeleteSubTree函式呼叫按非遞迴方式刪除以指定結點為根的子樹
template<class T>
void LinkBinTree<T>::DeleteSubTreeNode(LinkNode<T> *pNode)
{
    LinkQueue<LinkedNode<T>*> q;
    LinkedNode<T> *pCurNode = NULL;
    if(pNode==NULL)
        return;
    //按非遞迴層次遍歷的方式刪除子樹
    q.Insert(pNode);
    while(!q.IsEmpty())  //當佇列不為空時迴圈
    {
        q.Delete(pCurNode);
        if(pCurNode->m_pLeftChild)
            q.Insert(pCurNode->m_pLeftChild);
        if(pCurNode->m_pRightChild)
            q.Insert(pCurNode->m_pRightChild);
        delete pCurNode;
    }
}
//根據關鍵字查詢結點
//根據關鍵字查詢結點,實質就是按照某種規則依次訪問二叉樹中每個結點,直到找到與關鍵字匹配的結點。
template<class T>
LinkNode<T>* LinkBinTree<T>::SearchByKey(const T &x)
{
    LinkQueue<LinkedNode<T>*> q;
    LinkedNode<T> *pMatchNode = NULL;
    if(m_pRoot==NULL)
        return NULL;
    q.Insert(m_pRoot);      //按非遞迴方式根據關鍵字查詢關鍵結點
    while(!q.IsEmpty())  //當佇列不為空時迴圈
    {
        q.Delete(pMatchNode);
        if(pMatchNode->m_data==x)
            return pMatchNode;
        if(pMatchNode->m_pLeftChild)
            q.Insert(pMatchNode->m_pLeftChild);
        if(pMatchNode->m_pRightChild)
            q.Insert(pMatchNode->m_pRightChild);
    }
    return NULL;
}
//清空二叉樹
template<class T>
void LinkBinTree<T>::Clear()
{
    DeleteSubTree(m_pRoot);
}
//刪除二叉樹
template<class T>
LinkBinTree<T>::~LinkBinTree()
{
    clear();  //清空二叉樹中結點
}
資料結構-二叉樹

實現二叉連結串列需要用到佇列和棧,

二叉樹的遍歷,就是按照某種規則依次訪問二叉樹中的每個結點,且每個結點僅被訪問一次。根據結點訪問順序的不同,分為四種遍歷方式:先序遍歷、中序遍歷、後續遍歷、逐層遍歷

#include<iostream>
#include "LinkQueue.h"         //連結佇列類别範本
#include "LinkStack.h"         //連結棧類别範本

二叉樹的先序遍歷

二叉樹先序遍歷遞迴定義:對於一棵二叉樹,先訪問其根結點,再訪問根結點的左、右子樹,對於左、右子樹的結點仍然按照先序遍歷訪問,即先訪問根結點,再訪問左、右子樹。

約定:先序、中序、後序遍歷中均是先訪問左子樹後訪問右子樹。

//按遞迴方式先序遍歷
template<class T>
void LinkBinTree<T>::PreOrderTraverse(LinkedNode<T> *pNode)
{
    if(pNode==NULL)
        return;
    cout<<pNode->m_data<<" ";
    PreOrderTraverse(pNode->m_pLeftChild);
    PreOrderTraverse(pNode->m_pRightChild);
}
資料結構-二叉樹

按非遞迴方式先序遍歷

演算法設計:非遞迴先序遍歷根據訪問規律利用棧來實現,棧頂元素即是下一個要訪問的子樹的根結點。具體步驟如下:

  1.  將二叉樹的根結點入棧;
  2. 將棧頂元素出棧並訪問(即先訪問根結點),若棧頂元素存在右子樹則將右子樹入棧,若棧頂元素存在左子樹則將左子樹入棧;
  3. 重複步驟2,直至棧空。
//按非遞迴方式先序遍歷
template<class T>
void LinkBinTree<T>::PreOrderTraverse()
{
    LinkStack<LinkedNode<T>*> s;
    LinkedNode<T> *pNode = NULL;
    if(m_pRoot==NULL)
        return;
    //將根結點入棧
    s.Push(m_pRoot);
    while(!s.IsEmpty())     //棧不為空時迴圈
    {
        s.Pop(pNode);    //棧頂元素出棧並被訪問
        cout<<pNode->m_data<<" ";
        if(pNode->m_pRightChild){          //若結點存在右子樹,則將右子樹根結點入棧
            s.Push(pNode->m_pRightChild);
        }
        if(pNode->m_pLeftChild){           //若結點存在左子樹,則將左子樹根結點入棧
            s.Push(pNode->m_pLeftChild);
        }
    }
}
資料結構-二叉樹

二叉樹的中序遍歷

中序遍歷遞迴方式定義:對於一棵二叉樹,先訪問根結點左子樹,再訪問根結點,最後右子樹;對於左、右子樹中的結點仍然按照中序遍歷訪問。

資料結構-二叉樹​ 

//按遞迴方式中序遍歷
template<class T>
void LinkBinTree<T>::InOrderTraverse(LinkedNode<T> *pNode)
{
    if(pNode==NULL)
        return;
    InOrderTraverse(pNode->m_pLeftChild);
    cout<<pNode->m_data<<" ";
    InOrderTraverse(pNode->m_pRightChild);
}
資料結構-二叉樹

 

中序遍歷非遞迴方式

//按非遞迴方式中序遍歷    待驗證!!!!!
template<class T>
void LinkBinTree<T>::PreOrderTraverse()
{
    LinkStack<LinkedNode<T>*> s;
    LinkedNode<T> *pNode = NULL;
    if(m_pRoot==NULL)
        return;
    //將根結點入棧
    s.Push(m_pRoot);
    while(!s.IsEmpty())     //棧不為空時迴圈
    {
        s.Pop(pNode);    //棧頂元素出棧並被訪問
        if(pNode->m_pLeftChild==NULL)
        {
            cout<<pNode->m_data<<" ";
            if(pNode->m_pRightChild){
                s.Push(pNode->m_pRightChild);
            }
        }else
        {
            s.Push(pNode->m_pLeftChild);
        }
    }
}
資料結構-二叉樹

二叉樹的後序遍歷

後序遍歷遞迴方式定義:對於一棵二叉樹,先訪問根結點的左子樹,後訪問右子樹,最後訪問根結點;對於左、右子樹中的結點仍按照後序遍歷的方式訪問。

資料結構-二叉樹

//按遞迴方式後序遍歷
template<class T>
void LinkBinTree<T>::PostOrderTraverse(LinkedNode<T> *pNode)
{
    if(pNode==NULL)
        return;
    PostOrderTraverse(pNode->m_pLeftChild);
    PostOrderTraverse(pNode->m_pRightChild);
    cout<<pNode->m_data<<" ";
}
資料結構-二叉樹

非遞迴方式後序遍歷

//按非遞迴方式後序遍歷    待驗證!!!!!
template<class T>
void LinkBinTree<T>::PreOrderTraverse()
{
    LinkStack<LinkedNode<T>*> s;
    LinkedNode<T> *pNode = NULL;
    if(m_pRoot==NULL)
        return;
    //將根結點入棧
    s.Push(m_pRoot);
    while(!s.IsEmpty())     //棧不為空時迴圈
    {
        s.Pop(pNode);    //棧頂元素出棧並被訪問
        if(pNode->m_pLeftChild!=NULL)
        {
            if(pNode->m_pRightChild!=NULL){
               s.Push(pNode->m_pRightChild);
               s.Push(pNode->m_pLeftChild);
            }else
            {
                s.Push(pNode->m_pLeftChild);
            }
        }else if(pNode->m_pRightChild!=NULL)
        {
            s.Push(pNode->m_pRightChild);
        }else{
            cout<<pNode->m_data<<" ";
        }
     
    }
}
資料結構-二叉樹

二叉樹的逐層遍歷

逐層遍歷:是指從第1層開始依次對每層中的結點按照從左至右的順序進行訪問。

資料結構-二叉樹

演算法設計:

  1.  將二叉樹的根結點入隊;
  2. 將隊頭元素出隊並訪問,若隊頭元素有左子樹則將左子樹根結點入隊,若隊頭元素有右子樹則將右子樹根結點入隊;
  3. 重複步驟2,直至佇列為空。
//按非遞迴方式逐層遍歷
template<class T>
void LinkBinTree<T>::LevelOrderTraverse()
{
    LinkQueue<LinkedNode<T>*> q;
    LinkedNode<T> *pNode = NULL;
    if(m_pRoot==NULL)
        return;
    q.Insert(m_pRoot);//將根節點入隊
    while(!q.IsEmpty())//當佇列不為空是迴圈
    {
        q.Delete(pNode); //將隊頭元素出隊並訪問
        cout<<pNode->m_data<<" ";
        //若結點存在左子樹,將左子樹根結點入隊
        if(pNode->m_pLeftChild)
            q.Insert(pNode->m_pLeftChild);
        //若結點存在右子樹,將左子樹根結點入隊
        if(pNode->m_pRightChild)
            q.Insert(pNode->m_pRightChild);
    }
}
資料結構-二叉樹

二叉排序樹

二叉排序樹又稱二叉查詢樹,它或者是一棵空樹,或者是具有如下性質的樹:

  1. 若它的左子樹非空,則左子樹上所有結點的值均小於根結點的值。
  2. 若它的右子樹非空,則右子樹上所有結點的值均大於根結點的值。
  3. 左、右子樹也分別是二叉排序樹。

二叉排序樹的生成過程

二叉排序樹中插入一個新結點,應保證插入新結點後的二叉樹仍然是一棵二叉排序樹。對於一個給定元素k,將其插入到二叉排序樹中的具體步驟如下:

  1.  若二叉排序樹為空樹,則將元素k作為二叉排序樹的根結點。
  2. 若k等於根結點的值,則該元素已經是二叉排序樹中的結點,不需要重複插入,直接返回;若k小於根結點的值,則將k插入到左子樹中;若k大於根結點的值,則將k插入到右子樹中。
  3. 重複步驟2,直至要插入的子樹為空,此時將k作為該子樹的根節點。
//二叉排序樹插入新結點的實現,將元素k插入到二叉排序樹btree中
template<class T>
void InsertBST(LinkedBinTree<T> &btree,T K)
{
    LinkedNode<T> *pNode = NULL,*pChild = NULL;
    T x;
    //若二叉排序樹為空樹,則將K作為根結點
    if(btree.IsEmpty())
    {
        btree.CreateRoot(K);
        return;
    }
    pNode = btree.GetRoot();
    while(pNode!=NULL)
    {
        btree.GetNodeValue(pNode,x);
        if(K==x)      //若K已是二叉排序樹中的結點
            return;
        if(K<x)       //將K插入到該根結點的左子樹中
        {        
            if((pChild=btree.GetLeftChild(pNode))!=NULL) //若該根結點有左子樹,則繼續尋找新位置
                pNode=pChild;
            else
            {      //否則將新元素作為根結點的左孩子
                btree.InsertLeftChild(pNode,K);
                return;
            }
        }
        else
        {         //將K插入到該根結點的右子樹中
             if((pChild=btree.GetRightChild(pNode))!=NULL)  //若該根結點有右子樹,則繼續尋找新位置
                pNode=pChild;
            else
            {      //否則將新元素作為根結點的右孩子
                btree.InsertRightChild(pNode,K);
                return;
            }
        }
    }
}
//生成二叉排序樹,根據傳入資料集合R生成二叉排序樹btree
template<class T>
void CreateBST(T R[],int nSize,LinkedBinTree<T> &btree)
{
    int n;
    //將R中的元素逐一插入到二叉排序樹btree中
    for(n=1;n<=nSize;n++)
        InsertBST(btree,R[n]);
}
//以遞迴方式實現二叉排序樹的查詢
template<class T>
LinkedNode<T>* SearchBST(LinkedNode<T>* pRoot,T K)
{
    LinkedBinTree<T> btree;
    T x;
    if(pRoot==NULL)      //若子樹為空,則查詢失敗
        return NULL;
    btree.GetNodeValue(pRoot,x);
    if(K==x)         //若K等於根結點的值,則查詢成功
        return pRoot;
    else if(K<x)     //在左子樹中繼續進行二叉排序樹的查詢
        return SearchBST(btree.GetLeftChild(pRoot),K);
    else             //在右子樹中繼續進行二叉排序樹的查詢
        return SearchBST(btree.GetRightChild(pRoot),K);
}
資料結構-二叉樹

不同結構的二叉樹,查詢效率不一致。

如何生成平衡二叉樹?

哈夫曼樹

哈夫曼樹,又稱最優二叉樹,是指在一類有著相同葉子結點的樹中具有最短帶權路徑長度的二叉樹。

基本術語

1、結點的權和帶權路徑長度:在實際應用中,往往給樹中的結點賦予一個具有某種意義的實數,該實數就稱為結點的權。結點的帶權路徑長度是指從樹根到該結點的路徑長度與結點的權的乘積。

2、樹的帶權路徑長度:是指樹中所有葉子結點的帶權路徑長度之和,記作:

資料結構-二叉樹

其中,n為葉子結點的數目,Wi為第i個葉子結點的權,Li為根結點到第i個葉子結點的路徑長度,可知WiLi為第i個葉子結點的帶權路徑長度。

資料結構-二叉樹

 

哈夫曼樹及其構造方法

在由n個葉子結點構成的一類二叉樹中,具有最短帶權路徑長度的二叉樹稱為哈夫曼樹。

構造方法如下:

  1.  已知n個權值為Wi(i=1,2,3,4...n)的結點,將每個結點作為根結點生成n棵只有根結點的二叉樹Ti,形成森林F = {T1,T2,T3,T4...Tn}。
  2. 從森林F中選出根結點權值最小的兩棵二叉樹Tp和Tq,並通過新增新的根結點將它們合併為一棵新二叉樹,新二叉樹中Tp和Tq分別作為根結點的左子樹和右子樹,且根結點的權值等於Tp和Tq兩棵二叉樹的根結點權值之和。以合併後生成的新二叉樹替代森林F中的原有二叉樹Tp和Tq。
  3. 重複步驟2,直至森林F中只存在一棵二叉樹。

哈夫曼碼

哈夫曼編碼是利用哈夫曼樹得到的一種不定長的二進位制編碼,它在資料壓縮領域有著廣泛應用。

哈夫曼編碼

利用哈夫曼碼進行資料壓縮的基本原理:對於出現頻率較高的字元,使用較短的編碼;而對於出現頻率較低的字元,則使用較長的編碼,從而使得用於字元序列的編碼總長度最短、節省儲存空間。

哈夫曼編碼是指將用其他編碼法表示的字元序列轉成用哈夫曼碼錶示以減少儲存空間,其具體方法為:

  • 以要編碼的字符集C={c1,c2,c3...cn}作為葉子結點、字元出現的頻度或次數W={w1,w2,w3...wn}作為結點的權,構造哈夫曼樹。
  • 規定哈夫曼樹中,從根結點開始,雙親結點到左孩子結點的分支標為0,雙親結點到右孩子結點的分支標為1。從根結點到某一葉子結點經過的分支形成的編碼即是該葉子結點所對應自負的哈夫曼碼。

哈夫曼解碼

哈夫曼解碼是指將用哈夫曼碼錶示的字元序列轉成其他編碼法表示以讓計算正確顯示字元內容,其具體方法:

  •  將用於表示字元序列的哈夫曼碼逐位取出並送入哈夫曼樹中。
  • 從哈夫曼樹的根結點開始,對於每一個結點,遇到位0則經左分支到其左孩子,遇到位1則經右分支到其右孩子。重複該過程直至到達某一個葉子結點,該葉子結點所對應的字元即是解碼結果。解碼一個字元後回到哈夫曼樹的根結點開始解碼下一個字元。

相關文章