[程序员面试题精选100题]50.树的子结构

题目

输入两棵二叉树A和B,判断树B是不是A的子结构。

例如,下图中的两棵树A和B,由于A中有一部分子树的结构和B是一样的,因此B就是A的子结构。

思路

这是2010年微软校园招聘时的一道题目。二叉树一直是微软面试题中经常出现的数据结构。对微软有兴趣的读者一定要重点关注二叉树。

回到这个题目的本身。要查找树A中是否存在和树B结构一样的子树,我们可以分为两步:
(1)树A中找到和B的根结点的值一样的结点N
(2)再判断树A中以N为根结点的子树是不是包括和树B一样的结构。

第一步在树A中查找与根结点的值一样的结点。这实际上就是树的遍历。对二叉树这种数据结构熟悉的读者自然知道我们可以用递归的方法去遍历,也可以用循环的方法去遍历。由于递归的代码实现比较简洁,面试时如果没有特别要求,我们通常都会采用递归的方式。

// 在root1中寻找与root2根节点值相同的节点
    bool FindSubTree(TreeNode* root1,TreeNode* root2){
        if(root2 == nullptr){
            return true;
        }//if
        if(root1 == nullptr){
            return false;
        }//if
        bool result = false;
        // 找到一个节点与root2根节点相同
        if(root1->val == root2->val){
            result = IsSubTree(root1,root2);
        }//if
        // 如果以root1为根节点的子树不包含root2,继续到root1的子树中寻找
        if(!result){
            return FindSubTree(root1->left,root2) || FindSubTree(root1->right,root2);
        }//if
        return result;
    }

在上述代码中,我们递归调用FindSubTree遍历二叉树A。如果发现某一结点的值和树B的头结点的值相同,则调用IsSubTree,做第二步判断。

在面试的时候,我们一定要注意边界条件的检查,即检查空指针。当树A或树B为空的时候,定义相应的输出。如果没有检查并做相应的处理,程序非常容易崩溃,这是面试时非常忌讳的事情。

接下来考虑第二步,判断以树A中以N为根结点的子树是不是和树B具有相同的结构。同样,我们也可以用递归的思路来考虑:如果结点N的值和树B的根结点不相同,则以N为根结点的子树和树B肯定不具有相同的结点返回false;如果他们的值相同,则递归地判断他们的各自的左右结点的值是不是相同。递归的终止条件是我们到达了树A或者树B的叶结点。

bool IsSubTree(TreeNode* root1,TreeNode* root2){
        if(root2 == nullptr){
            return true;
        }//if
        if(root1 == nullptr){
            return false;
        }//if
        if(root1->val != root2->val){
            return false;
        }//if
        // 如果两节点值不同则判断root1的左子树和root2的左右子树是否相同
        return IsSubTree(root1->left,root2->left) && IsSubTree(root1->right,root2->right);
    }

完整代码

    /*------------------------------------
    *   日期:2015-03-26
    *   作者:SJF0115
    *   题目: 50.树的子结构
    *   来源:程序员面试题精选100题
    ---------------------------------------*/
    #include <iostream>
    #include <vector>
    #include <queue>
    using namespace std;

    struct TreeNode{
        int val;
        TreeNode *left;
        TreeNode *right;
        TreeNode(int x):val(x),left(nullptr),right(nullptr){}
    };
    // 判断以root1为根节点的子树中是否和root2一样
    bool IsSubTree(TreeNode* root1,TreeNode* root2){
        if(root2 == nullptr){
            return true;
        }//if
        if(root1 == nullptr){
            return false;
        }//if
        if(root1->val != root2->val){
            return false;
        }//if
        // 如果两节点值不同则判断root1的左子树和root2的左右子树是否相同
        return IsSubTree(root1->left,root2->left) && IsSubTree(root1->right,root2->right);
    }
    // 在root1中寻找与root2根节点值相同的节点并判断以此为根节点的子树中是否包含root2
    bool FindSubTree(TreeNode* root1,TreeNode* root2){
        if(root2 == nullptr){
            return true;
        }//if
        if(root1 == nullptr){
            return false;
        }//if
        bool result = false;
        // 找到一个节点与root2根节点相同
        if(root1->val == root2->val){
            result = IsSubTree(root1,root2);
        }//if
        // 如果以root1为根节点的子树不包含root2,继续到root1的子树中寻找
        if(!result){
            return FindSubTree(root1->left,root2) || FindSubTree(root1->right,root2);
        }//if
        return result;
    }

    // 创建二叉树
    void CreateTree(TreeNode* &root){
        int val;
        cin>>val;
        if(val == -1){
            root = nullptr;
            return;
        }//if
        root = new TreeNode(val);
        // 左子树
        CreateTree(root->left);
        // 右子树
        CreateTree(root->right);
    }
    // 2.1 递归先序遍历
    void PreOrder(TreeNode* root,vector<int> &result){
        if(root == nullptr){
            return;
        }//if
        result.push_back(root->val);
        // 左子树
        PreOrder(root->left,result);
        // 右子树
        PreOrder(root->right,result);
    }
    // 输出结果
    void Print(vector<int> result){
        int size = result.size();
        for(int i = 0;i < size;++i){
            cout<<result[i]<<" ";
        }//for
        cout<<endl;
    }
    int main(){
        freopen("C:\\Users\\Administrator\\Desktop\\c++.txt", "r", stdin);
        TreeNode *root1,*root2;
        vector<int> result;
        CreateTree(root1);
        PreOrder(root1,result);
        Print(result);
        result.clear();

        CreateTree(root2);
        PreOrder(root2,result);
        Print(result);
        result.clear();

        cout<<FindSubTree(root1,root2);
        return 0;
    }
