二叉樹中的那些常見的面試題


關於二叉樹


二叉樹作為樹的一種,是一種重要的數據結構,也是面試官經常考的東西。昨天看了一下關於樹中的面試題,發現二叉樹中的面試題比較常見的題型大概有下面幾個:創建一顆二叉樹(先序,中序,后序)、遍歷一顆二叉樹(先序,中序,后序和層次遍歷)、求二叉樹中葉子節點的個數、求二叉樹的高度、求二叉樹中兩個節點的最近公共祖先、打印和為某一值的全部路徑、求某一節點是否在一個樹中等等。

再詳細的說這些面試題之前,不妨先看一下幾種常見的二叉樹:

完全二叉樹:若二叉樹的高度是h,除第h層之外,其他(1~h-1)層的節點數都達到了最大個數,並且第h層的節點都連續的集中在最左邊。想到點什么沒?實際上,完全二叉樹和堆聯系比較緊密哈~~~

滿二叉樹:除最后一層外,每一層上的所有節點都有兩個子節點,最后一層都是葉子節點。

哈夫曼樹:又稱為最有數,這是一種帶權路徑長度最短的樹。哈夫曼編碼就是哈夫曼樹的應用。

平衡二叉樹:所謂平衡二叉樹指的是,左右兩個子樹的高度差的絕對值不超過 1。

紅黑樹:紅黑樹是每個節點都帶顏色的樹,節點顏色或是紅色或是黑色,紅黑樹是一種查找樹。紅黑樹有一個重要的性質,從根節點到葉子節點的最長的路徑不多於最短的路徑的長度的兩倍。對於紅黑樹,插入,刪除,查找的復雜度都是O(log N)。

 

二叉樹中的那些面試題


再具體說二叉樹中的那些面試題之前,我們先看一下二叉樹中的每個節點是什么樣的,以及為了完成這些面試題,二叉樹中聲明的函數原型是什么樣的:

二叉樹的節點:BinTreeNode

View Code
 1 //二叉樹的節點類
 2 class BinTreeNode
 3 {
 4 private:
 5     int data;
 6     BinTreeNode *left,*right;
 7 public:
 8     //利用初始化列表完成data,left,rightn的初始化
 9     BinTreeNode(const int &item,BinTreeNode *lPtr = NULL,BinTreeNode *rPtr = NULL):data(item) ,left(lPtr),right(rPtr){};
10     void set_data(int item)
11     {
12         data = item;
13     }
14     int get_data()const
15     {
16         return data;
17     }
18     void set_left(BinTreeNode *l)
19     {
20         left = l;
21     }
22     BinTreeNode* get_left()const
23     {
24         return left;
25     }
26     void set_right(BinTreeNode *r)
27     {
28         right = r;
29     }
30     BinTreeNode* get_right()const
31     {
32         return right;
33     }
34 };

二叉樹原型:BinTree,其中包含了這篇文章中要完成的函數原型的完整聲明。 

View Code
 1 //二叉樹
 2 class BinTree
 3 {
 4 private:
 5     BinTreeNode *root;
 6 public:
 7     BinTree(BinTreeNode *t = NULL):root(t){};
 8     ~BinTree(){delete root;};
 9     void set_root(BinTreeNode *t)
10     {
11         root = t;
12     }
13     BinTreeNode* get_root()const
14     {
15         return root;
16     }
17     //1.創建二叉樹
18     BinTreeNode* create_tree();
19     //2.前序遍歷
20     void pre_order(BinTreeNode *r)const;
21     //3.中序遍歷
22     void in_order(BinTreeNode *r)const;
23     //4.后序遍歷
24     void post_order(BinTreeNode *r)const;
25     //5.層次遍歷
26     void level_order(BinTreeNode *r)const;
27     //6.獲得葉子節點的個數
28     int get_leaf_num(BinTreeNode *r)const;
29     //7.獲得二叉樹的高度
30     int get_tree_height(BinTreeNode *r)const;
31     //8.交換二叉樹的左右兒子
32     void swap_left_right(BinTreeNode *r);
33     //9.求兩個節點pNode1和pNode2在以r為樹根的樹中的最近公共祖先
34     BinTreeNode* get_nearest_common_father(BinTreeNode *r,BinTreeNode *pNode1,BinTreeNode *pNode2)const;
35     //10.打印和為某一值的所有路徑
36     void print_rout(BinTreeNode *r,int sum)const;
37     //11.判斷一個節點t是否在以r為根的子樹中
38     bool is_in_tree(BinTreeNode *r,BinTreeNode *t)const;
39 };

