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

    运行结果:

    以上内容,只是本人看过的面试题目的一点总结,可能不够全,可能其中的代码也有很多的错误,可能其中的内容有点水,但是我想对那部分即将参加面试的应届毕业生还是有帮助的。。大神们勿喷~~

  • 相关阅读:
    开源推荐 | 可实现门禁、AI测温敏捷开发的人脸识别应用套件
    如何快速实现人脸识别通道?一文了解具体技巧
    虹软人脸识别——官方 Qt Demo 移植到 Linux
    人脸识别Demo解析C#
    智东西公开课干货盘点 | 全方位解析人脸识别商用落地
    智东西公开课 | 虹软带你读懂人脸识别商用那些事【第二期】
    虹软人脸识别
    虹软AI 人脸识别SDK接入 — 参数优化篇
    虹软2.0版本离线人脸识别C#类库分享
    虹软人脸识别Android Sample Code
  • 原文地址:https://www.cnblogs.com/zhouxiansheng/p/3946942.html
Copyright © 2011-2022 走看看