樹是一種比較重要的數據結構,尤其是二叉樹。二叉樹是一種特殊的樹,在二叉樹中每個節點最多有兩個子節點,一般稱爲左子節點和右子節點(或左孩子和右孩子),並且二叉樹的子樹有左右之分,其次序不能任意顛倒。二叉樹是遞歸定義的,因此,與二叉樹有關的題目基本都可以用遞歸思想解決,當然有些題目非遞歸解法也應該掌握,如非遞歸遍歷節點等等。本文努力對二叉樹相關題目做一個較全的整理總結,希望對找工作的同學有所幫助。
二叉樹節點定義如下:
struct BinaryTreeNode
{
int m_nValue;
BinaryTreeNode* m_pLeft;
BinaryTreeNode* m_pRight;
};
詳細解答
1. 求二叉樹中的節點個數
遞歸解法:
(1)如果二叉樹爲空,節點個數爲0
(2)如果二叉樹不爲空,二叉樹節點個數 = 左子樹節點個數 + 右子樹節點個數 + 1
參考代碼如下:
- int GetNodeNum(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL) // 遞歸出口
- return 0;
- return GetNodeNum(pRoot->m_pLeft) + GetNodeNum(pRoot->m_pRight) + 1;
- }
遞歸解法:
(1)如果二叉樹爲空,二叉樹的深度爲0
(2)如果二叉樹不爲空,二叉樹的深度 = max(左子樹深度, 右子樹深度) + 1
參考代碼如下:
- int GetDepth(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL) // 遞歸出口
- return 0;
- int depthLeft = GetDepth(pRoot->m_pLeft);
- int depthRight = GetDepth(pRoot->m_pRight);
- return depthLeft > depthRight ? (depthLeft + 1) : (depthRight + 1);
- }
前序遍歷遞歸解法:
(1)如果二叉樹爲空,空操作
(2)如果二叉樹不爲空,訪問根節點,前序遍歷左子樹,前序遍歷右子樹
參考代碼如下:
- void PreOrderTraverse(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return;
- Visit(pRoot); // 訪問根節點
- PreOrderTraverse(pRoot->m_pLeft); // 前序遍歷左子樹
- PreOrderTraverse(pRoot->m_pRight); // 前序遍歷右子樹
- }
(1)如果二叉樹爲空,空操作。
(2)如果二叉樹不爲空,中序遍歷左子樹,訪問根節點,中序遍歷右子樹
參考代碼如下:
- void InOrderTraverse(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return;
- InOrderTraverse(pRoot->m_pLeft); // 中序遍歷左子樹
- Visit(pRoot); // 訪問根節點
- InOrderTraverse(pRoot->m_pRight); // 中序遍歷右子樹
- }
(1)如果二叉樹爲空,空操作
(2)如果二叉樹不爲空,後序遍歷左子樹,後序遍歷右子樹,訪問根節點
參考代碼如下:
- void PostOrderTraverse(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return;
- PostOrderTraverse(pRoot->m_pLeft); // 後序遍歷左子樹
- PostOrderTraverse(pRoot->m_pRight); // 後序遍歷右子樹
- Visit(pRoot); // 訪問根節點
- }
相當於廣度優先搜索,使用隊列實現。隊列初始化,將根節點壓入隊列。當隊列不爲空,進行如下操作:彈出一個節點,訪問,若左子節點或右子節點不爲空,將其壓入隊列。
- void LevelTraverse(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return;
- queue<BinaryTreeNode *> q;
- q.push(pRoot);
- while(!q.empty())
- {
- BinaryTreeNode * pNode = q.front();
- q.pop();
- Visit(pNode); // 訪問節點
- if(pNode->m_pLeft != NULL)
- q.push(pNode->m_pLeft);
- if(pNode->m_pRight != NULL)
- q.push(pNode->m_pRight);
- }
- return;
- }
6. 求二叉樹第K層的節點個數
遞歸解法:
(1)如果二叉樹爲空或者k<1返回0
(2)如果二叉樹不爲空並且k==1,返回1
(3)如果二叉樹不爲空且k>1,返回左子樹中k-1層的節點個數與右子樹k-1層節點個數之和
參考代碼如下:
- int GetNodeNumKthLevel(BinaryTreeNode * pRoot, int k)
- {
- if(pRoot == NULL || k < 1)
- return 0;
- if(k == 1)
- return 1;
- int numLeft = GetNodeNumKthLevel(pRoot->m_pLeft, k-1); // 左子樹中k-1層的節點個數
- int numRight = GetNodeNumKthLevel(pRoot->m_pRight, k-1); // 右子樹中k-1層的節點個數
- return (numLeft + numRight);
- }
遞歸解法:
(1)如果二叉樹爲空,返回0
(2)如果二叉樹不爲空且左右子樹爲空,返回1
(3)如果二叉樹不爲空,且左右子樹不同時爲空,返回左子樹中葉子節點個數加上右子樹中葉子節點個數
參考代碼如下:
- int GetLeafNodeNum(BinaryTreeNode * pRoot)
- {
- if(pRoot == NULL)
- return 0;
- if(pRoot->m_pLeft == NULL && pRoot->m_pRight == NULL)
- return 1;
- int numLeft = GetLeafNodeNum(pRoot->m_pLeft); // 左子樹中葉節點的個數
- int numRight = GetLeafNodeNum(pRoot->m_pRight); // 右子樹中葉節點的個數
- return (numLeft + numRight);
- }
9. 判斷二叉樹是不是平衡二叉樹
遞歸解法:
(1)如果二叉樹爲空,返回真
(2)如果二叉樹不爲空,如果左子樹和右子樹都是AVL樹並且左子樹和右子樹高度相差不大於1,返回真,其他返回假
參考代碼:
- bool IsAVL(BinaryTreeNode * pRoot, int & height)
- {
- if(pRoot == NULL) // 空樹,返回真
- {
- height = 0;
- return true;
- }
- int heightLeft;
- bool resultLeft = IsAVL(pRoot->m_pLeft, heightLeft);
- int heightRight;
- bool resultRight = IsAVL(pRoot->m_pRight, heightRight);
- if(resultLeft && resultRight && abs(heightLeft - heightRight) <= 1) // 左子樹和右子樹都是AVL,並且高度相差不大於1,返回真
- {
- height = max(heightLeft, heightRight) + 1;
- return true;
- }
- else
- {
- height = max(heightLeft, heightRight) + 1;
- return false;
- }
- }
13. 由前序遍歷序列和中序遍歷序列重建二叉樹
二叉樹前序遍歷序列中,第一個元素總是樹的根節點的值。中序遍歷序列中,左子樹的節點的值位於根節點的值的左邊,右子樹的節點的值位
於根節點的值的右邊。
遞歸解法:
(1)如果前序遍歷爲空或中序遍歷爲空或節點個數小於等於0,返回NULL。
(2)創建根節點。前序遍歷的第一個數據就是根節點的數據,在中序遍歷中找到根節點的位置,可分別得知左子樹和右子樹的前序和中序遍
歷序列,重建左右子樹。
- BinaryTreeNode * RebuildBinaryTree(int* pPreOrder, int* pInOrder, int nodeNum)
- {
- if(pPreOrder == NULL || pInOrder == NULL || nodeNum <= 0)
- return NULL;
- BinaryTreeNode * pRoot = new BinaryTreeNode;
- // 前序遍歷的第一個數據就是根節點數據
- pRoot->m_nValue = pPreOrder[0];
- pRoot->m_pLeft = NULL;
- pRoot->m_pRight = NULL;
- // 查找根節點在中序遍歷中的位置,中序遍歷中,根節點左邊爲左子樹,右邊爲右子樹
- int rootPositionInOrder = -1;
- for(int i = 0; i < nodeNum; i++)
- if(pInOrder[i] == pRoot->m_nValue)
- {
- rootPositionInOrder = i;
- break;
- }
- if(rootPositionInOrder == -1)
- {
- throw std::exception("Invalid input.");
- }
- // 重建左子樹
- int nodeNumLeft = rootPositionInOrder;
- int * pPreOrderLeft = pPreOrder + 1;
- int * pInOrderLeft = pInOrder;
- pRoot->m_pLeft = RebuildBinaryTree(pPreOrderLeft, pInOrderLeft, nodeNumLeft);
- // 重建右子樹
- int nodeNumRight = nodeNum - nodeNumLeft - 1;
- int * pPreOrderRight = pPreOrder + 1 + nodeNumLeft;
- int * pInOrderRight = pInOrder + nodeNumLeft + 1;
- pRoot->m_pRight = RebuildBinaryTree(pPreOrderRight, pInOrderRight, nodeNumRight);
- return pRoot;
- }