一文搞定面試中的二叉樹問題
版權所有,轉載請注明出處,謝謝!
http://blog.csdn.net/walkinginthewind/article/details/7518888
樹是一種比較重要的數據結構,尤其是二叉樹。二叉樹是一種特殊的樹,在二叉樹中每個節點最多有兩個子節點,一般稱為左子節點和右子節點(或左孩子和右孩子),并且二叉樹的子樹有左右之分,其次序不能任意顛倒。二叉樹是遞歸定義的,因此,與二叉樹有關的題目基本都可以用遞歸思想解決,當然有些題目非遞歸解法也應該掌握,如非遞歸遍歷節點等等。本文努力對二叉樹相關題目做一個較全的整理總結,希望對找工作的同學有所幫助。
二叉樹節點定義如下:
struct BinaryTreeNode
{
? ? int m_nValue;
? ? BinaryTreeNode* m_pLeft;
? ? BinaryTreeNode* m_pRight;
};
相關鏈接:
輕松搞定面試中的鏈表題目
題目列表:
1. 求二叉樹中的節點個數
2. 求二叉樹的深度
3. 前序遍歷,中序遍歷,后序遍歷
4.分層遍歷二叉樹(按層次從上往下,從左往右)
5. 將二叉查找樹變為有序的雙向鏈表
6. 求二叉樹第K層的節點個數
7. 求二叉樹中葉子節點的個數
8. 判斷兩棵二叉樹是否結構相同
9. 判斷二叉樹是不是平衡二叉樹
10. 求二叉樹的鏡像
11. 求二叉樹中兩個節點的最低公共祖先節點
12. 求二叉樹中節點的最大距離
13. 由前序遍歷序列和中序遍歷序列重建二叉樹
14.判斷二叉樹是不是完全二叉樹
詳細解答
1. 求二叉樹中的節點個數
遞歸解法:
(1)如果二叉樹為空,節點個數為0
(2)如果二叉樹不為空,二叉樹節點個數 = 左子樹節點個數 + 右子樹節點個數 + 1
參考代碼如下:
[cpp]?view plaincopy
int?GetNodeNum(BinaryTreeNode?*?pRoot)?? {?? ????if(pRoot?==?NULL)??? ????????return?0;?? ????return?GetNodeNum(pRoot->m_pLeft)?+?GetNodeNum(pRoot->m_pRight)?+?1;?? }??
2. 求二叉樹的深度
遞歸解法:
(1)如果二叉樹為空,二叉樹的深度為0
(2)如果二叉樹不為空,二叉樹的深度 = max(左子樹深度, 右子樹深度) + 1
參考代碼如下:
[cpp]?view plaincopy
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);??? }??
3. 前序遍歷,中序遍歷,后序遍歷
前序遍歷遞歸解法:
(1)如果二叉樹為空,空操作
(2)如果二叉樹不為空,訪問根節點,前序遍歷左子樹,前序遍歷右子樹
參考代碼如下:
[cpp]?view plaincopy
void?PreOrderTraverse(BinaryTreeNode?*?pRoot)?? {?? ????if(pRoot?==?NULL)?? ????????return;?? ????Visit(pRoot);??? ????PreOrderTraverse(pRoot->m_pLeft);??? ????PreOrderTraverse(pRoot->m_pRight);??? }??
中序遍歷遞歸解法
(1)如果二叉樹為空,空操作。
(2)如果二叉樹不為空,中序遍歷左子樹,訪問根節點,中序遍歷右子樹
參考代碼如下:
[cpp]?view plaincopy
void?InOrderTraverse(BinaryTreeNode?*?pRoot)?? {?? ????if(pRoot?==?NULL)?? ????????return;?? ????InOrderTraverse(pRoot->m_pLeft);??? ????Visit(pRoot);??? ????InOrderTraverse(pRoot->m_pRight);??? }??
后序遍歷遞歸解法
(1)如果二叉樹為空,空操作
(2)如果二叉樹不為空,后序遍歷左子樹,后序遍歷右子樹,訪問根節點
參考代碼如下:
[cpp]?view plaincopy
void?PostOrderTraverse(BinaryTreeNode?*?pRoot)?? {?? ????if(pRoot?==?NULL)?? ????????return;?? ????PostOrderTraverse(pRoot->m_pLeft);??? ????PostOrderTraverse(pRoot->m_pRight);??? ????Visit(pRoot);??? }??
4.分層遍歷二叉樹(按層次從上往下,從左往右)
相當于廣度優先搜索,使用隊列實現。隊列初始化,將根節點壓入隊列。當隊列不為空,進行如下操作:彈出一個節點,訪問,若左子節點或右子節點不為空,將其壓入隊列。
[cpp]?view plaincopy
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;?? }??
5. 將二叉查找樹變為有序的雙向鏈表
要求不能創建新節點,只調整指針。
遞歸解法:
(1)如果二叉樹查找樹為空,不需要轉換,對應雙向鏈表的第一個節點是NULL,最后一個節點是NULL
(2)如果二叉查找樹不為空:
如果左子樹為空,對應雙向有序鏈表的第一個節點是根節點,左邊不需要其他操作;
如果左子樹不為空,轉換左子樹,二叉查找樹對應雙向有序鏈表的第一個節點就是左子樹轉換后雙向有序鏈表的第一個節點,同時將根節點和左子樹轉換后的雙向有序鏈表的最后一個節點連接;
如果右子樹為空,對應雙向有序鏈表的最后一個節點是根節點,右邊不需要其他操作;
如果右子樹不為空,對應雙向有序鏈表的最后一個節點就是右子樹轉換后雙向有序鏈表的最后一個節點,同時將根節點和右子樹轉換后的雙向有序鏈表的第一個節點連接。
參考代碼如下:
[cpp]?view plaincopy
? ? ? ? ? ?? void?Convert(BinaryTreeNode?*?pRoot,??? ?????????????BinaryTreeNode?*?&?pFirstNode,?BinaryTreeNode?*?&?pLastNode)?? {?? ????BinaryTreeNode?*pFirstLeft,?*pLastLeft,?*?pFirstRight,?*pLastRight;?? ????if(pRoot?==?NULL)??? ????{?? ????????pFirstNode?=?NULL;?? ????????pLastNode?=?NULL;?? ????????return;?? ????}?? ?? ????if(pRoot->m_pLeft?==?NULL)?? ????{?? ?????????? ????????pFirstNode?=?pRoot;?? ????}?? ????else?? ????{?? ????????Convert(pRoot->m_pLeft,?pFirstLeft,?pLastLeft);?? ?????????? ????????pFirstNode?=?pFirstLeft;?? ?????????? ????????pRoot->m_pLeft?=?pLastLeft;?? ????????pLastLeft->m_pRight?=?pRoot;?? ????}?? ?? ????if(pRoot->m_pRight?==?NULL)?? ????{?? ?????????? ????????pLastNode?=?pRoot;?? ????}?? ????else?? ????{?? ????????Convert(pRoot->m_pRight,?pFirstRight,?pLastRight);?? ?????????? ????????pLastNode?=?pLastRight;?? ?????????? ????????pRoot->m_pRight?=?pFirstRight;?? ????????pFirstRight->m_pLeft?=?pRoot;?? ????}?? ?? ????return;?? }??
6. 求二叉樹第K層的節點個數
遞歸解法:
(1)如果二叉樹為空或者k<1返回0
(2)如果二叉樹不為空并且k==1,返回1
(3)如果二叉樹不為空且k>1,返回左子樹中k-1層的節點個數與右子樹k-1層節點個數之和
參考代碼如下:
[cpp]?view plaincopy
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);??? ????int?numRight?=?GetNodeNumKthLevel(pRoot->m_pRight,?k-1);??? ????return?(numLeft?+?numRight);?? }??
7. 求二叉樹中葉子節點的個數
完全二叉樹有2*n-1 個節點,則它的葉子節點數為?
完全二叉樹的節點數是奇數,說明此完全二叉樹也是滿二叉樹,也就是說每個內部節點正好都有2個葉結點.
設內部節點數為a,葉節點數為b,結點總數為m,明顯有a+b=m (1)
非空滿二叉樹中所有節點的出度正好等于入度,每個內部節點出度為2,葉節點出度為0,所有節點的出度和為2a;根節點入度為0,其他節點的入度為1,所有節點的入度和為a+b-1;因此有2a=a+b-1 (2)
由(1),(2)得 b=(m+1)/2,a=(m-1)/2,b=a+1
也就是說,非空滿二叉樹的葉節點數正好比內部節點數多1
此完全二叉樹的結點總數為2n-1,因此其葉結點數為n.
遞歸解法:
(1)如果二叉樹為空,返回0
(2)如果二叉樹不為空且左右子樹為空,返回1
(3)如果二叉樹不為空,且左右子樹不同時為空,返回左子樹中葉子節點個數加上右子樹中葉子節點個數
參考代碼如下:
[cpp]?view plaincopy
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);?? } ?
8. 判斷兩棵二叉樹是否結構相同
不考慮數據內容。結構相同意味著對應的左子樹和對應的右子樹都結構相同。
遞歸解法:
(1)如果兩棵二叉樹都為空,返回真
(2)如果兩棵二叉樹一棵為空,另一棵不為空,返回假
(3)如果兩棵二叉樹都不為空,如果對應的左子樹和右子樹都同構返回真,其他返回假
參考代碼如下:
[cpp]?view plaincopy
bool?StructureCmp(BinaryTreeNode?*?pRoot1,?BinaryTreeNode?*?pRoot2)?? {?? ????if(pRoot1?==?NULL?&&?pRoot2?==?NULL)??? ????????return?true;?? ????else?if(pRoot1?==?NULL?||?pRoot2?==?NULL)??? ????????return?false;?? ????bool?resultLeft?=?StructureCmp(pRoot1->m_pLeft,?pRoot2->m_pLeft);??? ????bool?resultRight?=?StructureCmp(pRoot1->m_pRight,?pRoot2->m_pRight);??? ????return?(resultLeft?&&?resultRight);?? }???
9. 判斷二叉樹是不是平衡二叉樹
遞歸解法:
(1)如果二叉樹為空,返回真
(2)如果二叉樹不為空,如果左子樹和右子樹都是AVL樹并且左子樹和右子樹高度相差不大于1,返回真,其他返回假
參考代碼:
[cpp]?view plaincopy
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)??? ????{?? ????????height?=?max(heightLeft,?heightRight)?+?1;?? ????????return?true;?? ????}?? ????else?? ????{?? ????????height?=?max(heightLeft,?heightRight)?+?1;?? ????????return?false;?? ????}?? }??
10. 求二叉樹的鏡像
遞歸解法:
(1)如果二叉樹為空,返回空
(2)如果二叉樹不為空,求左子樹和右子樹的鏡像,然后交換左子樹和右子樹
參考代碼如下:
[cpp]?view plaincopy
BinaryTreeNode?*?Mirror(BinaryTreeNode?*?pRoot)?? {?? ????if(pRoot?==?NULL)??? ????????return?NULL;?? ????BinaryTreeNode?*?pLeft?=?Mirror(pRoot->m_pLeft);??? ????BinaryTreeNode?*?pRight?=?Mirror(pRoot->m_pRight);??? ?????????? ????pRoot->m_pLeft?=?pRight;?? ????pRoot->m_pRight?=?pLeft;?? ????return?pRoot;?? }??
11. 求二叉樹中兩個節點的最低公共祖先節點
遞歸解法:
(1)如果兩個節點分別在根節點的左子樹和右子樹,則返回根節點
(2)如果兩個節點都在左子樹,則遞歸處理左子樹;如果兩個節點都在右子樹,則遞歸處理右子樹
參考代碼如下:
[cpp]?view plaincopy
bool?FindNode(BinaryTreeNode?*?pRoot,?BinaryTreeNode?*?pNode)?? {?? ????if(pRoot?==?NULL?||?pNode?==?NULL)?? ????????return?false;?? ?? ????if(pRoot?==?pNode)?? ????????return?true;?? ?? ????bool?found?=?FindNode(pRoot->m_pLeft,?pNode);?? ????if(!found)?? ????????found?=?FindNode(pRoot->m_pRight,?pNode);?? ?? ????return?found;?? }?? ?? BinaryTreeNode?*?GetLastCommonParent(BinaryTreeNode?*?pRoot,??? ?????????????????????????????????????BinaryTreeNode?*?pNode1,??? ?????????????????????????????????????BinaryTreeNode?*?pNode2)?? {?? ????if(FindNode(pRoot->m_pLeft,?pNode1))?? ????{?? ????????if(FindNode(pRoot->m_pRight,?pNode2))?? ????????????return?pRoot;?? ????????else?? ????????????return?GetLastCommonParent(pRoot->m_pLeft,?pNode1,?pNode2);?? ????}?? ????else?? ????{?? ????????if(FindNode(pRoot->m_pLeft,?pNode2))?? ????????????return?pRoot;?? ????????else?? ????????????return?GetLastCommonParent(pRoot->m_pRight,?pNode1,?pNode2);?? ????}?? }??
遞歸解法效率很低,有很多重復的遍歷,下面看一下非遞歸解法。
非遞歸解法:
先求從根節點到兩個節點的路徑,然后再比較對應路徑的節點就行,最后一個相同的節點也就是他們在二叉樹中的最低公共祖先節點
參考代碼如下:
[cpp]?view plaincopy
bool?GetNodePath(BinaryTreeNode?*?pRoot,?BinaryTreeNode?*?pNode,??? ?????????????????list<BinaryTreeNode?*>?&?path)?? {?? ????if(pRoot?==?pNode)?? ????{????? ????????path.push_back(pRoot);?? ????????return?true;?? ????}?? ????if(pRoot?==?NULL)?? ????????return?false;?? ????path.push_back(pRoot);?? ????bool?found?=?false;?? ????found?=?GetNodePath(pRoot->m_pLeft,?pNode,?path);?? ????if(!found)?? ????????found?=?GetNodePath(pRoot->m_pRight,?pNode,?path);?? ????if(!found)?? ????????path.pop_back();?? ????return?found;?? }?? BinaryTreeNode?*?GetLastCommonParent(BinaryTreeNode?*?pRoot,?BinaryTreeNode?*?pNode1,?BinaryTreeNode?*?pNode2)?? {?? ????if(pRoot?==?NULL?||?pNode1?==?NULL?||?pNode2?==?NULL)?? ????????return?NULL;?? ????list<BinaryTreeNode*>?path1;?? ????bool?bResult1?=?GetNodePath(pRoot,?pNode1,?path1);?? ????list<BinaryTreeNode*>?path2;?? ????bool?bResult2?=?GetNodePath(pRoot,?pNode2,?path2);?? ????if(!bResult1?||?!bResult2)??? ????????return?NULL;?? ????BinaryTreeNode?*?pLast?=?NULL;?? ????list<BinaryTreeNode*>::const_iterator?iter1?=?path1.begin();?? ????list<BinaryTreeNode*>::const_iterator?iter2?=?path2.begin();?? ????while(iter1?!=?path1.end()?&&?iter2?!=?path2.end())?? ????{?? ????????if(*iter1?==?*iter2)?? ????????????pLast?=?*iter1;?? ????????else?? ????????????break;?? ????????iter1++;?? ????????iter2++;?? ????}?? ????return?pLast;?? }??
在上述算法的基礎上稍加變化即可求二叉樹中任意兩個節點的距離了。
12. 求二叉樹中節點的最大距離
即二叉樹中相距最遠的兩個節點之間的距離。
遞歸解法:
(1)如果二叉樹為空,返回0,同時記錄左子樹和右子樹的深度,都為0
(2)如果二叉樹不為空,最大距離要么是左子樹中的最大距離,要么是右子樹中的最大距離,要么是左子樹節點中到根節點的最大距離+右子樹節點中到根節點的最大距離,同時記錄左子樹和右子樹節點中到根節點的最大距離。
參考代碼如下:
[cpp]?view plaincopy
int?GetMaxDistance(BinaryTreeNode?*?pRoot,?int?&?maxLeft,?int?&?maxRight)?? {?? ?????? ?????? ????if(pRoot?==?NULL)?? ????{?? ????????maxLeft?=?0;?? ????????maxRight?=?0;?? ????????return?0;?? ????}?? ????int?maxLL,?maxLR,?maxRL,?maxRR;?? ????int?maxDistLeft,?maxDistRight;?? ????if(pRoot->m_pLeft?!=?NULL)?? ????{?? ????????maxDistLeft?=?GetMaxDistance(pRoot->m_pLeft,?maxLL,?maxLR);?? ????????maxLeft?=?max(maxLL,?maxLR)?+?1;?? ????}?? ????else?? ????{?? ????????maxDistLeft?=?0;?? ????????maxLeft?=?0;?? ????}?? ????if(pRoot->m_pRight?!=?NULL)?? ????{?? ????????maxDistRight?=?GetMaxDistance(pRoot->m_pRight,?maxRL,?maxRR);?? ????????maxRight?=?max(maxRL,?maxRR)?+?1;?? ????}?? ????else?? ????{?? ????????maxDistRight?=?0;?? ????????maxRight?=?0;?? ????}?? ????return?max(max(maxDistLeft,?maxDistRight),?maxLeft+maxRight);?? }??
13. 由前序遍歷序列和中序遍歷序列重建二叉樹
二叉樹前序遍歷序列中,第一個元素總是樹的根節點的值。中序遍歷序列中,左子樹的節點的值位于根節點的值的左邊,右子樹的節點的值位
于根節點的值的右邊。
遞歸解法:
(1)如果前序遍歷為空或中序遍歷為空或節點個數小于等于0,返回NULL。
(2)創建根節點。
前序遍歷的第一個數據就是根節點的數據,在中序遍歷中找到根節點的位置,可分別得知左子樹和右子樹的前序和中序遍
歷序列,重建左右子樹。
題目描述: 輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹并輸出它的后序遍歷序列。
輸入: 輸入可能包含多個測試樣例,對于每個測試案例,
輸入的第一行為一個整數n(1<=n<=1000):代表二叉樹的節點個數。
輸入的第二行包括n個整數(其中每個元素a的范圍為(1<=a<=1000)):代表二叉樹的前序遍歷序列。
輸入的第三行包括n個整數(其中每個元素a的范圍為(1<=a<=1000)):代表二叉樹的中序遍歷序列。
輸出: 對應每個測試案例,輸出一行:
如果題目中所給的前序和中序遍歷序列能構成一棵二叉樹,則輸出n個整數,代表二叉樹的后序遍歷序列,每個元素后面都有空格。
如果題目中所給的前序和中序遍歷序列不能構成一棵二叉樹,則輸出”No”。
樣例輸入: 81 2 4 7 3 5 6 84 7 2 1 5 3 8 681 2 4 7 3 5 6 84 1 2 7 5 3 8 6 樣例輸出: 7 4 2 5 8 6 3 1 No
? ? 采用遞歸的方式重構二叉樹,關鍵是要考慮到一些特殊情況,比如:只有根節點的二叉樹、只有左子樹或只有右子樹的二叉樹以及二叉樹根節點為NULL、前序中序序列不匹配導致不能重構二叉樹等。
? ? AC代碼如下(一直在如何實現判斷能否重構二叉樹的地方徘徊,在九度論壇里大致看了下,借鑒了下各位前輩的思路:定義一個全局bool變量,用來跟蹤判斷能夠重構):
[cpp]?view plaincopy
#include<stdio.h>?? #include<stdlib.h>?? ?? typedef?int?ElemType;?? typedef?struct?BTNode??? {?? ????ElemType?data;?? ????struct?BTNode?*left;?? ????struct?BTNode?*right;?? }BTNode,*BTree;?? ?? bool?CanReBuild;?????? ?? ? ? ?? void?RebuildBinaryTree(BTree?*ppTree,int?*pre,int?*inv,int?len)?? {?? ????if(p
總結
以上是生活随笔為你收集整理的一文搞定面试中的二叉树问题的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。