2.1 創建一顆二叉樹

創建一顆二叉樹,可以創建先序二叉樹,中序二叉樹,后序二叉樹。我們在創建的時候為了方便,不妨用‘#’表示空節點,這時如果先序序列是:6 4 2 3 # # # # 5 1 # # 7 # #,那么創建的二叉樹如下:

下面是創建二叉樹的完整代碼:穿件一顆二叉樹,返回二叉樹的根。

View Code
 1 //創建二叉樹,這里不妨使用前序創建二叉樹,遇到‘#’表示節點為空
 2 BinTreeNode* BinTree::create_tree()
 3 {
 4     char item;
 5     BinTreeNode *t,*t_l,*t_r;
 6     cin>>item;
 7     if(item != '#')
 8     {
 9         BinTreeNode *pTmpNode = new BinTreeNode(item-48);
10         t = pTmpNode;
11         t_l = create_tree();
12         t->set_left(t_l);
13         t_r = create_tree();
14         t->set_right(t_r);
15         return t;
16     }
17     else
18     {
19         t = NULL;
20         return t;
21     }
22 }

2.2 二叉樹的遍歷

二叉樹的遍歷分為:先序遍歷,中序遍歷和后序遍歷,這三種遍歷的寫法是很相似的,利用遞歸程序完成也是灰常簡單的:

View Code
 1 //前序遍歷
 2 void BinTree::pre_order(BinTreeNode *r)const
 3 {
 4     BinTreeNode *pTmpNode = r;
 5     if(pTmpNode != NULL)
 6     {
 7         cout<<pTmpNode->get_data()<<" ";
 8         pre_order(pTmpNode->get_left());
 9         pre_order(pTmpNode->get_right());
10     }
11 }
12 //中序遍歷
13 void BinTree::in_order(BinTreeNode *r)const
14 {
15     BinTreeNode *pTmpNode = r;
16     if(pTmpNode != NULL)
17     {
18         in_order(pTmpNode->get_left());
19         cout<<pTmpNode->get_data()<<" ";
20         in_order(pTmpNode->get_right());
21     }
22 }
23 //后序遍歷
24 void BinTree::post_order(BinTreeNode *r)const
25 {
26     BinTreeNode *pTmpNode = r;
27     if(pTmpNode != NULL)
28     {
29         post_order(pTmpNode->get_left());
30         post_order(pTmpNode->get_right());
31         cout<<pTmpNode->get_data()<<" ";
32     }
33 }

2.3 層次遍歷

層次遍歷也是二叉樹遍歷的一種方式,二叉樹的層次遍歷更像是一種廣度優先搜索(BFS)。因此二叉樹的層次遍歷利用隊列來完成是最好不過啦,當然不是說利用別的數據結構不能完成。

View Code
 1 //層次遍歷
 2 void BinTree::level_order(BinTreeNode *r)const
 3 {
 4     if(r == NULL)
 5         return;
 6     deque<BinTreeNode*> q;
 7     q.push_back(r);
 8     while(!q.empty())
 9     {
10         BinTreeNode *pTmpNode = q.front();
11         cout<<pTmpNode->get_data()<<" ";
12         q.pop_front();
13         if(pTmpNode->get_left() != NULL)
14         {
15             q.push_back(pTmpNode->get_left());
16         }
17         if(pTmpNode->get_right() != NULL)
18         {
19             q.push_back(pTmpNode->get_right());
20         }
21     }
22 }

2.4 求二叉樹中葉子節點的個數

樹中的葉子節點的個數 = 左子樹中葉子節點的個數 + 右子樹中葉子節點的個數。利用遞歸代碼也是相當的簡單,易懂。 

View Code
 1 //獲取葉子節點的個數
 2 int BinTree::get_leaf_num(BinTreeNode *r)const
 3 {
 4     if(r == NULL)//該節點是空節點,比如建樹時候用'#'表示
 5     {
 6         return 0;
 7     }
 8     if(r->get_left()==NULL && r->get_right()==NULL)//該節點並不是空的,但是沒有孩子節點
 9     {
10         return 1;
11     }
12     //遞歸整個樹的葉子節點個數 = 左子樹葉子節點的個數 + 右子樹葉子節點的個數
13     return get_leaf_num(r->get_left()) + get_leaf_num(r->get_right());
14 }

2.5 求二叉樹的高度

求二叉樹的高度也是非常簡單,不用多說:樹的高度 = max(左子樹的高度,右子樹的高度) + 1 。

