【持续更新】面试中的二叉树
目录
树是数据结构中的重中之重,尤其以各类二叉树为学习的难点。一直以来,对于树的掌握都是模棱两可的状态,现在希望通过写一个关于二叉树的专题系列。在学习与总结的同时更加深入的了解掌握二叉树。本系列文章将着重介绍一般二叉树、完全二叉树、满二叉树、线索二叉树、霍夫曼树、二叉排序树、平衡二叉树、红黑树、B树。
//定义二叉树结构体
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
1、二叉树中节点数
思路:使用递归,左子树节点数 + 右子树节点数 + 1。
class solution_Tree {
public:
int getNodeNum(TreeNode* root) {
if (!root)return 0;
int left = getNodeNum(root->left);
int right = getNodeNum(root->right);
return right + left + 1;
}
};
2、统计二叉树叶子节点数
class solution_Tree {
public:
int getLeafNodeNum(TreeNode* root) {
if (!root)return 0;
if (!root->left && !root->right)
return 1;
return getLeafNodeNum(root->left) + getLeafNodeNum(root->right);
}
};
3、二叉树的深度
class solution_Tree {
public:
int depthOfTree(TreeNode* root) {
if (!root)return 0;
int left = depthOfTree(root->left);
int right = depthOfTree(root->right);
return (right < left ? left : right) + 1;
}
};
4、二叉树的深度优先遍历
1)前序遍历,遍历顺序:中左右.
class solution_Tree {
public:
void preTraversal(TreeNode* root) {
if (!root)return;
cout << root->val << " ";
preTraversal(root->left);
preTraversal(root->right);
}
};
2)中序遍历,遍历顺序:左中右.
class solution_Tree {
public:
void preorder(TreeNode* root) {
if (!root)return;
cout << root->val << " ";
preorder(root->left);
preorder(root->right);
}
};
3)后序遍历,遍历顺序:左右中.
class solution_Tree {
public:
void postorder(TreeNode* root) {
if (!root)return;
postorder(root->left);
postorder(root->right);
cout << root->val << " ";
}
};
5、二叉树广度优先遍历(层次遍历)
思路:按层次从上到下,从左到右遍历,可使用队列先进先出的特性.
class solution_Tree {
public:
void breadthFirst(TreeNode* root) {
if (!root)return;
queue<TreeNode*> q;
q.push(root);
while (!q.empty())
{
TreeNode* p = q.front();
cout << p->val << " ";
q.pop();
if (p->left)q.push(p->left);
if (p->right)q.push(p->right);
}
return;
}
};
6、判断二叉树是否平衡
思路:平衡二叉树定义,任意节点左右子数高度不能相差超过1。所以我们肯定要用到求树的高度的函数,但这个函数由于也需要用到递归,而且每个节点调用一次会很浪费时间。所以我们用递归在求高度的同时判断树是否平衡,如果平衡则返回子树高度给上一层,如果不平衡则直接返回-1。最终返回值如果是-1,则树是不平衡的,如果不是-1,则树是平衡的。
使用递归方法,依次判断每个节点左右子树是否平衡.
class solution_Tree {
bool isBalence(TreeNode* root, int &deep) {
if (!root)return true;
int left = 0, right = 0;
if (isBalence(root->left, left) && isBalence(root->right, right)) {
if ((left - right) > 1)return false;
deep = (right > left ? right : left) + 1;
return true;
}
return false;
}
public:
bool is_balance(TreeNode* root) {
int deep = 0;
return isBalence(root, deep);
}
};
7、二叉树的镜像
二叉树的镜像定义:源二叉树 8 / \ 6 10 / \ / \ 5 7 9 11 镜像二叉树 8 / \ 10 6 / \ / \ 11 9 7 5
思路:先前序遍历这棵树的每个结点,如果遍历到的结点有子结点,就交换它的两个子节点,当交换完所有的非叶子结点的左右子结点之后,就得到了树的镜像.
class solution_Tree {
public:
void Mirror(TreeNode *root) {
if (!root)return;
TreeNode *tmp = root->left;
root->left = root->right;
root->right = tmp;
Mirror(root->left);
Mirror(root->right);
}
};
非递归写法
class solution_Tree {
public:
void Mirror(TreeNode *pRoot) {
if (pRoot == NULL)
return;
stack<TreeNode*> stackNode;
stackNode.push(pRoot);
while (stackNode.size()) {
TreeNode* tree = stackNode.top();
stackNode.pop();
if (tree->left != NULL || tree->right != NULL) {
TreeNode *ptemp = tree->left;
tree->left = tree->right;
tree->right = ptemp;
}
if (tree->left)
stackNode.push(tree->left);
if (tree->right)
stackNode.push(tree->right);
}
}
};
8、二叉树最右节点
思路:很明显使用广度优先遍历,关键在于如何标记最右端节点。按层遍历常见的写法是将节点放入队列,出列后再将其子节点放入队列,实质上是一层一层放入队列,并且队列中最多只能有相邻的两层节点。那么我们可以用两个队列来存储,将上层节点放入队列一,出队后将其子节点(即下层节点)放入队列二,两个队列交替进行。
class solution_Tree {
public:
void getRightNode(TreeNode* root) {
queue<TreeNode*> q1, q2;
TreeNode *tmp;
if (root == NULL) {
return;
}
q1.push(root);
//只要两队列都不为空
while (!(q1.empty() && q2.empty())) {
while (!q1.empty()) {
tmp = q1.front();
q1.pop();
if (tmp->left != NULL) {
q2.push(tmp->left);
}
if (tmp->right != NULL) {
q2.push(tmp->right);
}
if (q1.empty()) {
cout << tmp->val << endl;
}
}
while (!q2.empty()) {
tmp = q2.front();
q2.pop();
if (tmp->left != NULL) {
q1.push(tmp->left);
}
if (tmp->right != NULL) {
q1.push(tmp->right);
}
if (q2.empty()) {
cout << tmp->val << endl;
}
}
}
}
};
9、重建二叉树(已知前序遍历和中序遍历序列)
思路:1)先求出根节点(前序序列第一个元素)。
2)将根节点带入到中序遍历序列中求出左右子树的中序遍历序列。
3)通过左右子树的中序序列元素集合带入前序遍历序列可以求出左右子树的前序序列。
4)左右子树的前序序列第一个元素分别是根节点的左右儿子
5)求出了左右子树的4种序列可以递归上述步骤
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre, vector<int> in) {
//判定递归终止条件;
if (pre.size() == 0 || in.size() == 0) return NULL;
//定义Node节点并其求根节点;
int root = pre[0];
TreeNode* node = new TreeNode(root);
vector<int>::iterator it;
//1.求左右子树的遍历序列;
vector<int> preLeft, preRight, inLeft, inRight;
//(1).求根节点在中序遍历序列中的位置;
vector<int>::iterator i;
for (it = in.begin(); it != in.end(); it++)
if (root == *it) i = it;
//(2).求左右子树的中序遍历子序列;
int k = 0;
for (it = in.begin(); it != in.end(); it++) {
if (k == 0) inLeft.push_back(*it);
else if (k == 1) inRight.push_back(*it);
else {}
if (it == i) k = 1;
}
//(3).求左右子树的前序遍历子序列;
k = 0;
vector<int>::iterator ite;
for (it = pre.begin() + 1; it != pre.end(); it++) {
for (ite = inLeft.begin(); ite != inLeft.end(); ite++) {
if (*it == *ite) {
preLeft.push_back(*it);
k = 1;
}
}
if (k == 0) preRight.push_back(*it);
k = 0;
}
//根据遍历序列求出跟的左右节点;
node->left = reConstructBinaryTree(preLeft, inLeft);
node->right = reConstructBinaryTree(preRight, inRight);
//返回节点地址;
return node;
}
};
10、判断两颗二叉树是否相等
思路:1)如果两棵二叉树都为空,返回真
2)如果两棵二叉树一棵为空,另一棵不为空,返回假
3)如果两棵二叉树都不为空,如果对应的左子树和右子树都同构返回真,其他返回假
class Solution {
public:
bool StructureCmp(TreeNode * pRoot1, TreeNode * pRoot2)
{
if (pRoot1 == NULL && pRoot2 == NULL) // 都为空,返回真
return true;
else if (pRoot1 == NULL || pRoot2 == NULL) // 有一个为空,一个不为空,返回假
return false;
bool resultLeft = StructureCmp(pRoot1->left, pRoot2->left); // 比较对应左子树
bool resultRight = StructureCmp(pRoot1->right, pRoot2->right); // 比较对应右子树
return (resultLeft && resultRight);
}
};
11、输出二叉树第k层节点数
思路:递归,左子树节点数+右子树节点数;
int GetNodeNumKthLevel(TreeNode *pRoot, int k){
if (pRoot == NULL || k < 1) return 0;
else if (k == 1) return 1;
return GetNodeNumKthLevel(pRoot->left, k - 1)+GetNodeNumKthLevel(pRoot->right, k - 1);
}
12、按层换行输出二叉树各节点的value
思路:广度优先遍历,使用一个辅助的双端队列。
void Print(TreeNode* pRoot) {
TreeNode* pRight = pRoot;
deque<TreeNode*> q;
if (pRoot)q.push_back(pRoot);
else return;
while (!q.empty()) {
TreeNode* tmp = q.front();
cout << tmp->val << " ";
q.pop_front();
if (tmp->left)q.push_back(tmp->left);
if (tmp->right)q.push_back(tmp->right);
if (tmp == pRight) {
if (pRight->right)pRight = pRight->right;
else if (pRight->left)pRight = pRight->left;
else if(!q.empty())pRight = q.back();
cout << endl;
}
}
}