本篇文章部分参考轻松搞定面试中的二叉树题目实现。
所有源码:https://github.com/qzxin/BinaryTree/blob/master/binary-tree-all.cpp
1. 求二叉树中的节点个数
递归求解,二叉树的节点个数等于 左子树的个数+右子树的个数+1(根)
//求二叉树中的节点个数
int GetNums(BinaryTreeNode* root) {
if (root == NULL)
return 0;
return GetNums(root->m_pLeft) + GetNums(root->m_pRight) + 1;
}
求二叉树中叶子节点的个数
叶子节点的定义是:自身非空,左右为NULL;
和求二叉树中的节点个数类似,只是必须满足左右为NULL,才算1个
int GetLeafNodeNums(BinaryTreeNode* root) {
if (root == NULL)
return 0;
if (root->m_pLeft == NULL && root->m_pRight == NULL)
return 1;
int leftNums = GetLeafNodeNums(root->m_pLeft);
int rightNums = GetLeafNodeNums(root->m_pRight);
return leftNums+rightNums;
}
2. 求二叉树的深度
递归求解,二叉树的深度等于 左右子树深度中的最大值+1(根)
// 求二叉树的深度
int GetDepth(BinaryTreeNode* root) {
if (root == NULL)
return 0;
return max(GetDepth(root->m_pLeft), GetDepth(root->m_pRight)) + 1;
}
3. 求二叉树第K层的节点个数
递归实现
k = 1, nums = 1;
二叉树第K层的节点个数 = 左子树K-1层的节点个数 + 右子树K-1层的节点个数
// 求二叉树第K层的节点个数
int GetNLevelNums(BinaryTreeNode* root, int k) {
if (root == NULL || k == 0)
return 0;
if (k == 1)
return 1;
// 左右子树k-1层节点数的和
return GetNLevelNums(root->m_pLeft, k-1) + GetNLevelNums(root->m_pRight, k-1);
}
4 . 递归遍历:前序,中序,后序
// 递归遍历:前序遍历,中序遍历,后序遍历
void visit(BinaryTreeNode* root) {
cout << root->m_val << " ";
}
void PreOrderTravel(BinaryTreeNode* root) {
if (root == NULL)
return;
visit(root);
PreOrderTravel(root->m_pLeft);
PreOrderTravel(root->m_pRight);
}
void InOrderTravel(BinaryTreeNode* root) {
if (root == NULL)
return;
InOrderTravel(root->m_pLeft);
visit(root);
InOrderTravel(root->m_pRight);
}
void PostOrderTravel(BinaryTreeNode* root) {
if (root == NULL)
return;
PostOrderTravel(root->m_pLeft);
PostOrderTravel(root->m_pRight);
visit(root);
}
5. 非递归遍历:前序,中序,后序,层序
5.1 前序遍历
先访问根,然后压右节点,左节点进栈。(访问时顺序则是,根,左,右)
void PreOrderTravel(BinaryTreeNode* root) {
if (root == NULL)
return;
stack<BinaryTreeNode*> s;
s.push(root);
while (s.empty() == false) {
root = s.top();
visit(root);
s.pop();
if (root->m_pRight)
s.push(root->m_pRight);
if (root->m_pLeft)
s.push(root->m_pLeft);
}
}
5.2 中序遍历
先入栈后访问。因为中序首先要访问左节点,所以要压到树的最后一个左节点,才开始访问;最后一个左节点没有左节点(相当于根),之后开始遍历它的右子树。
void InOrderTravelRecur(BinaryTreeNode* root) {
if (root == NULL)
return;
stack<BinaryTreeNode*> s;
BinaryTreeNode* node = root;
while (node != NULL || s.empty() == false) {
if (node) {
s.push(node);
node = node->m_pLeft;
} else {
node = s.top();
s.pop();
visit(node);
node = node->m_pRight;
}
}
}
5.3 后序遍历
用2个栈实现,
// 辅助栈s1:压根,压左,压右;
// 栈s2: 压根,压右,压左;(出栈访问则为:左,右,根)
void PostOrderTravelRecur(BinaryTreeNode* root) {
if (root == NULL)
return;
stack<BinaryTreeNode*> s1;
stack<BinaryTreeNode*> s2;
BinaryTreeNode* node = root;
s1.push(node);
while (s1.empty() == false) {
node = s1.top();
s1.pop();
s2.push(node); //根压入s2
if (node->m_pLeft)
s1.push(node->m_pLeft);// 左 压入 s1
if (node->m_pRight)
s1.push(node->m_pRight); // 右 压入 s1
}
while (s2.empty() == false) {
node = s2.top();
s2.pop();
visit(node);
}
}
5.4 层序遍历
逐层访问,先入先出,使用队列实现。访问根,然后将左、右入队列
void LevelOrderTravel(BinaryTreeNode* root) {
if (root == NULL)
return;
queue<BinaryTreeNode*> q;
q.push(root);
while (q.empty() == false) {
root = q.front();
visit(root);
q.pop();
if (root->m_pLeft)
q.push(root->m_pLeft);
if (root->m_pRight)
q.push(root->m_pRight);
}
}
6. 将二叉查找树变为有序的双向链表
递归实现,二叉树可以分为左子树、根、右子树
设双向链表的头尾是pFirst, pLast
- 根为空: pFirst, pLast为NULL
- 处理左子树
- 左子树空:则根为双向链表的头部
- 左子树非空:左子树双向链表的头,是最终双向链表的头;左子树双向链表的尾和根相连;
- 处理右子树
- 右子树空:根为双向链表的尾部
- 右子树非空: 右子树双向链表的尾,是最终双向链表的尾;右子树双向链表的头和根相连;
因此,除了声明最终双向链表的头尾,还要声明左、右子树转换成双向链表的头尾。
// 将二叉查找树变为有序的双向链表
void CovertToList(BinaryTreeNode* root, BinaryTreeNode* &pFirst, BinaryTreeNode* &pLast) {
BinaryTreeNode* pLeftFirst(NULL), *pLeftLast(NULL), *pRightFirst(NULL), *pRightLast(NULL);
if (root == NULL) {
pFirst = NULL;
pLast = NULL;
return;
}
if (root->m_pLeft == NULL) {
pFirst = root;
} else {
CovertToList(root->m_pLeft, pLeftFirst, pLeftLast);
pLeftLast->m_pRight = root;
root->m_pLeft = pLeftLast;
pFirst = pLeftFirst;
}
if (root->m_pRight == NULL) {
pLast = root;
} else {
CovertToList(root->m_pRight, pRightFirst, pRightLast);
pRightFirst->m_pLeft = root;
root->m_pRight = pRightFirst;
pLast = pRightFirst;
}
}
7. 求二叉树的镜像
递归的交换树的左右子树。
// 求二叉树的镜像
BinaryTreeNode* MirrorTree(BinaryTreeNode* root) {
if (root == NULL)
return NULL;
BinaryTreeNode* pLeft = MirrorTree(root->m_pLeft);
BinaryTreeNode* pRight = MirrorTree(root->m_pRight);
root->m_pLeft = pRight;
root->m_pRight = pLeft;
return root;
}
8. 判断两棵二叉树是否结构相同
递归实现:先判断根是否相同,再判断左子树和右子树是否相同
// 判断两棵二叉树是否结构相同
bool TreeStructCmp(BinaryTreeNode* root1, BinaryTreeNode* root2) {
if (root1 == NULL && root2 == NULL)
return true;
if (root1 != root2)
return false;
bool leftResult = TreeStructCmp(root1->m_pLeft, root2->m_pLeft);
bool rightResult = TreeStructCmp(root1->m_pRight, root2->m_pRight);
return leftResult && rightResult;
}
9. 判断二叉树是不是平衡二叉树
只有每一个子树都是平衡树,才能保证它是平衡二叉树。因此,先判断左右子树是否是平衡的并记录左右子树的深度,再判断树是否平衡。
bool IsAVL(BinaryTreeNode* root) {
int depth;
return IsAVLTree(root, depth);
}
// 后序遍历,只访问1次节点
bool IsAVLTree(BinaryTreeNode* root, int &depth) {
if (root == NULL) {
depth = 0;
return true;
}
int leftDepth, rightDepth;
// 后序遍历,先判断左右子树是否是平衡的,再判断树是否平衡
if (IsAVLTree(root->m_pLeft, leftDepth) && IsAVLTree(root->m_pRight, rightDepth)) {
if (abs(leftDepth-rightDepth) <= 1) {
depth = max(leftDepth, rightDepth)+1;
return true;
}
}
return false;
}
10. 判断二叉树是否是搜索二叉树
注意:仅通过比较左节点 <= 根 <= 右节点 不能判断这是一个搜索二叉树,因为这不能保证右子树的所有节点值都大于左子树中的所有节点。如下图所示,它不是BST
4
1 5
0 6
通过中序遍历(从小到大的顺序)来判断是否是一个搜索二叉树。
// 判断二叉树是否是搜索二叉树
bool IsBST(BinaryTreeNode* root) {
int prev = INT_MIN; // 比较元素,设为最小
return IsBSTreeHelper(root, prev);
}
// 中序遍历,判断遍历顺序是否是从小到大
bool IsBSTreeHelper(BinaryTreeNode* root, int &prev) {
if (root == NULL)
return true;
if (IsBSTreeHelper(root->m_pLeft, prev)) {
if (root->m_val >= prev) {
prev = root->m_val;
if (IsBSTreeHelper(root->m_pRight, prev))
return true; // 此时左子树、根、右子树都满足要求
else
return false; // 右不满足
} else {
return false; // 根不满足
}
} else {
return false; // 左不满足
}
}
11. 判断二叉树是不是完全二叉树
完全二叉树,要求在最后一层的右侧才可以存在空节点。
层序遍历,一旦一个节点含有空子树后,之后所有的节点必须只含有NULL。
- 不含有空子树时,按层序将当前节点的左右子树压入队列
- 一旦含有空子树时,当前节点和队列中后续节点的左右子树,必须为空,才是完全二叉树。
/ 判断二叉树是不是完全二叉树
// 层序遍历,一旦一个节点含有空子树后,之后所有的节点必须只含有NULL
bool IsCompleteBinaryTree(BinaryTreeNode* root) {
if (root == NULL)
return true;
queue<BinaryTreeNode* > q;
bool HaveNULL = false;
q.push(root);
while (q.empty() == false) {
root = q.front();
q.pop();
if (HaveNULL) {
if (root->m_pLeft || root->m_pRight)
return false;
} else {
if (root->m_pLeft && root->m_pRight) {
// 左右非空
q.push(root->m_pLeft);
q.push(root->m_pRight);
} else if (root->m_pLeft && root->m_pRight == NULL) {
// 左非空,右空
HaveNULL = true;
q.push(root->m_pLeft);
} else if (root->m_pLeft == NULL && root->m_pRight) {
// 左空,右非空
return false;
} else {
// 左右为空
HaveNULL = true;
}
}
}
return true;
}
12. 求二叉树中两个节点的最低公共祖先节点
递归解法:
(1)如果两个节点,1个在左子树,1个在右子树,则最低公共祖先节点是根
(2)如果两个节点都在左子树,递归处理左子树;反之处理右子树
在树中寻找 node1 ,并保存路径,如果没找到node1,递归把路径的节点pop;
在树中寻找 node2 ,并保存路径,如果没找到node1,递归把路径的节点pop;
如果有1个没有找到,则返回NULL,如果都找到,则从2条路径的开始,向后遍历,找到最后一个共同的节点。
// 求二叉树中两个节点的最低公共祖先节点
BinaryTreeNode* GetLastCommonParent(BinaryTreeNode* root, BinaryTreeNode* node1, BinaryTreeNode* node2) {
if (root == NULL || node1 == NULL || node2 == NULL)
return NULL;
list<BinaryTreeNode*> path1, path2;
bool found1 = GetPath(root, node1, path1);
bool found2 = GetPath(root, node2, path2);
BinaryTreeNode* pLast = NULL;
if (!found1 || !found2)
return NULL;
// 找到路径中最后一个共同节点
list<BinaryTreeNode*>::const_iterator iter1 = path1.begin();
list<BinaryTreeNode*>::const_iterator iter2 = path2.begin();
while (iter1 != path1.end() && iter2 != path2.end()) {
if (*iter1 != *iter2)
break;
pLast = *iter1;
iter1++;
iter2++;
}
return pLast;
}
// 获得根到该节点的路径
bool GetPath(BinaryTreeNode* root, BinaryTreeNode* node, list<BinaryTreeNode*> &path) {
if (root == node) {
path.push_back(root);
return true;
}
if (root == NULL)
return false;
bool found = false;
path.push_back(root); // 路径从根开始
found = GetPath(root->m_pLeft, node, path); //左子树中查找
if (!found)
found = GetPath(root->m_pRight, node, path); // 右子树中查找
if (!found)
path.pop_back(); // 此子树中没找到,pop出已经压入的此子树的节点
return found;
}
13. 求二叉树中节点的最大距离
二叉树中节点的最大距离maxDistance指,任意两个节点相连的路径长度的最大值。
- 树为空,距离为0;
- 树非空,最大距离有3种可能,左子树中的最大距离,右子树中的最大距离,从左子树到根然后到右子树的最大距离
int GetMaxDistance(BinaryTreeNode* root) {
int maxLeft = 0, maxRight = 0; // 左右子树到根的最大距离
return GetMaxDistRecur(root, maxLeft, maxRight); // 在计算最大距离时,记录左右子树到根的最大距离
}
//maxLeft, maxRight 左右子树中的节点到根的最大距离
int GetMaxDistRecur(BinaryTreeNode* root, int &maxLeft, int &maxRight) {
if (root == NULL) {
maxLeft = 0, maxRight = 0;
return 0;
}
int maxLL, maxLR, maxRL, maxRR;
int maxDistLeft, maxDistRight; // 左、右子树中的最大距离
if (root->m_pLeft) {
maxDistLeft = GetMaxDistRecur(root->m_pLeft, maxLL, maxLR);
maxLeft = max(maxLL, maxRR) + 1;
} else {
maxDistLeft = 0;
maxLeft = 0;
}
if (root->m_pRight) {
maxDistRight = GetMaxDistRecur(root->m_pRight, maxRL, maxRR);
maxRight = max(maxRL, maxRR) + 1;
} else {
maxDistRight = 0;
maxRight = 0;
}
return max(maxLeft+maxRight, max(maxDistLeft, maxDistRight));
}
14. 由前序遍历序列和中序遍历序列重建二叉树
前序的开始是根;中序的一个节点的左侧是左子树,右侧是右子树。
因此先从前序中确定1个根,然后去中序中找到这个根,确定属于该根的左右子树的范围。递归的处理左右子树。
// 由前序遍历序列和中序遍历序列重建二叉树
BinaryTreeNode* RebuildBinaryTree(int* preorder, int* inorder, int nums) {
if (preorder == NULL || inorder == NULL || nums <= 0)
return NULL;
BinaryTreeNode* root = new BinaryTreeNode(preorder[0]); // 根
int rootPositionOnInorder = -1;
for (int i = 0; i < nums; i++) {
if (inorder[i] == root->m_val) {
rootPositionOnInorder = i; // 中序中寻找根
break;
}
}
if (rootPositionOnInorder == -1) {
cout << "Input Error." << endl;
}
// Rebuild Left Tree
root->m_pLeft = RebuildBinaryTree(preorder+1, inorder, rootPositionOnInorder);
// Rebuild Right Tree
root->m_pRight = RebuildBinaryTree(preorder+1+rootPositionOnInorder, inorder+1+rootPositionOnInorder, nums-rootPositionOnInorder-1);
return root;
}