View Code
 1 //獲得二叉樹的高度
 2 int BinTree::get_tree_height(BinTreeNode *r)const
 3 {
 4     if(r == NULL)//節點本身為空
 5     {
 6         return 0;
 7     }
 8     if(r->get_left()==NULL && r->get_right()==NULL)//葉子節點
 9     {
10         return 1;
11     }
12     int l_height = get_tree_height(r->get_left());
13     int r_height = get_tree_height(r->get_right());
14     return l_height >= r_height ? l_height + 1 : r_height + 1; 
15 }

2.6 交換二叉樹的左右兒子

交換二叉樹的左右兒子,可以先交換根節點的左右兒子節點,然后遞歸以左右兒子節點為根節點繼續進行交換。樹中的操作有先天的遞歸性。。

View Code
 1 //交換二叉樹的左右兒子
 2 void BinTree::swap_left_right(BinTreeNode *r)
 3 {
 4     if(r == NULL)
 5     {
 6         return;
 7     }
 8     BinTreeNode *pTmpNode = r->get_left();
 9     r->set_left(r->get_right());
10     r->set_right(pTmpNode);
11     swap_left_right(r->get_left());
12     swap_left_right(r->get_right());
13 }

2.7 判斷一個節點是否在一顆子樹中

可以和當前根節點相等,也可以在左子樹或者右子樹中。 

View Code
 1 //判斷一個節點t是否在以r為根的子樹中
 2 bool BinTree::is_in_tree(BinTreeNode *r,BinTreeNode *t)const
 3 {
 4     if(r == NULL)
 5     {
 6         return false;
 7     }
 8     else if(r == t)
 9     {
10         return true;
11     }
12     else
13     {
14         bool has = false;
15         if(r->get_left() != NULL)
16         {
17             has = is_in_tree(r->get_left(),t);
18         }
19         if(!has && r->get_right()!= NULL)
20         {
21             has = is_in_tree(r->get_right(),t);
22         }
23         return has;
24     }
25 }

2.8 求兩個節點的最近公共祖先

求兩個節點的公共祖先可以用到上面的:判斷一個節點是否在一顆子樹中。(1)如果兩個節點同時在根節點的右子樹中,則最近公共祖先一定在根節點的右子樹中。(2)如果兩個節點同時在根節點的左子樹中,則最近公共祖先一定在根節點的左子樹中。(3)如果兩個節點一個在根節點的右子樹中,一個在根節點的左子樹中,則最近公共祖先一定是根節點。當然,要注意的是:可能一個節點pNode1在以另一個節點pNode2為根的子樹中,這時pNode2就是這兩個節點的最近公共祖先了。顯然這也是一個遞歸的過程啦:

View Code
 1 //求兩個節點的最近公共祖先
 2 BinTreeNode* BinTree::get_nearest_common_father(BinTreeNode *r,BinTreeNode *pNode1,BinTreeNode *pNode2)const
 3 {
 4     //pNode2在以pNode1為根的子樹中(每次遞歸都要判斷,放在這里不是很好。)
 5     if(is_in_tree(pNode1,pNode2))
 6     {
 7         return pNode1;
 8     }
 9     //pNode1在以pNode2為根的子樹中
10     if(is_in_tree(pNode2,pNode1))
11     {
12         return pNode2;
13     }
14     bool one_in_left,one_in_right,another_in_left,another_in_right;
15     one_in_left = is_in_tree(r->get_left(),pNode1);
16     another_in_right = is_in_tree(r->get_right(),pNode2);
17     another_in_left = is_in_tree(r->get_left(),pNode2);
18     one_in_right = is_in_tree(r->get_right(),pNode1);
19     if((one_in_left && another_in_right) || (one_in_right && another_in_left))
20     {
21         return r;
22     }
23     else if(one_in_left && another_in_left)
24     {
25         return get_nearest_common_father(r->get_left(),pNode1,pNode2);
26     }
27     else if(one_in_right && another_in_right)
28     {
29         return get_nearest_common_father(r->get_right(),pNode1,pNode2);
30     }
31     else
32     {
33         return NULL;
34     }
35 }

可以看到這種做法,進行了大量的重復搜素,其實有另外一種做法,那就是存儲找到這兩個節點的過程中經過的所有節點到兩個容器中,然后遍歷這兩個容器,第一個不同的節點的父節點就是我們要找的節點啦。 實際上這還是采用了空間換時間的方法。

2.9 從根節點開始找到所有路徑,使得路徑上的節點值和為某一數值(路徑不一定以葉子節點結束)

這道題要找到所有的路徑,顯然是用深度優先搜索(DFS)啦。但是我們發現DFS所用的棧和輸出路徑所用的棧應該不是一個棧,棧中的數據是相反的。看看代碼:注意使用的兩個棧。

View Code
 1 //注意這兩個棧的使用
 2 stack<BinTreeNode *>dfs_s;
 3 stack<BinTreeNode *>print_s;
 4 //打印出從r開始的和為sum的所有路徑
 5 void BinTree::print_rout(BinTreeNode *r,int sum)const
 6 {
 7     if(r == NULL)
 8     {
 9         return;
10     }
11     //入棧
12     sum -= r->get_data();
13     dfs_s.push(r);
14     if(sum <= 0)
15     {
16         if(sum == 0)
17         {
18             while(!dfs_s.empty())
19             {
20                 print_s.push(dfs_s.top());
21                 dfs_s.pop();
22             }
23             while(!print_s.empty())
24             {
25                 cout<<print_s.top()->get_data()<<" ";
26                 dfs_s.push(print_s.top());
27                 print_s.pop();
28             }
29             cout<<endl;
30         }
31         sum += r->get_data();
32         dfs_s.pop();
33         return;
34     }
35     //遞歸進入左子樹
36     print_rout(r->get_left(),sum);
37     //遞歸進入右子樹
38     print_rout(r->get_right(),sum);
39     //出棧
40     sum += r->get_data();
41     dfs_s.pop();
42 }

 

最后,給出一點測試代碼:

View Code
 1 int main()
 2 {
 3     BinTree tree;
 4     /*--------------------------------------------------------------------------*/
 5     cout<<"請輸入二叉樹前序序列進行建樹,'#'代表空節點:"<<endl;
 6     tree.set_root(tree.create_tree());
 7     cout<<endl;
 8     /*--------------------------------------------------------------------------*/
 9     cout<<"前序遍歷的結果:";
10     tree.pre_order(tree.get_root());
11     cout<<endl<<endl;
12     /*--------------------------------------------------------------------------*/
13     cout<<"中序遍歷的結果:";
14     tree.in_order(tree.get_root());
15     cout<<endl<<endl;
16     /*--------------------------------------------------------------------------*/
17     cout<<"后序遍歷的結果:";
18     tree.post_order(tree.get_root());
19     cout<<endl<<endl;
20     /*--------------------------------------------------------------------------*/
21     cout<<"層次遍歷的結果:";
22     tree.level_order(tree.get_root());
23     cout<<endl<<endl;
24     /*--------------------------------------------------------------------------*/
25     cout<<"該二叉樹葉子節點的個數:";
26     cout<<tree.get_leaf_num(tree.get_root())<<endl<<endl;
27     /*--------------------------------------------------------------------------*/
28     cout<<"該二叉樹的高度是:";
29     cout<<tree.get_tree_height(tree.get_root())<<endl<<endl;
30     /*--------------------------------------------------------------------------*/
31     tree.swap_left_right(tree.get_root());
32     cout<<"交換左右子樹之后的先序遍歷結果為:";
33     tree.pre_order(tree.get_root());
34     cout<<endl<<endl;
35     /*--------------------------------------------------------------------------*/
36     BinTreeNode *p1 = tree.get_root()->get_left()->get_right();
37     BinTreeNode *p2 = tree.get_root()->get_left()->get_left();
38     BinTreeNode *p3 = tree.get_root()->get_right()->get_right()->get_right();
39     cout<<p1->get_data()<<""<<p2->get_data()<<"的最近公共祖先是:";
40     BinTreeNode *p = tree.get_nearest_common_father(tree.get_root(),p1,p2);
41     cout<<p->get_data()<<endl;
42     cout<<p1->get_data()<<""<<p3->get_data()<<"的最近公共祖先是:";
43     p = tree.get_nearest_common_father(tree.get_root(),p1,p3);
44     cout<<p->get_data()<<endl<<endl;
45     /*--------------------------------------------------------------------------*/
46     cout<<"路徑如下:"<<endl;
47     tree.print_rout(tree.get_root(),12);
48     return 0;
49 }

運行結果:

以上內容,只是本人看過的面試題目的一點總結,可能不夠全,可能其中的代碼也有很多的錯誤,可能其中的內容有點水,但是我想對那部分即將參加面試的應屆畢業生還是有幫助的。。大神們勿噴~~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM