zoukankan      html  css  js  c++  java
  • 二叉树常见面试题(进阶)

    一、常见题型

    1. 求两个节点的最近公共祖先;

    2. 求二叉树中最远的两个节点的距离;

    3. 由前序遍历和中序遍历重建二叉树(如:前序序列:1 2 3 4 5 6 - 中序序列 :3 2 4 1 6 5);

    4. 判断一棵树是否是完全二叉树 ;

    5. 将二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向;

    6.求二叉树的宽度;

    7. 判断一棵二叉树是否是平衡二叉树;

    8.判断一颗二叉树是否是另一颗树的子树。

    二、解题思路分析

    1.两个节点的最近公共祖先

    求两个节点的最近公共祖先可分为三种情况,分别为:

    (1)搜索二叉树,根据搜索二叉树的性质,左子树的所有节点比根节点小,右子树的所有节点比跟节点大。


    如果两个节点都比根节点小,则递归左子树 ;
    如果两个节点都比跟节点大,则递归右子树 ;
    否则,两个节点一个在左子树,一个在右子树,则当前节点就是最近公共祖先节点。

     1 Node* GetAncestor(Node* root, Node* x1, Node* x2)//1.该二叉树为搜索二叉树
     2         {
     3             assert(x1 && x2);
     4             if (x1->_data <= root->_data && x2->_data <= root->_data)
     5             {
     6                 return GetAncestor(root->_left, x1, x2);//两个节都小于根节点,最近公共祖先在左子树中
     7             }
     8             else if (x1->_data > root->_data && x2->_data > root->_data)
     9             {
    10                 return GetAncestor(root->_right, x1, x2);//两个节都大于根节点,最近公共祖先在左子树中
    11             }
    12             else
    13                 return root;  //一个在左子树,一个在右子树,找到公共祖先
    14 
    15         }

    (2)三叉链,二叉树节点有指向父节点的指针。首先给出node1的父节点node1->_parent,然后将node1的所有父节点依次和node2->parent作比较,如果发现两个节点相等,则该节点就是最近公共祖先,直接将其返回。如果没找到相等节点,则将node2的所有父节点依次和node1->_parent->_parent作比较......直到node1->_parent==NULL。代码如下:

     1     struct BinaryNode   //节点的结构
     2     {  
     3         BinaryNode* _left;  
     4         BinaryNode* _right;  
     5         BinaryNode* _parent;  
     6         int _data;  
     7       
     8         BinaryNode(const int& data)  
     9             :_data(data)  
    10             , _left(NULL)  
    11             , _right(NULL)  
    12             , _parent(NULL)  
    13         {}  
    14     };  
     1   Node * GetLastCommonAncestor(Node * root, Node * node1, Node * node2)  
     2     {  
     3         Node * temp;  
     4         while (node1 != NULL)  
     5         {  
     6             node1 = node1->_parent;  
     7             temp = node2;  
     8             while (temp != NULL)  
     9             {  
    10                 if (node1 == temp->_parent)  
    11                     return node1;  
    12                 temp = temp->_parent;  
    13             }  
    14         }  
    15     }  

    该算法时间复杂度为O(n^2),可用另一种O(n)的算法:

    给定的两个节点都含有父节点,因此,可将这两个节点看做是两个链表的头结点,将求两个节点的最近公共祖先节点转化为求两链表的交点,这两个链表的尾节点都是根节点。

     1     int Hight(BinaryNode* root, BinaryNode* node)  
     2     {  
     3         int len = 0;  
     4         for (; node != NULL; node = node->_parent)  
     5             len++;  
     6       
     7         return len;  
     8     }  
     9     BinaryNode* GetLastCommonAncestor(BinaryNode* root, BinaryNode* node1, BinaryNode* node2)  
    10     {  
    11       
    12         if (root == NULL || node1 == NULL || node2==NULL)  
    13             return NULL;  
    14       
    15         int len1 = Hight(root,node1);  
    16         int len2 = Hight(root,node2);  
    17               
    19         for (; len1 > len2; len1--)  
    20             node1 = node1->_parent;  
    21         for (; len2 > len1; len2--)  
    22             node2 = node2->_parent;  
    23       
    24         while (node1 && node2 && node1 != node2)  
    25         {  
    26             node1 = node1->_parent;  
    27             node2 = node2->_parent;  
    28         }  
    29           
    30         if (node1 == node2)  
    31             return node1;  
    32         else  
    33             return NULL;  
    34     }  

    (3)普通二叉树,这种情况可采用与搜索二叉树类似的解法

     从根节点开始遍历,如果node1和node2中的任一个和root匹配,那么与root匹配的节点就是最低公共祖先。 如果都不匹配,则分别递归左、右子树,如果有一个 节点出现在左子树,并且另一个节点出现在右子树,则root就是最低公共祖先.  如果两个节点都出现在左子树,则说明最低公共祖先在左子树中,否则在右子树。

     1 Node* GetAncestor(Node* root, Node* x1, Node* x2)
     2         {
     3             assert(x1 && x2);
     4             if (root == NULL) {
     5                 return NULL;
     6             }
     7             if (root == x1 || root == x2) //如果两个节点是父子关系,其中的一个节点为公共祖先
     8             {
     9                 return root;
    10             }
    11             bool x1inleft, x2inleft, x1inright, x2inright;
    12             x1inleft = JudgeNode(root->_left, x1);  //判断x1是否在左子树
    13             x1inright = JudgeNode(root->_right x1);  //判断x1是否在右子树
    14             assert(x1inleft || x1inright);  //至少有一个为真
    15             x2inleft = JudgeNode(root->_left, x2);  //判断x2是否在左子树
    16             x2inright = JudgeNode(root->_right, x2);  //判断x2是否在右子树
    17             assert(x2inleft || x2inright);  //至少有一个为真
    18             if ((x1inleft && x2inright) || (x1inright && x2inright))
    19             {
    20                 return root;  //一个在左子树,一个在右子树,找到公共祖先
    21             }
    22             else if (x1inleft && x2inleft)  //两个节都在左子树中,最近公共祖先在左子树中
    23             {
    24                 return GetAncestor(root->_left, x1, x2);
    25             }
    26             else {  //两个节都在右子树中,最近公共祖先在右子树中
    27                 return GetAncestor(root->_right, x1, x2);
    28             }
    29         }

    上述方法时间复杂度为O(N^2),下面的方法时间复杂度为O(N),但是需要额外的空间来存储路径。

    1) 找到从根到node1的路径,并存储在一个向量或数组中。
    2)找到从根到node2的路径,并存储在一个向量或数组中。
    3) 遍历这两条路径,直到遇到一个不同的节点,则前面的那个即为最低公共祖先.

     

     1         bool GetNodePaths(Node* root, Node* node, stack<Node *>& s)
     2         {
     3             if (root == NULL)
     4             {
     5                 return false;
     6             }
     7             s.push(root);
     8             if (root == node)
     9             {
    10                 return true;
    11             }
    12             bool inleft = GetNodePaths(root->_left, node, s);
    13             if (inleft)
    14             {
    15                 return true;
    16             }
    17             bool inright = GetNodePaths(root->_right, node, s);
    18             if (inright)
    19             {
    20                 return true;
    21             }
    22             s.pop();
    23             return false;
    24         }
    25         Node* GetAncestor(Node* root, Node* x1, Node* x2);
    26         {
    27             assert(x1 && x2);
    28             stack<Node*> paths1, paths2;
    29             if (!GetNodePaths(root->_left, x1, paths1) || !GetNodePaths(root->_right, x2, paths2))
    30             {
    31                 return NULL;
    32             }
    else{
               while(paths1.size()>paths2.size()){
                  paths1.pop();
               }
               while(paths1.size()<paths2.size()){
                  paths2.pop();
               }

               while(!paths1.empty() && !paths2.empty() && paths1.top()!=paths2.top()){
                  if(paths1.top()==paths2.top())
                    return paths1.top();
                  paths1.pop();
                  paths2.pop();
               }
             }
             return NULL;
    33 }

    2.最远的两个节点的距离

     第一种情况最远的两个节点的距离为它们到根节点的路径长度之和,又有可能距离最远的两个节点之间的路径不经过根节点,如图所示:

    所以不要考虑不全,直接用两个子树的的高度相加来表示最远的两个节点的距离。有两种方法求解:

    还是要借助两个子树的高度求解,但是要递归整棵树,如果子树中出现第二种情况要更新最大距离,时间复杂度为O(N^2)。

     1     //求二叉树中最远的两个节点的距离
     2     size_t MaxLen()
     3     {
     4         size_t maxlen = 0;
     5         _MaxLen(_root, maxlen);
     6         return maxlen;
     7     }
     8     void _MaxLen(Node* root, size_t maxlen)  //O(N^2)
     9     {
    10         if (root == NULL)
    11         {
    12             return 0;
    13         }
    14         int leftdepth = Depth(root->_left);  
    15         int rightdepth = Depth(root->_right);
    16         if (leftdepth + rightdepth > maxlen)
    17         {
    18             maxlen = leftdepth + rightdepth;
    19         }
    20         _MaxLen(root->_left, maxlen);
    21         _MaxLen(root->_right, maxlen);
    22     }

    另一种时间复杂度为O(N)的解法:

     1     size_t _MaxLen(Node* root, size_t maxlen)  //O(N)
     2     {
     3         if (root == NULL)
     4         {
     5             return;
     6         }
     7         size_t left = _MaxLen(root->_left, maxlen);
     8         size_t right = _MaxLen(root->_right, maxlen);
     9         if (right+left>maxlen)
    10         {
    11             maxlen = right + left;
    12         }
    13         return left > right ? left + 1 : right + 1;
    14     }

    3. 前序遍历和中序遍历重建二叉树

    这个题是要用一颗二叉树的前序遍历序列和中序遍历序列,如:前序序列:1 2 3 4 5 6 - 中序序列 :3 2 4 1 6 5,来重新构建二叉树。可以利用前序序列和中序序列中根节点的位置特性作为重建依据。图示解析过程如下:

    创建右子树的方法与左子树的方法完全相同。当 prev 遍历完前序序列,即二叉树创建完成。代码如下:

     1 //由前序遍历和中序遍历重建二叉树(如:前序序列:1 2 3 4 5 6 - 中序序列 :3 2 4 1 6 5)
     2         Node* RebulidTree(char* prev, char* inbgein, char* inend)
     3         {
     4             assert(prev && inbgein && inend);
     5             if (inbgein > inend || prev == '')
     6             {
     7                 return NULL;
     8             }
     9             Node* root = new Node(*prev);  //先创建根节点
    10             char* div = inbgein;  //让div查找根节点
    11             while (div <= inend) {
    12                 if (*div == *prev)
    13                 {
    14                     if (inbgein <= div -1)
    15                     {
    16                         root->_left = RebulidTree(++prev, inbgein, div - 1);//递归创建左子树
    17                     }
    18                     else {
    19                         root->_left = NULL;
    20                     }
    21                     if (div + 1 <= inend)
    22                     {
    23                         root->_right = RebulidTree(++prev, div + 1, inend);//递归创建右子树
    24                     }
    25                     else {
    26                         root->_right = NULL;
    27                     }
    28                     break;
    29                 }
    30                 ++div;
    31             }
    32             return root;
    33         }

    4. 判断一棵树是否是完全二叉树

    完全二叉树: 前n-1层都是满的,第n层如有空缺,则是缺在右边,即第n层的最右边的节点,它的左边是满的,右边是空的。

    这是一个层序遍历非递归法的变型题,同样要借助额外空间来临时存储节点。按照层序遍历二叉树,找到第一个只有非满结点(这个节点只有两种情况,孩子为空或者只有左没有右),如果之后的节点还有非满结点,则不是。

     1     bool IsComplateTree(Node* root)
     2     {
     3         queue<Node*> q;
     4         if (root)
     5         {
     6             q.push(root);  //先将节点压入队列中
     7         }
     8         //这里给一个tag是标记是否出现非满节点
     9         bool tag = true;
    10         while (!q.empty())
    11         {
    12             Node* front = q.front();  
    13             q.pop();
    14             //如果已经出现过非满结点,则后面再出现有孩子的结点则一定不是完全二叉树。
    15             if (front->_left)
    16             {
    17                 if (tag == false)
    18                 {
    19                     return false;
    20                 }
    21                 q.push(front->_left);
    22             }
    23             else {
    24                 tag = false;
    25             }
    26             if (front->_right)
    27             {
    28                 if (tag == false)
    29                 {
    30                     return false;
    31                 }
    32                 q.push(front->_right);
    33             }
    34             else {
    35                 tag = false;
    36             }
    37         }
    38         return true;
    39     }

    第二种思路:将所有的结点全部押入队列中,每次判断队列的头如果队列头为空了则跳出循环,如果此后队列中还有元素则不是完全二叉树。

     1 bool IsCompleteTree(BinaryTreeNode *pRoot)
     2 {
     3          if(pRoot == NULL)
     4                return false;
     5 
     6           queue<BinaryTreeNode*> q;
     7           q.push(pRoot);
     8           BinaryTreeNode* pCur = q.front();
     9           while(pCur != NULL)
    10           {
    11                q.pop();
    12                q.push(pCur -> left);
    13                q.push(pCur -> right);
    14                pCur = q.front();
    15           }
    16 
    17           q.pop();//把空pop出来
    18           //因为以经有一个空了,所以只要头不为空就不是完全二叉树
    19           while(! q.empty())
    20           {
    21                if(q.front() != NULL)
    22                     return false;
    23                q.pop();
    24           }
    25           return true;
    26 }

    5. 将二叉搜索树转换成一个排序的双向链表

    与二叉树的线索花化雷同

     1     void _ToList(Node* cur, Node*& prev)
     2     {
     3         if (cur == NULL)
     4             return;
     5 
     6         _ToList(cur->_left, prev);
     7         // 
     8         cur->_left = prev;
     9         if(prev)
    10             prev->_right = cur;
    11 
    12         prev = cur;
    13 
    14         _ToList(cur->_right, prev);
    15     }
    16 
    17     Node* ToList(Node* root)
    18     {
    19         Node* prev = NULL;
    20         _ToList(root, prev);
    21 
    22         Node* head = root;
    23         while (head && head->_left)
    24         {
    25             head = head->_left;
    26         }
    27 
    28         return head;
    29     }

    6.求二叉树的宽度

    所谓二叉树的宽度是指:二叉树各层节点个数的最大值。

    我们知道层序遍历二叉树是使用 queue 来实现的:每次打印一个节点之后,如果存在左右子树,则把左右子树压入 queue,那么此时的队列中可能既包含当前层的节点,也包含下一层的节点。

    而我们要求的是对于特定某一层的节点的个数,因此我们需要从头结点开始,记录每一层的个数,对于当前层的每一个节点,在弹出自身之后把其左右子树压入 queue,当把当前层全部弹出队列之后,在队列中剩下的就是下一层的节点。然后比较队列的size和之前得到的maxWidth,取最大值即为队列的宽度。最终队列为空,得到的maxWidth就是二叉树的宽度!

     1     int Width(Node* root)
     2     {
     3         queue<Node*> q;
     4         if (root)
     5             q.push(root);
     6         int maxwidth = 1;
     7         while (!q.empty())    
     8         {
     9             int length = q.size();
    10             while (length-- > 0)    
    11             {
    12                 Node* front = q.front();
    13                 q.pop();
    14                 if (front->_left)
    15                 {
    16                     q.push(front->_left);
    17                 }
    18                 if (front->_right)
    19                 {
    20                     q.push(front->_right);
    21                 }
    22             }
    23             maxwidth = maxwidth > q.size() ? maxwidth : q.size();
    24         }
    25         return maxwidth;
    26     }

    7. 二叉树是否是平衡二叉树

    二叉树中每一个节点的左右子树高度之差均小于2即为平衡二叉树。那么当一颗二叉树的所有子树都是平衡二叉树时,它本身必定为平衡二叉树,用此思想可递归判断二叉树是否是平衡二叉树。代码如下:

     1     //--判断一棵二叉树是否是平衡二叉树
     2     bool IsBalance(Node* root)  //O(N^2)
     3     {
     4         if (root == NULL)
     5         {
     6             return false;
     7         }
     8         int left = Depth(root->_left);
     9         int right = Depth(root->_right);  
    10         return abs(right - left) < 2 && IsBalance(root->_left) && IsBalance(root->_right);
    11     }

    这种方法借助左右的高度比较来确定是否为二叉树,需多次遍历二叉树,时间复杂度为O(N^2)。下面是一种O(N)的算法:

     1     bool IsBalance(Node* root, int& depth)  //O(N)
     2     {
     3         if (root == NULL)
     4         {
     5             depth = 0;
     6             return true;
     7         }
     8         int leftdepth = 0;
     9         if (IsBalance(root->_left, leftdepth) == false)
    10         {
    11             return false;
    12         }
    13         int rightdepth = 0;
    14         if (IsBalance(root->_right, rightdepth) == false)
    15         {
    16             return false;
    17         }
    18         depth = rightdepth > leftdepth ? rightdepth + 1 : leftdepth + 1;
    19         return abs(leftdepth - rightdepth) < 2;
    20     }

     8.二叉树是否为另一颗树的子树

    判断一颗二叉树是否是另一颗树的子树。

     先在找二叉树里找根节点,找到之后判断后续的节点是否相等,如果相等,则为子树。

     1     bool JudgeNextTree(Node* next, Node* child) //两棵树的起始节点的值已经相等,在判断其他节点是否相等
     2     {
     3         if (child == NULL)
     4         {
     5             return true;
     6         }
     7         if (next == NULL)
     8         {
     9             return false;
    10         }
    11         if (next->_data == child->_data)    //
    12         {
    13             return JudgeNextTree(next->_left, child->_left) && JudgeNextTree(next->_right, child->_right);
    14         }
    15         else {
    16             return false;  //如果左右孩子都相等,则是子树,否则不是
    17         }
    18     }
    19     bool JudgeTree(Node* parent, Node* child) //判断child是否为parent的子树
    20     {
    21         if (child == NULL) //空树是任何树的子树
    22         {
    23             return true;
    24         }
    25         if (parent == NULL)  //空树没有除空树的任何子树
    26         {
    27             return false;
    28         }
    29         if (parent->_data == child->_data)  //当前节点与要查找子树的根节点相同时
    30         {
    31             return JudgeNextTree(parent, child);  //从相等节点开始判断是否为子树
    32         }
    33         else if (JudgeTree(parent->_left, child->_left) == true)  //判断当前节点的左子树是否与要查找子树的根节点相同
    34         {
    35             return true;
    36         }
    37         else {
    38             return JudgeTree(parent->_right, child->_right);  //判断当前节点的右子树是否与要查找子树的根节点相同
    39         }
    40     }
  • 相关阅读:
    Spring @EventListener 异步中使用condition的问题
    tomcat启动时报:IOException while loading persisted sessions: java.io.EOFException的解决方案 ZT
    Linux上更换默认的java版本
    Spring Security框架下Restful Token的验证方案
    Restful下的token认证方案
    Spring4.x Jpa + hibernate的配置(废弃JpaTemplate)
    FastCV安装报错---LaunchAnyWhere错误:载入Java VM时Windows出现错误:2
    Spring AOP 开发中遇到问题:Caused by: java.lang.IllegalArgumentException: warning no match for this type name: com.xxx.collector.service.impl.XxxServiceImpl [Xlint:invalidAbsoluteTypeName]
    支付宝APP支付开发- IOException : DER input, Integer tag error
    PowerDesigner新装后的设置
  • 原文地址:https://www.cnblogs.com/33debug/p/7252371.html
Copyright © 2011-2022 走看看