1. 程式人生 > >二叉樹遞迴實現

二叉樹遞迴實現

二叉樹遞迴實現比較符合樹的特點,也較容易理解,程式碼也較為簡單。接下來進行圖文詳解。

C程式碼下載
C++程式碼下載
java程式碼下載
( 備用地址下載)

導航
1.建立二叉樹
2.前序遍歷二叉樹
3.中序遍歷二叉樹
4.後序遍歷二叉樹
5.層次遍歷二叉樹
6.計算二叉樹深度
7.計算二叉樹雙分支節點數
8.計算二叉樹單分支節點數
9.計算二叉樹葉子節點數
10.新增節點
11.查詢二叉樹中的節點

注:有一些重複的程式碼且多的就不重複貼出來了,需要的可以點上面的連結去下載。







一、建立二叉樹

按照前序遍歷來建立,給定一個串,其規則是空格代表空節點
例如給定串:ABC D EF G ;


建立步驟如下:

這個回溯就相當於出棧一樣,當條件不滿足了,就return,然後系統維護的棧就會執行出棧操作,這樣就相當於回溯了。由此可以推出非遞迴的實現方式。

1.C語言實現

/*
* function              建立二叉樹(前序建立)
* param     PBTree*     root(二級指標)
* param     char* s     根據給定字串建立
* param     int i       指示當前s的下標
* return                無
*/
void CreateBTree(PBTree* root, char
* s, bool newTree) { static int i = -1; //s下標 if (newTree) i = -1; //因為i是靜態變數,所以只能手動修改它值,如果是新的二叉樹則重置它的值 ++i; //如果s為空,即二叉樹為空,若s=""或者到達末尾s[i]都等於'\0',這裡空節點用空格表示 if (!s || s[i] == '\0' || s[i] == ' ') (*root) = NULL; else { *root = (PBTree)malloc(sizeof(_binary_tree)); //建立節點
(*root)->data = s[i]; CreateBTree(&(*root)->left, s, false); //遞迴建立左子樹 CreateBTree(&(*root)->right, s, false); //遞迴建立右子樹 } }

1).為什麼要用二級指標?
因為傳進去當引數的根節點指標會改變,所以需要一個指標的指標來一直的指向根節點

2).為什麼要用靜態變數i?
因為如果i不是靜態的,每次遞迴的時候都會重置i的值。

3).為什麼要加個bool標誌?
因為i是靜態的,只能手動去修改它的值,而只有當新建立一棵樹的時候才需要重置,遞迴的時候是不需要的。

2.C++實現

/*
* function              建立二叉樹
* param     Node**      二級指標
* param     string      s
* return                無
*/
template<typename T>
void BTree<T>::CreateBtree(Node** root, string s)
{
    ++m_i;
    //如果s為空,即二叉樹為空,若s=""或者到達末尾s[i]都等於'\0',這裡空節點用空格表示
    if (s.empty() || s[m_i] == ' ')
        (*root) = nullptr;
    else
    {
        *root = new Node(s[m_i]);           //建立節點
        CreateBtree(&(*root)->left, s);     //遞迴建立左子樹   
        CreateBtree(&(*root)->right, s);    //遞迴建立右子樹
    }
}

這裡不再需要靜態變數,因為已經將下標作為成員變量了,只當新建立樹的時候才重置它的值。

3.java實現

/*
    * function              建立二叉樹
    * param     Node        node
    * param     string      s
    * return                返回根節點
    */
    private Node createBtree(Node node, String s) {
        ++iPos;
        //如果s為空,即二叉樹為空,若s=""或者到達末尾s[i]都等於'\0',這裡空節點用空格表示
        if (s.isEmpty() || s.charAt(iPos) == ' ')
            return null;
        else {
            node = new Node(s.charAt(iPos));
            node.setLeft(createBtree(node.getLeft(),s));
            node.setRight(createBtree(node.getRight(),s));
            return node;
        }
    }

一開始寫這個java遞迴建立二叉樹的時候,我就卡在了,如何記錄根節點的地址,因為java中沒有指標這種概念,所以二級指標這種方法就不可能了,網上有一篇部落格說是可以寫一個類模擬二級指標,但是這樣又太複雜,我不想這樣寫。然後轉啊轉的,就找到了一個寫得比較好的。這個思想和C/C++的差不多,就是最後會返回根節點回去,也許你會想,它為什麼會返回根節點?因為第一個進棧的就是根節點,所以最後一個出棧的就是根節點。





