题目1:输入一个整数和一棵二叉树。从树的根节点开始往下访问一直到叶节点所经过的所有节点形成一条路径。打印出和输入整数相等的所有路径。
思路:当访问到某一节点时,把该节点添加到路径上,并累加当前节点的值。如果当前节点为叶节点并且当前路径的和刚好等于输入的整数,则当前的路径符合要求,我们把它打印出来。如果当前节点不是叶节点,则继续访问它的子节点。当前节点访问结束后,递归函数将自动回到父节点。因此我们在函数退出之前要在路径上删除当前节点并减去当前节点的值,以确保返回父节点时路径刚好是根节点到父节点的路径。
void FindAllPath(BTreeNode *pRoot, int expectedNum, vector<int>& path, int currentSum) { if (pRoot == NULL) { return; } currentSum += pRoot->m_nValue; path.push_back(pRoot->m_nValue); bool isLeaf = (pRoot->m_pLeft==NULL) && (pRoot->m_pRight==NULL); if (isLeaf && (currentSum==expectedNum)) { for (int i=0; i<path.size(); i++) { cout << path[i] << " "; } cout << endl; } //剪枝优化 if(currentSum > expectedNum) { if (pRoot->m_pLeft) { FindAllPath(pRoot->m_pLeft, expectedNum, path, currentSum); } if (pRoot->m_pRight) { FindAllPath(pRoot->m_pRight, expectedNum, path, currentSum); } } currentSum -= pRoot->m_nValue; path.pop_back(); }
变种1:二叉树是一种特殊的二叉树:查找二叉树(位于左子树上的结点都比父结点小,而位于右子树上的结点都比父结点大)。此时我们只需要从根节点开始和两个结点进行比较,如果当前结点的值比两个结点都大,则最低的共同父结点一定在当前结点的左子树中。如果当前结点的值比两个结点都小,则最低的共同父结点一定在当前结点的右子树中。如果当前结点的值大于一个结点的值而小于另外一个结点的值,则当前节点就是所要查找的最低共同父结点;
BSTreeNode* GetLastCommonParent(BSTreeNode* pRoot, BSTreeNode* pNode1, BSTreeNode* pNode2) { if(pRoot==NULL || pNode1==NULL || pNode2==NULL) return NULL; if(pRoot->m_nValue>pNode1->m_nValue && pRoot->m_nValue>pNode2->m_nValue) { if(pRoot->m_pLeft->m_nValue==pNode1->m_nValue || pRoot->m_pLeft->->m_nValue==pNode2->m_nValue) return pRoot; return GetLastCommonParent(pRoot->m_pLeft, pNode1, pNode2); } if(pRoot->m_nValue<pNode1->m_nValue && pRoot->m_nValue<pNode2->m_nValue) { if(pRoot->m_pRight->m_nValue==pNode1->m_nValue || pRoot->m_pRight->->m_nValue==pNode2->m_nValue) return pRoot; return GetLastCommonParent(pRoot->m_pRight, pNode1, pNode2); } if((pRoot->m_nValue<pNode1->m_nValue && pRoot->m_nValue>pNode2->m_nValue) || (pRoot->m_nValue>pNode1->m_nValue && pRoot->m_nValue<pNode2->m_nValue)) return pRoot; return NULL; }
思路1:首先判断一个结点的子树中是不是包含了另外一个结点,然后从根节点开始,判断当前结点为根的树中左右子树是不是包含我们要找的两个结点,如果两个结点都出现在它的左子树中,那最低的共同父结点也出现在它的左子树中。如果两个节点都出现在它的右子树中,那最低的共同父结点也出现在它的右子树中。如果两个结点一个出现在左子树中,一个出现在右子树中,那当前的结点就是最低的共同父结点。
<span style="color:#000000;">bool HasNode(BTreeNode* pRoot, BTreeNode* pNode) { assert(pRoot!=NULL && pNode!=NULL); if(pRoot == pNode) return true; bool bHas = false; if(pRoot->m_pLeft != NULL) bHas = HasNode(pRoot->m_pLeft, pNode); if(!bHas && pRoot->m_pRigth!=NULL) bHas = HasNode(pRoot->m_pRight, pNode); return bHas; } BTreeNode* GetLastCommonParent(BTreeNode* pRoot, BTreeNode* pNode1, BTreeNode* pNode2) { if(pRoot==NULL || pNode1==NULL || pNode2==NULL) return NULL; bool bLeftHasNode1 = false; bool bLeftHasNode2 = false; if(pRoot->m_pLeft != NULL) { bLeftHasNode1 = HasNode(pRoot->m_pLeft, pNode1); bLeftHasNode2 = HasNode(pRoot->m_pLeft, pNode2); } if(bLeftHasNode1 && bLeftHasNode2) { if(pRoot->m_pLeft==pNode1 || pRoot->m_pLeft==pNode2) return pRoot; return GetLastCommonParent(pRoot->m_pLeft, pNode1, pNode2); } bool bRightHasNode1 = false; bool bRightHasNode2 = false; if(pRoot->m_pRight != NULL) { bRightHasNode1 = HasNode(pRoot->m_pRight, pNode1); bRightHasNode2 = HasNode(pRoot->m_pRight, pNode2); } if(bRightHasNode1 && bRightHasNode2) { if(pRoot->m_pRight==pNode1 ||pRoot->m_pRight==pNode2) return pRoot; return GetLastCommonParent(pRoot->m_pLeft, pNode1, pNode2); } if((bLeftHasNode1 & bRightHasNode2) || (bLeftHasNode2 & bRightHasNode1)) return pRoot; return NULL; }
注意:思路1存在重复遍历,故时间复杂度为O(n2);
变种2:树不一定是二叉树,每个结点都有一个指针指向它的父结点。于是我们可以从任何一个结点出发,得到一个到达树根结点的单向链表,然后这个问题就转化成了求两个单向链表的第一个公共结点。
思路2:为两个结点分别得到一个由根结点开始的路径(以链表形式保存),求这两个路径的最后一个公共结点即可;
bool GetNodePath(BTreeNode* pRoot, BTreeNode* pNode, list<BTreeNode*>& path) { if(pRoot == pNode) return true; path.push_back(pRoot); bool bFound = false; if(pRoot->m_pLeft != NULL) bFound = GetNodePath(pRoot->m_pLeft, pNode, path); if(!bFound && pRoot->m_pRight!=NULL) bFound = GetNodePath(pRoot->m_pRight, pNode, path); if(!bFound) path.pop_back(); return bFound; } BTreeNode* GetLastCommonNode(list<BTreeNode*>& path1, list<BTreeNode*>& path2) { list<BTreeNode*>::iterator it1 = path1.begin(); list<BTreeNode*>::iterator it2 = path2.begin(); BTreeNode* pNode = NULL; for(; (it1!=path1.end())&&(it2!=path2.end()); it1++, it2++) { if(*it1 = *it2) pNode = *it1; else return pNode; } return NULL; } BTreeNode* GetLastCommonParent(BTreeNode* pRoot, BTreeNode* pNode1, BTreeNode* pNode2) { if(pRoot==NULL || pNode1==NULL || pNode2==NULL) return NULL; list<BTreeNode*> path1; GetNodePath(pRoot, pNode1, path1); list<BTreeNode*> path2; GetNodePath(pRoot, pNode2, path2); return GetLastCommonNode(path1, path2); }
注意:思路2代码实现的时间复杂度为O(n),但由于使用了两个链表来保存路径,故其空间复杂度为O(logn);
题目3:求一个二叉树任意两个节点之间的距离
思路:为两个节点分别保存一个从根节点开始的路径(链表形式保存),找到这两个路径的最后一个公共结点,计算出两个节点到此公共结点的距离之和即可。
bool GetNodePath(BTreeNode* pRoot, BTreeNode* pNode, list<BTreeNode*>& path) { path.push_back(pRoot); if(pRoot == pNode) return true; bool bFound = false; if(pRoot->m_pLeft != NULL) bFound = GetNodePath(pRoot->m_pLeft, pNode, path); if(!bFound && pRoot->m_pRight!=NULL) bFound = GetNodePath(pRoot->m_pRight, pNode, path); if(!bFound) path.pop_back(); return bFound; } int GetLastCommonNodeIndex(list<BTreeNode*>& path1, list<BTreeNode*>& path2) { list<BTreeNode*>::iterator it1 = path1.begin(); list<BTreeNode*>::iterator it2 = path2.begin(); int index = -1; for(; (it1!=path1.end())&&(it2!=path2.end()); it1++, it2++) { if(*it1 = *it2) index++; } return index; } int GetDistanceOfTwoNodes(BTreeNode* pRoot, BTreeNode* pNode1, BTreeNode* pNode2) { if(pRoot==NULL || pNode1==NULL || pNode2==NULL) return NULL; list<BTreeNode*> path1; GetNodePath(pRoot, pNode1, path1); list<BTreeNode*> path2; GetNodePath(pRoot, pNode2, path2); int index = GetLastCommonNodeIndex(path1, path2); int dist = 0; if(index != -1) dist = path1.size()-index-1+path2.size()-index-1; return dist; }
思路:距离最远的两点必然在以某个结点为根的子树上,它们间的路径必然经过该子树的根节点,因而,以任意一个节点B为根的子树,计算出经过该子树结点B的最大距离,则所有最大距离的最大值就是所要求的二叉树的最大距离,即“树的直径”。而经过数的根节点的最大距离为:左子树的高度+右子树的高度(假设空节点的高度为-1,根节点高度为0)。
int GetMaxDistace(BTreeNode* pRoot, int& maxDist) { if(pRoot == NULL) return -1; int leftHeight = GetMaxDistance(pRoot->m_pLeft, maxDist); int rightHeight = GetMaxDistance(pRoot->m_pRight, maxDist); int dist = leftHeight + rightHeight; if(dist > maxDist) maxDist = dist; return leftHeight>rightHeight?leftHeight+1:rightHeight+1; }
思路:二叉树的深度等于其左右子树深度的较大值加1,注意如果一个树只有一个节点的话,其深度为1(高度为0!)。
int GetDepth(BTreeNode* pRoot) { if(pRoot == NULL) return 0; int leftDepth = GetDepth(pRoot->m_pLeft); int rightDepth = GetDepth(pRoot->m_pRight); return leftDepth>rightDepth?leftDepth+1:rightDepth+1; }