1. 程式人生 > >如何寫一顆二叉樹(遞迴)【菜鳥學習日記】

如何寫一顆二叉樹(遞迴)【菜鳥學習日記】

老規矩,開篇點題,今天寫了二叉樹,整理總結一下

要寫二叉樹,先畫出一顆來
這裡寫圖片描述

二叉樹我是用連結串列來實現的

1、每一個節點包含資料,左指標和右指標,分別指向左孩子和右孩子

這裡寫圖片描述

//建立節點型別
//節點中有資料,有指標
template<class T>
struct  BinaryTreeNode
{
    T _data;//資料
    BinaryTreeNode<T> *_left;//左孩子
    BinaryTreeNode<T> *_right;//右孩子

    //初始化
    BinaryTreeNode(const T&x)
        :_data(x)
        , _left(NULL
) , _right(NULL) {} };

2、然後開始建立一個二叉樹類

並建立樹的各個節點

思路:

這裡寫圖片描述
‘#’是我們這裡用到的識別符號(invalid),用來標記的,當然也可以用其它的,當遇到#就代表為空,沒有節點了

從根節點開始按前序的順序遍歷建立(這裡的遍歷用了遞迴的方法)

這裡寫圖片描述

class BinaryTree
{
    typedef BinaryTreeNode<T> Node;
public:
    BinaryTree(T* a,size_t n,const T&invalid)
    {
        size_t index
= 0; _root = GreateTree(a, n, invalid, index); } Node* GreateTree(T* a, size_t n, const T&invalid,size_t &index) { //根->左子樹->右字樹 Node* root = NULL; if (a[index] != invalid) { root = new Node(a[index]);//建立根節點 root->_left = GreateTree(a, n, invalid, ++index
);//左樹 root->_right = GreateTree(a, n, invalid, ++index);//右樹 } return root; } protected: Node* _root;//根節點 };

3、建立好一棵樹,就可以實現它的其它介面了

前序、種序、後續遍歷,遍歷其實就是
列印的順序不同

有一點要注意的是,因為我們遍歷時要有根節點引數,但類外拿不到樹的根節點,所以再寫一些要用根節點的介面時,要有一個無參型別的函式,在類的內部再將根節點傳參訪問

//前序遍歷
    void PrevOrder()
    {
        PrevOrder(_root);
    }
    //根->左->右
    void PrevOrder(Node* root)
    {
        //為空樹,返回
        if (root == NULL)
        {
            return;
        }
        //不為空樹
        cout << root->_data <<" ";
        PrevOrder(root->_left);
        PrevOrder(root->_right);

    }
    //中序
    void MidOrder()
    {
        MidOrder(_root);
    }
    //左->根->右
    void MidOrder(Node* root)
    {
        if (root == NULL)
        {
            return;
        }
        MidOrder(root->_left);
        cout << root->_data <<" ";
        MidOrder(root->_right);

    }
    //後序
    void PostOrder()
    {
        PostOrder(_root);
    }
    //左->右->根
    void PostOrder(Node* root)
    {
        if (root == NULL)
        {
            return;
        }
        PostOrder(root->_left);
        PostOrder(root->_right);
        cout << root->_data <<" ";

    }
//測試一下
void TestBinaryTree()
{
    int arr[] = { 1, 2, 3, '#', '#', 4, '#', '#', 5, '#', '#'};
    BinaryTree<int> t(arr, sizeof(arr) / sizeof(int), '#');

    cout << "前序:";
    t.PrevOrder();
    cout << endl;

    cout << "中序:";
    t.MidOrder();
    cout << endl;

    cout << "後序:";
    t.PostOrder();
    cout << endl;
}

這裡寫圖片描述

還有一種層序遍歷

層序遍歷與前面三種實現的方法不太一樣,層序的遍歷要藉助一個佇列來實現

這裡寫圖片描述

先將根節點入佇列,然後將其左右孩子入佇列,將隊頭Pop;

每次將隊頭的左右孩子入隊,Pop隊頭,就可以實現層序遍歷了

   //層序
    void LevelOrder()
    {
        LevelOrder(_root);
    }
    void LevelOrder(Node* root)
    {
        if (root == NULL)
        {
            return;
        }
        //藉助棧來實現
        //棧裡放指向節點的指標
        queue<Node*> q;
        q.push(root);
        while (!q.empty())
        {
            //列印隊頭,入棧對頭的左右孩子再隊尾,出隊頭
            Node* front = q.front();//取隊頭
            cout << front->_data << " ";
            if (front->_left != NULL)
            {
                q.push(front->_left);
            }
            if (front->_right != NULL)
            {
                q.push(front->_right);
            }
            q.pop();
        }   
    }

這裡寫圖片描述

實現了四種遍歷的介面,接下來該實現計算節點的幾種介面了,還有計算深度的介面