二、前序遍歷二叉樹
遍歷順序是:根節點 -> 左節點 -> 右節點
然後一個二叉樹又可以分為很多子樹,每一顆子樹都會有根、左、右節點,所以遞迴的思想就很好的體現在這裡了。

1.C程式碼

/*
* function              前序遍歷
* param     PBTree      root
* return                無
*/
void PreOrder(PBTree root)
{
    if (root)
    {
        printf("%c", root->data);
        PreOrder(root->left);
        PreOrder(root->right);
    }
}

2.C++程式碼

/*
* function              前序遍歷
* param     Node*       root
* return                無
*/
template<typename T>
void BTree<T>::PreOrder(Node* root)
{
    if (root)
    {
        cout << root->data << " ";
        PreOrder(root->left);
        PreOrder(root->right);
    }
}

3.java程式碼

    /*
    * function              前序遍歷
    * param     Node        root
    * return                無
    */
    private void preOrder(Node root) {
        if (root != null) {
            System.out.print(root.data + " ");
            preOrder(root.left);
            preOrder(root.right);
        }
    }





三、中序遍歷二叉樹
遍歷順序是:左節點 -> 根節點 -> 右節點

1.C程式碼

/*
* function              中序遍歷
* param     PBTree      root
* return                無
*/
void InOrder(PBTree root)
{
    if (root)
    {
        InOrder(root->left);
        printf("%c", root->data);
        InOrder(root->right);
    }
}

2.C++程式碼

/*
* function              中序遍歷
* param     Node*       root
* return                無
*/
template<typename T>
void BTree<T>::InOrder(Node* root)
{
    if (root)
    {
        InOrder(root->left);
        cout << root->data << " ";
        InOrder(root->right);
    }
}

3.java程式碼

    /*
    * function              中序遍歷
    * param     Node        root
    * return                無
    */
    private void inOrder(Node root) {
        if (root != null) {
            inOrder(root.left);
            System.out.print(root.data + " ");
            inOrder(root.right);
        }
    }





四、後序遍歷二叉樹
遍歷順序:左節點 -> 右節點 -> 根節點

1.C程式碼

/*
* function              後序遍歷
* param     PBTree      root
* return                無
*/
void PostOrder(PBTree root)
{
    if (root)
    {
        PostOrder(root->left);
        PostOrder(root->right);
        printf("%c", root->data);
    }
}

2.C++程式碼

/*
* function              後序遍歷
* param     Node*       root
* return                無
*/
template<typename T>
void BTree<T>::PostOrder(Node* root)
{
    if (root)
    {
        PostOrder(root->left);
        PostOrder(root->right);
        cout << root->data << " ";
    }
}

3.java程式碼

/*
    * function              後序遍歷
    * param     Node        root
    * return                無
    */
    private void postOrder(Node root) {
        if (root != null) {
            postOrder(root.left);
            postOrder(root.right);
            System.out.print(root.data + " ");
        }
    }





五、層次遍歷二叉樹
從左到右遍歷,這個需要一個輔助函式,這個函式負責遍歷指定層數,然後主函式負責迴圈遍歷層數。這個缺點就是,每一次的遍歷都要從根節點開始,當層數很大的時候,遍歷就會非常消耗時間。

/*
* function              層次遍歷輔助函式
* param     PBTree      root
* param     int         level
* return                無
*/
void PrintNodeAtLevel(PBTree root, int level)
{
    // 空樹或層級不合理
    if (NULL == root || level < 1)
        return;

    if (1 == level) //相當於輸出根節點,因為每一個節點都可以左為子樹的根節點
    {
        printf("%c", root->data);
        return;
    }

    // 左子樹的 level - 1 級
    PrintNodeAtLevel(root->left, level - 1);

    // 右子樹的 level - 1 級
    PrintNodeAtLevel(root->right, level - 1);
}





六、計算二叉樹深度
先計算左子樹深度,再計算右子樹深度,然後返回較大的那個+1

/*
* function              計算二叉樹深度
* param     PBTree      root
* return                返回二叉樹深度
*/
int BTreeDepth(PBTree root)
{
    if (!root)
        return 0;
    else
    {
        int lDepth = BTreeDepth(root->left);   //遞迴計算左子樹的深度
        int rDepth = BTreeDepth(root->right);  //遞迴計算右子樹的深度
                                            //返回較深的那個+1
        if (lDepth >= rDepth)
            return lDepth + 1;
        else
            return rDepth + 1;
    }
}