时间: 2024-10-24 19:13:36

[程序员面试题精选100题]50.树的子结构的相关文章

[程序员面试题精选100题]9.链表中倒数第k个结点

题目 输入一个单向链表,输出该链表中倒数第k个结点.链表的倒数第0个结点为链表的尾指针. 思路一 因为是单向链表,只有从前往后的指针而没有从后往前的指针.因此我们不能倒序遍历链表,只能正序遍历.假设整个链表有n个结点,那么倒数第k个结点是从头结点开始的第n-k-1个结点(从0开始计数).我们只需要得到链表中结点的个数n,那我们只要从头结点开始往后走n-k-1步就可以了. 因此这种方法需要遍历链表两次.第一次得到链表中结点个数n,第二次得到从头结点开始的第n-k-1个结点即倒数第k个结点.时间复杂

[程序员面试题精选100题]1.把二叉查找树转变成排序的双向链表

[题目] 输入一棵二叉查找树,将该二叉查找树转换成一个排序的双向链表.要求不能创建任何新的结点,只调整指针的指向. 比如将二叉查找树                                            10                                          /    \                                        6       14                                      / 

[程序员面试题精选100题]58.八皇后问题

题目 在8×8的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行.同一列或者同一对角斜线上.下图中的每个黑色格子表示一个皇后,这就是一种符合条件的摆放方法.请求出总共有多少种摆法. 思路 这就是有名的八皇后问题.解决这个问题通常需要用递归,而递归对编程能力的要求比较高.因此有不少面试官青睐这个题目,用来考察应聘者的分析复杂问题的能力以及编程的能力. 由于八个皇后的任意两个不能处在同一行,那么这肯定是每一个皇后占据一行.于是我们可以定义一个数组colIndex[8],colI

[程序员面试题精选100题]6.二叉查找树的后序遍历结果

[题目] 输入一个整数数组,判断该数组是不是某二叉查找树的后序遍历的结果.如果是返回true,否则返回false. 例如输入5.7.6.9.11.10.8,由于这一整数序列是如下树的后序遍历结果:           8         /    \       6    10      /   \   /   \    5    7 9 11 因此返回true. 如果输入7.4.6.5,没有哪棵树的后序遍历的结果是这个序列,因此返回false. [分析] 这是一道trilogy的笔试题,主要考

[程序员面试题精选100题]12.从上往下遍历二叉树

[题目] 输入一颗二元树,从上往下按层打印树的每个结点,同一层中按照从左往右的顺序打印. 例如输入         8      /      \     6     10    /   \     /  \   5  7   9  11 输出8   6   10   5   7   9   11 [分析] 这曾是微软的一道面试题.这道题实质上是要求遍历一棵二元树,只不过不是我们熟悉的前序.中序或者后序遍历. 我们从树的根结点开始分析.自然先应该打印根结点8,同时为了下次能够打印8的两个子结点,

[程序员面试题精选100题]13.第一个只出现一次的字符

[题目] 在一个字符串中找到第一个只出现一次的字符.如输入abaccdeff,则输出b. [分析] [代码] /********************************* * 日期:2013-12-23 * 作者:SJF0115 * 题目: 13.第一个只出现一次的字符 * 来源:微软 * 分类:程序员面试题精选100题 **********************************/ #include <iostream> #include <cstring> us

[程序员面试题精选100 题]17.把字符串转换成整数

题目 输入一个表示整数的字符串,把该字符串转换成整数并输出.例如输入字符串"345",则输出整数345. 分析 这道题尽管不是很难,学过 C/C++语言一般都能实现基本功能,但不同程序员就这道题写出的代码有很大区别,可以说这道题能够很好地反应出程序员的思维和编程习惯,因此已经被包括微软在内的多家公司用作面试题.建议读者在往下看之前自己先编写代码,再比较自己写的代码和下面的参考代码有哪些不同. 我们需要考虑一下几个方面的问题: (1)正负问题: 由于整数可能不仅仅之含有数字,还有可能以'

[程序员面试题精选100题]19.反转链表

题目 输入一个链表的头结点,反转该链表,并返回反转后链表的头结点. 分析 假设经过若干操作,我们已经把结点 pre之前的指针调整完毕,这些结点的next指针都指向前面一个结点.现在我们遍历到结点cur.我们需要把调整结点的next指针让它指向前一个结点pre.注意一旦调整了指针的指向,链表就断开了,如下图所示: 因为已经没有指针指向结点nextNode,我们没有办法再遍历到结点nextNode 了.因此为了避免链表断开,我们需要在调整cur的next指针之前要把nextNode保存下来. 代码

[程序员面试题精选100题]10.排序数组中和为给定值的两个数字

剑指Offer之和为S的两个数字 剑指Offer之和为S的连续正数序列 扩展(1):输入一个数组,判断这个数组中是不是存在三个数字i, j, k,满足i+j+k等于0. 扩展(2):如果输入的数组是没有排序的,但知道里面数字的范围,其他条件不变,如何在O(n)时间里找到这两个数字?这个的基本思路是先用哈希表实现O(n)的排序(请参照本面试题系列的第57题),接下来的步骤都一样了.