面试题—树

  1. 二叉树的深度
    题目:输入一颗二叉树的根节点,求该树的深度。从根节点到叶节点一次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
    分析:如果只有根节点,深度为1.如果只有左子树,那么树的深度是左子树深度+1;如果有左子树和右子树,则就是两者深度的最大值再加1.递归很容易实现。

    int TreeDepth(BinaryTreeNode* root)
    {
    	if(root == NULL)
    		return 0;
    	int left = TreeDepth(root->pLeft);
    	int right = TreeDepth(root->pRight);
    	return (left > right)?(left+1):(right+1);
    }

    延伸:输入一颗二叉树的根节点,判断该树是否为平衡二叉树。即如果任意结点的左右子树的深度相差不超过1,那么就是一颗平衡二叉树。
    分析:如果用上述计算深度的函数来进行判断,会重复遍历。那如果能使用后序遍历来遍历二叉树的每一个结点,则遍历到一个节点之前,它的左右子树都已经遍历过了。在遍历结点的时候记录它的深度,使用它到叶节点路径的长度表示,则可以一遍遍历判断是否是二叉平衡树。

    bool Balanced(BinaryTreeNode* root,int *depth)
    {
    	//传入根节点和表示节点深度的整型,初值0
    	if(root == NULL) 
    	{
    		*depth = 0;
    		return true;
    	}
    	int left,right;
    	if(Balanced(root->pLeft,&left)&&Balanced(root->pRight,&right))
    	{
    		int diff = left - right;
    		if(diff<=1 && diff>=-1)
    		{
    			*depth = 1+(left > right?left:right);
    			return true;
    		}
    	}
    	return false;
    }
  2. 树的子结构
    题目:输入两棵二叉树AB,判断B是否为A的子结构。

    struct BinaryTreeNode
    {
    	int nValue;
    	BinaryTreeNode* pLeft;
    	BinaryTreeNode* pRight;
    };
    
    bool isSubtree(BinaryTreeNode* Root1,BinaryTreeNode* Root2)
    {
    	if(Root2 == NULL) return true;
    	if(Root1 == NULL) return false;
    	if(Root1->nValue != Root2->nValue)
    		return false;
    	return isSubtree(Root1->pLeft,Root2->pLeft)&&
    		isSubtree(Root1->pRight,Root2->pRight);
    }
    
    bool Subtree(BinaryTreeNode* Root1,BinaryTreeNode* Root2)
    {
    	bool result = false;
    	if(Root1!=NULL && Root2!=NULL)
    	{
    		if(Root1->nValue == Root2->nValue)
    			result = isSubtree(Root1,Root2);
    		if(!result)
    			result = Subtree(Root1->pLeft,Root2);
    		if(!result)
    			result = Subtree(Root1->pRight,Root2);
    	}
    	return result;
    }
  3. 二叉树中和为某一值的路径
    题目:输入一颗二叉树和一个整数,打印出二叉树中节点值的和为该整数的所有路径。
    分析:前序遍历,访问到某一个结点,首先添加进路径,计算累加值是否等于整数,符合打印出,不符合时若为非叶子节点,则继续访问它的子结点。记得在函数退出返回上一层父结点时,需减去当前结点的值。(递归)

    void FindPath(BinaryTreeNode *root,int value,std::vector<int>& path,int& cursum)
    {
    	cursum += root->nValue;
    	path.push_back(root->nValue);
    
    	//叶节点,且和为value,打印路径
    	bool isLeaf;
    	if( root->pLeft == NULL &&  root->pLeft == NULL)
    		isLeaf = true;
    	else 
    		isLeaf = false;
    
    	if(isLeaf && cursum == value)
    	{
    		cout<<"A path is found:";
    		std::vector<int>::iterator iter = path.begin();
    		for(;iter!=path.end();iter++)
    			cout<<*iter<<"  ";
    		cout<<endl;
    	}
    
    	//非叶子结点,遍历子结点
    	if(root->pLeft!=NULL)
    		FindPath(root->pLeft,value,path,cursum);
    	if(root->pRight!=NULL)
    		FindPath(root->pRight,value,path,cursum);
    
    	//返回父结点
    	cursum -= root->nValue;
    	path.pop_back();
    }
    void Find(BinaryTreeNode *root,int value)
    {
    	if(root == NULL) return;
    	std::vector<int> path;
    	int sum = 0;
    	FindPath(root,value,path,sum);
    }
  4. 重建二叉树
    题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的结果中都不含有重复的数字。
    前序遍历{1,2,4,7,3,5,6,8},中序遍历{4,7,2,1,5,3,8,6}
    分析:前序遍历:根左右;中序遍历:左根右。首先找到根节点,由前序遍历知道1为根节点,然后中序遍历中的4,7,2就为左子树节点,5,3,8,6为右子树节点。同样分析前序遍历中{2,4,7}和中序遍历中{4,7,2}的顺序,即使用递归即可。

    struct BinaryTreeNode
    {
    	int nValue;
    	BinaryTreeNode* pLeft;
    	BinaryTreeNode* pRight;
    };
    
    BinaryTreeNode* ConstructCore(int* startPreorder, int* endPreorder,
    							  int* startInorder, int* endInorder)
    {
    	//前序遍历序列的第一个数字根节点
    	int rootValue = startPreorder[0];
    	BinaryTreeNode* root = new BinaryTreeNode();
    	root->nValue = rootValue;
    	root->pLeft = root->pRight = NULL;
    
    	//递归到最后
    	if(startPreorder == endPreorder)
    	{
    		if(startInorder == endInorder && *startPreorder == *startInorder)
    			return root;
    		else
    			throw std::exception("Invalid input.");
    	}
    	//在中序遍历中找到根节点的值
    	int* rootInorder = startInorder;
    	while(rootInorder <= endInorder && *rootInorder!= rootValue)
    		++rootInorder;
    	if(rootInorder == endInorder && *rootInorder!= rootValue)
    		throw std::exception("Invalid input.");
    
    	int LeftLength = rootInorder - startInorder;//左子树长度
    	int* leftPreorderEnd = startPreorder + LeftLength;
    	if(LeftLength>0)
    	{
    		//构建左子树
    		root->pLeft = ConstructCore(startPreorder + 1,leftPreorderEnd,startInorder,rootInorder-1);
    	}
    	if(LeftLength < endPreorder - startPreorder)
    	{
    		//构建右子树
    		root->pRight = ConstructCore(leftPreorderEnd + 1,endPreorder,rootInorder+1,endInorder);
    	}
    	return root;
    }
    
    BinaryTreeNode* Construct(int* preorder, int* inorder, int length)
    {
    	if(preorder == NULL || inorder ==NULL || length <= 0)
    		return NULL;
    	return ConstructCore(preorder,preorder+length-1,
    		inorder,inorder+length-1);
    }


  5. 二叉树的后序遍历序列
    题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。假设输入的数组任意两个数字都不相同。
    分析:{5,7,6,9,11,10,8}确定8为根节结点,则前半部分小于8的为左子树,后半部分大于8的为右子树,递归判断。

    bool postorder(int BST[],int len)
    {
    	if(BST == NULL || len < 0)
    		return false;
    	 int root = BST[len-1];
    
    	 int leftlen = 0;
    	 while(BST[leftlen] < root)
    	 {
    		 leftlen++;
    	 }
    	 int j = leftlen;
    	 for(;j < len-1;++j)
    	 {
    		 if(BST[j] < root)
    			 return false;
    	 }
    	 bool left = true;
    	 if(leftlen > 0)
    		 bool left = postorder(BST,leftlen);
    	 bool right = true;
    	 if(leftlen < len - 1)
    		 right = postorder(BST+leftlen,len-leftlen-1);
    	 return (left&&right);
    }
  6. 从上到下打印二叉树
    题目:从上往下打印出二叉树的每个结点,同一层按照从左到右顺序打印。
    分析:每打印一个节点,将其子结点插入一个队列的尾部。

    void LayerPrint(BinaryTreeNode* root)
    {
    	if(root == NULL)
    		return;
    	std::deque<BinaryTreeNode *> queueTree;
    	queueTree.push_back(root);
    
    	while(queueTree.size())
    	{
    		BinaryTreeNode *node = queueTree.front();
    		cout << node->nValue;
    		queueTree.pop_front();
    
    		if(node->pLeft != NULL)
    			queueTree.push_back(node->pLeft);
    		if(node->pRight != NULL)
    			queueTree.push_back(node->pRight);
    	}
    }
  7. 树中两个节点的最低公共祖先
    题目:二叉树中查找两个结点的第一个公共祖先,避免在数据结构中存储额外的结点。注意:这不一定需要是二叉搜索树。分析:假设是二叉搜索树,我们可以在这两个结点上做一个改进的搜索来看这两路径在哪里分叉。因为根节点是所有节点的祖先,又因为二叉树自身的性质,我们会得到,当两个目标节点都比当前节点小的时候,我们走左节点,当两个目标节点都比当前节点大的 时候,我们走右节点。第一个碰到的节点的值在两个目标节点之间的节点就是 lowest common ancestor。

    //二叉搜索树
    int findLowestCommonAncestor(node* root, int value1, int value2) {  
            node* curNode = root;  
            while(1) {  
                 // go to the left child  
                 if(curNode->value>value1 && curNode->value>value2)   
                     curNode = curNode->left;  
                 // go to the right child  
                 else if (curNode->value < value1 && curNode->value < value2)  
                      curNode = curNode->right;  
                 else  
                      return curNode->value;  
            }  
        }

    可惜的是,如果不是二叉查找树,就得使用别的办法。
    思路1 :如果每个结点都连接到他的父结点,那么我们就可以跟踪p跟q的路径直到他们交叉。 这就和两个链表查找公共结点类似了。

    struct BinaryTreeNode
    {
    	int nValue;
    	BinaryTreeNode* pLeft;
    	BinaryTreeNode* pRight;
    	BinaryTreeNode* pParent;
    };
    BinaryTreeNode * NearestCommonAncestor(BinaryTreeNode * root,BinaryTreeNode * p,BinaryTreeNode * q)  
    {  
        BinaryTreeNode* temp;  
             while(p!=NULL)  
        {  
            p=p->parent;  
            temp=q;  
            while(temp!=NULL)  
            {  
                if(p==temp->parent)  
                    return p;  
                temp=temp->parent;  
            }  
        }  
    }

    思路2 :如果p跟q都在某个结点的左侧,那就在该结点左侧分支中去寻找共同的祖先。直到它们不在同一侧了,你就找到了这个最低公共结点了。不难发现我们判断以一个结点为根的树是否含有某个结点时,需要遍历树的每个结点。接下来我们判断左子结点或者右结点为根的树中是否含有要找结点,仍然需要遍历。第二次遍历的操作其实在前面的第一次遍历都做过了。由于存在重复的遍历,本方法在时间效率上肯定不是最好的。

    思路3: 对于任意一个结点r,我们可知: 1) 如果p在一侧,q在另外一侧,那么r就是最近的共同的祖先。 2) 否则的话,最近的共同的祖先就在左侧或者右侧。 因此,我们可以给出一个叫左搜索-右搜索的递归算法来计算当前结点的左侧跟右侧分别有多少个结点(p或者q)。如果在某一侧只有2个结点,那么我们就需要 判断这个子结点是不是p或者q(因为这种情况下,当前结点就是最近的共同的祖先)。如果不是p或者q,那我们就得从子结点开始继续搜索。 如果需要寻找的结点(p或者q)在当前结点的右侧,此外另一个结点在另一侧。那么当前及诶点就是最近的共同的祖先。

    static int TWO_NODES_FOUND = 2;
    static int ONE_NODES_FOUND = 1;
    static int NO_NODES_FOUND = 0;
    
    int covers(BinaryTreeNode* root,BinaryTreeNode* p,BinaryTreeNode* q)
    {
    	int ret = NO_NODES_FOUND;
    	if(root == NULL) return ret;
    	if(root == p || root == q)
    		ret += 1;
    	ret += covers(root->pLeft,p,q);
    	if(ret == TWO_NODES_FOUND)
    		return ret;
    	return ret + covers(root->pRight,p,q);
    }
    
    BinaryTreeNode* commonAncestor(BinaryTreeNode* root,BinaryTreeNode* p,BinaryTreeNode* q)
    {
    	if(q==p&&(root->pLeft==q||root->pRight==q)) return root;
    	int nodesFromLeft = covers(root->pLeft,p,q);
    	if(nodesFromLeft == TWO_NODES_FOUND)
    	{
    		//p,q在同一侧
    		if(root->pLeft == p || root->pLeft == q)
    			return root->pLeft;
    		else
    			return commonAncestor(root->pLeft,p,q);
    	}
    	else if(nodesFromLeft == ONE_NODES_FOUND)
    	{
    		if(root==p) return p;
    		else if(root==q) return q;
    	}
    
    	int nodesFromRight = covers(root->pRight,p,q);
    	if(nodesFromRight == TWO_NODES_FOUND)
    	{
    		if(root->pRight == p || root->pRight == q)
    			return root->pRight;
    		else
    			return commonAncestor(root->pRight,p,q);
    	}
    	else if(nodesFromRight == ONE_NODES_FOUND)
    	{
    		if(root==p) return p;
    		else if(root==q) return q;
    	}
    	if(nodesFromLeft == ONE_NODES_FOUND&&nodesFromRight == ONE_NODES_FOUND)
    		return root;
    	else return NULL;
    }
  8. 二叉树的镜像
    题目:输出二叉树的镜像
    分析:前序遍历树的每个结点,若遍历到的结点有子结点,就交换它的两个子结点。(递归

    void Mirror(BinaryTreeNode *pNode)
    {
    	if((pNode == NULL) || (pNode->pLeft == NULL && pNode->pRight == NULL))
    		return;
    	BinaryTreeNode *pTemp = pNode->pLeft;
    	pNode->pLeft = pNode->pRight;
    	pNode->pRight = pTemp;
    
    	if(pNode->pLeft != NULL)
    		Mirror(pNode->pLeft);
    	if(pNode->pRight != NULL)
    		Mirror(pNode->pRight);
    }
  9. 构造二叉树
    题目:给定一个升序排列的数组,创建最小高度二叉树的算法。
    分析:如果可能,我们尝试建立这样一个二叉树,对于每一个结点,他左分支的结点总数与右分支的结点总数一致。
    1)插入数组中的中间数
    2)将中间数左边的元素插入到左分支
    3)将中间数右边的元素插入到右分支
    4)递归

    BinaryTreeNode* addToTree(int *arr,int start,int end)
    {
    	if(start>end)
    		return NULL;
    	int mid=(start+end)/2;
    	BinaryTreeNode* node = new BinaryTreeNode();
    	node->nValue = arr[mid];
    	node->pLeft = addToTree(arr,start,mid-1);
    	node->pRight = addToTree(arr,mid+1,end);
    	return node;
    }
  10. 二叉查找树某一层所有结点链表
    题目:对于一个二叉查找树,设计可以给出在任一深度所有结点的链表的算法,比如树的深度是D,就有D个链接。
    分析:我们可以做对树一层层的遍历,对树的初次遍历可以做轻微的修改。 在常见的初次遍历中,我们在遍历结点的时候不关心现在所在的层。在这个问题中,就有必要知道当前的层信息。因此我们使用一个虚拟结点来检测我们是否完成了本层并进行下一层访问。
  11. 寻找二叉搜索树中的下一个结点
    题目:寻找二叉树搜索树已知结点的下一个结点(例如,按中序遍历)。已知含有一个指向双亲的指针。
    分析:在中序遍历中,先访问左孩子结点X.left,然后双亲结点X,最后右孩子结点X.right.
    查找结点X的下一个结点的过程可以分为:
    1)如果X有右孩子,那么下一个结点肯定在X的右侧。而在这个分支上,需要首先访问最左边的子结点。
    2)否则我们访问X的父结点(P)
    a) 如果X是它的一个左子结点(P.left=X),那么P就是X的继承者。
    b) 如果X是它的一个右子结点(P.right=X),因此我们就调用successor(P),因为此时P已经完全遍历了。
  12. You have two large binary trees:T1,with millions of nodes,and T2,with hundreds of nodes.Create an algorithm to decide if T2 is a subtree of T1.
    Create a string representing the inorder and preorder traversals.If T2’s preorder traversal is a substring of T1’s preorder traversal,and T2’s inorder traversal is a substring of T1’s inorder traversal,then T2 is a substring of T1.
    We can check this using a suffix tree.We may hit memory limitations because suffix trees are extremely memroy intensive.If the become an issue,we can—
    treeMatch(……………..)

Leave a Reply

Your email address will not be published. Required fields are marked *