七、計算二叉樹雙分支節點數
用前序遍歷的方法,一個個節點遍歷過去,如果該節點不為空,則判斷它是否有左孩子和有孩子,如果兩個都有,則count + 1

/*
* function              計算二叉樹雙分支節點數
* param     PBTree      root
* return                返回二叉樹雙分支節點數
*/
int GetN2(PBTree root, bool newTree)
{
    static int count = 0;
    if (newTree) count = 0;
    if (root == NULL)   //如果二叉樹為空,則返回0
        return 0;
    else
    {
        if (root->left && root->right)  //當該節點有兩個分支的時候+1
            ++count;
        GetN2(root->left, false);    //遍歷左子樹
        GetN2(root->right, false);   //遍歷右子樹
    }
    return count;
}





八、計算二叉樹單分支節點數
和計算雙分支節點的方法一樣,只需要把判斷語句改一下即可

if ((root->left && !root->right) || (!root->left && root->right))  //當該節點僅且只有一個分支的時候+1
            ++count;





九、計算二叉樹葉子節點數
這個就簡單了,有一個公式: n0 = n2 + 1

/*
* function              計算二叉樹終端節點數
* param     PBTree      root
* return                二叉樹終端節點數
*/
int GetN0(PBTree root)
{
    return GetN2(root, true) + 1; //計算公式n0 = n2 + 1;
}





十、新增節點
可以用前序、中序、後序、層次遍歷的方法來新增,前三個的缺點很明顯,最後新增後可能會退化成一個長長的單鏈表。所以這裡採用層次遍歷的方法新增,一層層掃描,遇到空節點就新增在它那裡。

/*
* function              新增節點的輔助函式
* param     PBTree      root
* param     int         level
* param     char        ch
* param     bool*       標記是否新增成功
* return                無
*/
void LevelAdd(PBTree root, int level,char ch,bool* bAdd)
{
    //用來標記新的節點是否已經新增,如果添加了就退出了,避免重複新增
    if (*bAdd)return;
    //如果該節點為空,則可以將ch賦值給該節點
    if (!root->left || !root->right)
    {
        PBTree node = (PBTree)malloc(sizeof(_binary_tree)); //建立節點
        node->data = ch;
        node->left = NULL;
        node->right = NULL;
        if(!root->left)
            root->left = node;
        else
            root->right = node;
        *bAdd = true;
        return;
    }
    //層級不合理
    if (level < 1)
        return;
    //遞迴結束條件
    if (1 == level)
        return;

    // 左子樹的 level - 1 級
    LevelAdd(root->left, level - 1, ch, bAdd);

    // 右子樹的 level - 1 級
    LevelAdd(root->right, level - 1, ch, bAdd);
}

/*
* function              新增節點值(新增的位置是不確定的)
* param     PBTree      root
* param     char        ch
* return                無
*/
void AddValue(PBTree root,char ch)
{
    //採用層次遍歷的辦法,一層層掃描,若有空的地方,則新增到該地方
    if (NULL == root) //如果此時二叉樹為空,則建立根節點
    {
        root = (PBTree)malloc(sizeof(_binary_tree));    //建立節點
        root->data = ch;
        root->left = NULL;
        root->right = NULL;
        return;
    }   
    int depth = BTreeDepth(root);

    bool bAdd = false;   //標記是否新增成功,避免重複新增
    for (int i = 1; i <= depth; i++)
    {
        if (bAdd)   //如果已經新增成功,則退出
            break;
        LevelAdd(root, i, ch, &bAdd);
    }       
}

十一、查詢二叉樹中的節點

用前序遍歷的方法查詢

/*
* function              查詢該值
* param     PBTree      root
* param     char        ch
* param     bool        標誌是否是第一次查詢,如果是第一次要將標誌重置,因為靜態變數要手動重置它的值
* return                若存在則返回true,否則返回false
*/
bool Search(PBTree root, char ch,bool first)
{
    static bool bFind = false;
    if (first) bFind = false;
    if (bFind)return true;    //如果已經找到了就不需要繼續查找了
    //利用前序遍歷來查詢
    if (root)
    {
        if (root->data == ch)
            bFind = true;
        Search(root->left, ch, false);
        Search(root->right, ch, false);
    }
}