  • 計算節點總數
  • 計算葉子節點數
  • 計算第K層的節點數
  • 樹的深度

計算節點的總數

可以有兩種思路

  1. 遍歷,計數
  2. 將其轉化為子問題,節點總數=左子樹+右子樹+根

實現一下:

//節點總數
    //方法一:遍歷
    //size_t Size()
    //{
    //  size_t size = 0;//計數
    //  Size(_root,size);
    //  return size;
    //}

    //size_t Size(Node* root,size_t &size)
    //{
    //  if (root == NULL)
    //  {
    //      return 0;
    //  }
    //遍歷計數
    //  size++;
    //  Size(root->_left, size);
    //  Size(root->_right, size);
    //  return size;
    //}

    //方法二:轉化為子問題
    size_t Size()
    {
         return Size(_root);
    }

    size_t Size(Node* root)
    {
        if (root == NULL)
        {
            return 0;
        }
        //左子樹+右子樹+根
        return Size(root->_left) + Size(root->_right)+1;
    }

這裡寫圖片描述

計算葉子節點數

這個問題也是跟上一個問題一樣兩種思路,遍歷或者轉化為子問題

    //方法一:遍歷
    //size_t LeafSize()//葉子節點數
    //{
    //  size_t size = 0;
    //  LeafSize(_root,size);
    //  return size;
    //}
    //size_t LeafSize(Node* root,size_t &size)
    //{
    //  if (root == NULL)
    //  {
    //      return 0;
    //  }
    //  if (root->_left == NULL&&root->_right == NULL)
    //  {
    //      size++;
    //  }   
    //  LeafSize(root->_left,size);
    //  LeafSize(root->_right,size);
    //  return size;
    //}

    //方法二:子問題
    size_t LeafSize()//葉子節點數
    {
        return LeafSize(_root);
    }
    size_t LeafSize(Node* root)
    {
        if (root == NULL)
        {
            return 0;
        }
        //是葉子節點,返回1
        if (root->_left == NULL&&root->_right == NULL)
        {
            return 1;
        }
        //不是葉子節點,再向下遞迴
        return LeafSize(root->_left)+LeafSize(root->_right);
    }

計算第K層的節點數

這裡寫圖片描述
這裡我們雖然要求的是第k層的節點數,但是我們無法直接到達第k層,我們依然要從根節點開始逐層進入,直到k==1,我們就到達了我們要求的那層了,也就是我們遞迴結束的標誌

然後,再來將問題轉化為子問題,第k層的節點數=第k-1層的左子樹+右子樹節點

這裡要注意的是,引數不能用k的引用,因為如果用了引用,當

我們訪問完左子樹時,k已經被修改,再訪問右子樹時,會出錯

    size_t GetKLevel(size_t k)//第k層節點數
    {
        return _GetKLevel(_root,k);
    }
    size_t _GetKLevel(Node* root,size_t k)//這裡注意不能用k的引用
    {
        assert(k > 0);
        if (root == NULL)
        {
            return 0;
        }
        //k==1
        if (k == 1)
        {
            return 1;
        }
        //k>1,第k層的節點數=第k-1層的左子樹+右子樹節點
        if (k > 1)
        {
            return _GetKLevel(root->_left, k - 1) + _GetKLevel(root->_right, k - 1);
        }
    }

樹的深度

這裡寫圖片描述

這個問題也同樣轉化為子問題解決

思路:樹的深度=左右子樹深度大的+1,每一個子樹都是如此,遞迴就行了

當遞迴到葉子時,返回1
這裡寫圖片描述

//深度
    size_t Depth()
    {
        return Depth(_root);
    }
    size_t Depth(Node* root)
    {
        if (root == NULL)
        {
            return 0;
        }
        //為最底層葉子時,返回1
        if (root->_left == NULL&&root->_right == NULL)
        {
            return 1;
        }
        //否則,返回左右子樹大的+1
        return Depth(root->_left) > Depth(root->_right) ?
            Depth(root->_left) + 1 : Depth(root->_right) + 1;
    }

測試一下這顆新樹:
這裡寫圖片描述

總結:其實寫二叉樹,主要就是用了遞迴的思想

還有就是將一個問題轉化為子問去解決