常見的二叉樹面試題
阿新 • • 發佈:2019-02-08
面試中,最常見是資料結構就是二叉樹和連結串列了,其中和二叉樹有關的常見面試題主要是:樹的前序遍歷、中序遍歷、後序遍歷、分層遍歷、樹的節點數、樹的葉子節點數、樹的第K層節點數、樹的深度、樹的寬度、平衡二叉樹的判定、完全二叉樹的判定、滿二叉樹的判定、由前序中序反推後序遍歷、由前序中序重建二叉樹,處理這些問題基本思想無外乎是“遍歷+遞迴”。
關於樹的遍歷,遞迴方式太傻太天真,但也是最基本的思想,實現程式碼網上資料可謂是汗牛充棟,在此就不贅述了。
下面僅列舉了非遞迴實現樹的前序、中序以及後序遍歷,由於筆者對程式碼有潔癖,最不能忍受一個while迴圈裡再巢狀另外一個while迴圈(網上千篇一律的實現程式碼方式),雖然二者的思想都是一致的,但筆者的實現方式更為直觀易懂,故此處的版本都只借助一個棧、一個while迴圈:
前序非遞迴遍歷:
void PreOrderTraseNonRecursion(BTree* t)
{
stack<BNode*> s;
s.push(t);
while(!s.empty())
{
BNode* p = s.top();
s.pop();
Visit(p);
if(p->rchild)
s.push(p->rchild);
if(p->lchild)
s.push(p->lchild);
}
}
中序非遞迴遍歷:
前序和中序的非遞迴遍歷都很好理解,我就不再浪費口舌筆墨了,要是真看不懂,請回去把嚴蔚敏那本《資料結構》再啃一遍、兩遍、三遍……要是還看不懂,我只能懷著無比沉重的心情告訴你一個非常不幸的訊息:“對不起,你不適合這個行業!”void InOrderTraseNonRecursion(BTree* t) { stack<BNode*> s; BNode* p=t; while(p||!s.empty()) { if(p) { s.push(p); p=p->lchild; } else { p = s.top(); s.pop(); Visit(p); p=p->rchild; } } }
後序非遞迴遍歷:後序非遞迴遍歷稍微比較難理解些,但是你如果對前序和中序遍歷思想已經融會貫通了,其實也就那樣:
從根節點開始,只要當前節點存在,或者棧不為空,則重複下面操作:
(1)從當前節點開始,進棧並走左子樹,直到左子樹為空。
(2)如果棧頂節點的右子樹為空,或者棧頂節點的右孩子為剛訪問過的節點,
則退棧並訪問,然後置當前節點指標為空。
(3)否則走右子樹。
還有一種網上流傳的後序遍歷的方式是藉助於兩個棧,一個棧用來遍歷,另一個棧儲存遍歷到的結點,最後一起出棧,就是後序遍歷結果。void PostOrderTraseNonRecursion(BTree* t) { stack<BNode*> s; BNode* p=t; BNode* q=NULL; while(p||!s.empty()) { if(p) { s.push(p); p=p->lchild; } else { p = s.top(); if(p->rchild==NULL || p->rchild==q) { Visit(p); s.pop(); q = p; p = NULL; } else { p=p->rchild; } } } }
void PostOrderTraseNonRecursion2(BTree* t)
{
stack<BNode*> sTraverse,sVisit;
BNode* p=NULL;
if(t==NULL)
return;
sTraverse.push(t);
while(!sTraverse.empty())
{
p=sTraverse.top();
sTraverse.pop();
sVisit.push(p);
if(p->lchild)
sTraverse.push(p->lchild);
if(p->rchild)
sTraverse.push(p->rchild);
}
while(!sVisit.empty())
{
p=sVisit.top();
sVisit.pop();
Visit(p);
}
}
分層遍歷:通過分層遍歷可實現統計樹的深度、每一層節點數、樹的最大寬度等要求。基本解法想必你也知道了,就是藉助一個佇列,進行入隊、出隊操作,如下面的二叉樹,要是一口氣全部列印所有節點,即輸出:
1 2 3 4 5 6 7。
再進一步考察,如何實現將二叉樹按層列印,即每一行列印二叉樹的每一層樹節點,輸出為:
1
2 3
4 5 6
7
1/ \
2 3
/ \ \
4 5 6
/ 7
這就需要再基本的分層遍歷基礎上再做些手腳了,下面是一個簡單的例子:
//分層列印節點,每一行為一層,返回該二叉樹的層數
int DepthTrase(BTree* t)
{
if(t==NULL)
return 0;
vector<BNode*> v;
v.push_back(t);
int front=0;//該層的開始節點下標
int rear=0;//該層結束節點下標
int last=1;//所有已遍歷的節點個數
int level=0;//層次數
while(rear < v.size())
{
BNode* p = v[rear++];
if(p->lchild)
v.push_back(p->lchild);
if(p->rchild)
v.push_back(p->rchild);
if(rear>=last)
{
//列印每一層節點
for(int i=front;i<rear;++i)
{
Visit(v[i]);
}
cout<<endl;
//重置 為下一層遍歷準備
front=rear;
last=v.size();
level++;
}
}
return level;
}
下面再介紹幾個二叉樹中的高階應用: 1、根節點到r節點的路徑: 後序遍歷時訪問到r節點時,棧中的所有的節點均為r節點的祖先,這些祖先構成根節點到r節點的路徑。 由此引出下面的第2個問題…… 2、求二叉樹中兩個節點的最低公共祖先節點:
遞迴解法效率很低,有很多重複的遍歷,不再贅述。
非遞迴解法:先求從根節點到兩個節點的路徑,然後再比較對應路徑的節點,最後一個相同的節點也就是他們在二叉樹中的最低公共祖先節點,問題轉換為求兩個單鏈表的最後一個公共節點。 3、二叉樹節點最大距離
姑且定義"距離"為兩節點之間邊的個數。求二叉樹中相距最遠的兩個節點之間的距離,如下圖: