zoukankan      html  css  js  c++  java
  • 二叉树中的那些常见的面试题

    关于二叉树


    二叉树作为树的一种,是一种重要的数据结构,也是面试官经常考的东西。昨天看了一下关于树中的面试题,发现二叉树中的面试题比较常见的题型大概有下面几个:创建一颗二叉树(先序,中序,后序)、遍历一颗二叉树(先序,中序,后序和层次遍历)、求二叉树中叶子节点的个数、求二叉树的高度、求二叉树中两个节点的最近公共祖先、打印和为某一值的全部路径、求某一节点是否在一个树中等等。

    再详细的说这些面试题之前,不妨先看一下几种常见的二叉树:

    完全二叉树:若二叉树的高度是h,除第h层之外,其他(1~h-1)层的节点数都达到了最大个数,并且第h层的节点都连续的集中在最左边。想到点什么没?实际上,完全二叉树和堆联系比较紧密哈~~~

    满二叉树:除最后一层外,每一层上的所有节点都有两个子节点,最后一层都是叶子节点。

    哈夫曼树:又称为最有数,这是一种带权路径长度最短的树。哈夫曼编码就是哈夫曼树的应用。

    平衡二叉树:所谓平衡二叉树指的是,左右两个子树的高度差的绝对值不超过 1。

    红黑树:红黑树是每个节点都带颜色的树,节点颜色或是红色或是黑色,红黑树是一种查找树。红黑树有一个重要的性质,从根节点到叶子节点的最长的路径不多于最短的路径的长度的两倍。对于红黑树,插入,删除,查找的复杂度都是O(log N)。

     

    二叉树中的那些面试题


    再具体说二叉树中的那些面试题之前,我们先看一下二叉树中的每个节点是什么样的,以及为了完成这些面试题,二叉树中声明的函数原型是什么样的:

    二叉树的节点:BinTreeNode

    复制代码
    复制代码
     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,其中包含了这篇文章中要完成的函数原型的完整声明。 

    复制代码
    复制代码
     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 # #,那么创建的二叉树如下:

    下面是创建二叉树的完整代码:穿件一颗二叉树,返回二叉树的根。

    复制代码
    复制代码
     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 二叉树的遍历

    二叉树的遍历分为:先序遍历,中序遍历和后序遍历,这三种遍历的写法是很相似的,利用递归程序完成也是灰常简单的:

    复制代码
    复制代码
     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)。因此二叉树的层次遍历利用队列来完成是最好不过啦,当然不是说利用别的数据结构不能完成。

    复制代码
    复制代码
     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 求二叉树中叶子节点的个数

    树中的叶子节点的个数 = 左子树中叶子节点的个数 + 右子树中叶子节点的个数。利用递归代码也是相当的简单,易懂。 

    复制代码
    复制代码
     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 。

    复制代码
    复制代码
     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 交换二叉树的左右儿子

    交换二叉树的左右儿子,可以先交换根节点的左右儿子节点,然后递归以左右儿子节点为根节点继续进行交换。树中的操作有先天的递归性。。

    复制代码
    复制代码
     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 判断一个节点是否在一颗子树中

    可以和当前根节点相等,也可以在左子树或者右子树中。 

    复制代码
    复制代码
     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就是这两个节点的最近公共祖先了。显然这也是一个递归的过程啦:

    复制代码
    复制代码
     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所用的栈和输出路径所用的栈应该不是一个栈,栈中的数据是相反的。看看代码:注意使用的两个栈。

    复制代码
    复制代码
     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 }
    复制代码
    复制代码

     

    最后,给出一点测试代码:

    复制代码
    复制代码
     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 }
    复制代码
    复制代码

    运行结果:

  • 相关阅读:
    .NetCore Grpc 客服端 工厂模式配置授权
    DOCKER 拉取 dotnet 镜像太慢 docker pull mcr.microsoft.com too slow
    Introducing .NET 5
    VSCode 出现错误 System.IO.IOException: The configured user limit (128) on the number of inotify instances has been reached.
    Omnisharp VsCode Attaching to remote processes
    zookeeper3.5.5 centos7 完全分布式 搭建随记
    Hadoop2.7.7 centos7 完全分布式 配置与问题随记
    MySQL索引 索引分类 最左前缀原则 覆盖索引 索引下推 联合索引顺序
    SQL基础随记3 范式 键
    MySQL调优 优化需要考虑哪些方面
  • 原文地址:https://www.cnblogs.com/beipiaoboy/p/3251414.html
Copyright © 2011-2022 走看看