一. 二叉树定义

二叉树具有天然的递归特性,凡是二叉树相关题,首先应该联想到递归

struct BinTreeNode {
BinTreeNode* left;
BinTreeNode* right;
int val;
BinTreeNode(int value) : left(nullptr), right(nullptr), val(value) { }
};

二. 二叉树遍历

详见笔者博文:二叉树遍历大总结

#include <iostream>
#include <vector>
#include <stack>
using namespace std; // 二叉树递归定义
struct BinTreeNode {
BinTreeNode* left;
BinTreeNode* right;
int val;
BinTreeNode(int value) : left(NULL), right(NULL), val(value) { }
}; // 根据结点值构建二叉树
BinTreeNode* BuildBinTree(BinTreeNode* root, int value) {
if (root == NULL) {
root = new BinTreeNode(value); // 结点为空,new一个节点
} else if (value <= root->val) {
root->left = BuildBinTree(root->left, value);
} else {
root->right = BuildBinTree(root->right, value);
}
return root;
} // 先序遍历,递归
void PreOrder(BinTreeNode* root) {
if (root == NULL) return;
std::cout << root->val << "-->"; // 访问节点
PreOrder(root->left);
PreOrder(root->right);
} // 先序遍历,非递归,栈版本
void PreOrderStk(BinTreeNode* root) {
if (root == NULL) return;
std::stack<BinTreeNode*> stk;
while (root != NULL || !stk.empty()) {
while (root != NULL) {
std::cout << root->val << "-->"; // 访问节点
stk.push(root);
root = root->left; // 深度优先搜索到最左节点,然后回溯
}
BinTreeNode* cur = stk.top();
stk.pop();
root = cur->right; // 转向右子树
}
} // 先序遍历,非递归,非栈,Morris
void PreOrderMorris(BinTreeNode* root) {
if (root == NULL) return;
BinTreeNode* prev = NULL;
BinTreeNode* cur = root; // cur为正在访问的结点
while (cur != NULL) {
if (cur->left == NULL) {
std::cout << cur->val << "-->"; // 访问节点
cur = cur->right;
} else {
prev = cur->left; // 前驱结点为 左子树最右下节点
while (prev->right != NULL && prev->right != cur) {
prev = prev->right;
}
if (prev->right == NULL) { // 还没线索化,则建立线索
std::cout << cur->val << "-->"; // 仅这一行与中序不同
prev->right = cur; // 建立线索
cur = cur->left; // 转向处理左子树
} else {
cur = cur->right; // 转向处理右子树
prev->right = NULL; // 已经线索化,则删除线索
}
}
}
} // 中序遍历,递归
void InOrder(BinTreeNode* root) {
if (root == NULL) return;
InOrder(root->left);
std::cout << root->val << "-->"; // 访问节点
InOrder(root->right);
} // 中序遍历,非递归,栈版本
void InOrderStk(BinTreeNode* root) {
if (root == NULL) return;
std::stack<BinTreeNode*> stk;
while (root != NULL || !stk.empty()) {
while (root != NULL) {
stk.push(root);
root = root->left; // 深度优先搜索到最左节点,然后回溯
}
BinTreeNode* cur = stk.top();
stk.pop();
std::cout << cur->val << "-->"; // 访问节点
root = cur->right; // 转向右子树
}
} // 中序遍历,非递归,非栈,Morris
void InOrderMorris(BinTreeNode* root) {
if (root == NULL) return;
BinTreeNode* prev = NULL;
BinTreeNode* cur = root; // cur为正在访问的结点
while (cur != NULL) {
if (cur->left == NULL) {
std::cout << cur->val << "-->"; // 访问节点
cur = cur->right;
} else {
prev = cur->left; // 前驱结点为 左子树最右下节点
while (prev->right != NULL && prev->right != cur) {
prev = prev->right;
}
if (prev->right == NULL) { // 还没线索化,则建立线索
prev->right = cur; // 建立线索
cur = cur->left; // 转向处理左子树
} else {
std::cout << cur->val << "-->"; // 已经线索化,则访问节点,并删除线索
cur = cur->right; // 转向处理右子树
prev->right = NULL; // 删除线索
}
}
}
} // 后序遍历,递归
void PostOrder(BinTreeNode* root) {
if (root == NULL) return;
PostOrder(root->left);
PostOrder(root->right);
std::cout << root->val << "-->";
} // 后序遍历,非递归,栈版本
void PostOrderStk(BinTreeNode* root) {
if (root == NULL) return;
std::stack<BinTreeNode*> stk;
BinTreeNode* cur = root;
BinTreeNode* prev = NULL;
do {
while (cur != NULL) {
stk.push(cur);
cur = cur->left;
}
prev = NULL;
while (!stk.empty()) {
cur = stk.top();
stk.pop();
if (cur->right == prev) { // 从右子树返回,表示其右子树已经访问完毕
std::cout << cur->val << "-->";
prev = cur;
} else {
stk.push(cur);
cur = cur->right; // 其右子树还未访问,转向其右子树访问
break;
}
}
} while (!stk.empty());
} // 层次遍历,递归版
void LevelOrder(BinTreeNode* root, int level, std::vector<std::vector<int>>& res) {
if (root == NULL) return;
if (level > res.size()) {
res.push_back(std::vector<int>());
}
res.at(level - ).push_back(root->val);
LevelOrder(root->left, level + , res);
LevelOrder(root->right, level + , res);
}
std::vector<std::vector<int>> LevelOrderHelp(BinTreeNode* root) {
std::vector<std::vector<int>> res;
LevelOrder(root, , res);
return res;
} // 层次遍历,非递归,队列版
void LevelOrderQueue(BinTreeNode* root) {
if (root == NULL) return;
std::deque<BinTreeNode*> dequeTreeNode;
dequeTreeNode.push_back(root);
while (!dequeTreeNode.empty()) {
BinTreeNode* cur = dequeTreeNode.front();
dequeTreeNode.pop_front();
std::cout << cur->val << "-->"; // 访问节点
if (cur->left != NULL) dequeTreeNode.push_back(cur->left);
if (cur->right != NULL) dequeTreeNode.push_back(cur->right);
}
} int main() {
// your code goes here
std::vector<int> num = {, , , , , , , , };
BinTreeNode* root = nullptr;
for (auto val : num) {
root = BuildBinTree(root, val);
}
std::cout << "1:PreOrder:" << std::endl;
std::cout << "\t PreOrder:" ;
PreOrder(root);
std::cout << std::endl;
std::cout << "\t PreOrderStk:";
PreOrderStk(root);
std::cout << std::endl;
std::cout << "\t PreOrderMorris:";
PreOrderMorris(root);
std::cout << std::endl; std::cout << "2:InOrder:" << std::endl;
std::cout << "\t InOrder:";
InOrder(root);
std::cout << std::endl;
std::cout << "\t InOrderStk:";
InOrderStk(root);
std::cout << std::endl;
std::cout << "\t InOrderMorris:";
InOrderMorris(root);
std::cout << std::endl; std::cout << "3:PostOrder:" << std::endl;
std::cout << "\t PostOrder:";
PostOrder(root);
std::cout << std::endl;
std::cout << "\t PostOrderStk:";
PostOrderStk(root);
std::cout << std::endl; std::cout << "4:LevelOrder:" << std::endl;
std::cout << "\t LevelOrder:";
LevelOrderQueue(root);
std::cout << std::endl;
return ;
}

三. 二叉树构建

1. 先序序列和中序序列构建一颗二叉树  中序序列和后序序列构建一棵二叉树

注:为什么先序和后序不能确定一棵二叉树?(例如:先序ABCD,后序CBDA)

给定先序遍历序列和后序遍历序列,求有多少种不同形态的二叉树?

考虑简单的两个节点A和B,前序遍历为AB,后序遍历为BA,则会存在两种二叉树: A->left=B 和 A->right=B

存在不同二叉树的原因在于某节点缺少其中一个子树,在遍历序列中呈现出前序和后序遍历中节点的值相邻但顺序相反(例如先序AB,后序BA),那么总的二叉树数量番倍

int BinTreeNum(const std::vector<int>& preOrder, const std::vector<int>& postOrder) {
assert(preOrder.size() == postOrder.size());
int num = 1;
for (int i = ; i < preOrder.size(); ++i) {
auto iter = std::find(postOrder.begin(), postOrder.end(), preOrder.at(i));
int index = std::distance(postOrder.begin(), iter);
if (i < preOrder.size() - && index > && preOrder.at(i+1) == postOrder.at(index-1)) {
num *= ;
}
}
return num;
}

2. 有序数组/链表 构建一颗二叉查找树BST

四. 二叉树判断

1. 判断二叉查找树BST

2. 判断平衡二叉树

3. 判断对称二叉树

4. 判断相同二叉树

5. 判断二叉树的子树

五. 二叉树路径相关问题

1. 二叉树路径和问题

2. 二叉树节点之间的最大距离(任意两个节点之间的最大步数)

二叉树中相距最远的两个节点之间的距离。
递归解法:
(1)如果二叉树为空,返回0,同时记录左子树和右子树的深度,都为0
(2)如果二叉树不为空,最大距离要么是左子树中的最大距离,要么是右子树中的最大距离,要么是左子树节点中到根节点的最大距离+右子树节点中到根节点的最大距离,同时记录左子树和右子树节点中到根节点的最大距离

int GetMaxDistance(BinTreeNode* root, int& maxLeft, int& maxRight) {
// maxLeft, 左子树中的节点距离根节点的最远距离
// maxRight, 右子树中的节点距离根节点的最远距离
if (root == NULL) {
maxLeft = ;
maxRight = ;
return ;
}
int maxLL, maxLR, maxRL, maxRR;
int maxDistLeft = ;
int maxDistRight = ; if (root->left != NULL) {
maxDistLeft = GetMaxDistance(root->left, maxLL, maxLR);
maxLeft = std::max(maxLL, maxLR) + ;
} else {
maxDistLeft = ;
maxLeft = ;
} if (root->right != NULL) {
maxDistRight = GetMaxDistance(root->right, maxRL, maxRR);
maxRight = std::max(maxRL, maxRR) + ;
} else {
maxDistRight = ;
maxRight = ;
} return std::max(maxLeft + maxRight, std::max(maxDistLeft, maxDistRight));
}

3. 二叉树最大路径和问题(任意两个节点之间)

思路:有点类似于 数组最大子段和问题,部分和大于0,表示贡献值为0,可以相加起来

我们对二叉树进行 dfs,先算出 左右子树的和值 leftSum 和 rightSum, 如果 leftSum 或者 rightSum 大于0,则表示此结果对 后续结果是有利的

int maxSum = INT_MIN;
int dfs(BinTreeNode* root) {
if (root == NULL) return ;
int leftSum = dfs(root->left);
int rightSum = dfs(root->right);
int sum = root->val;
if (leftSum > ) sum += leftSum;
if (rightSum > ) sum += rightSum;
maxSum = std::max(maxSum, sum);
if (std::max(leftSum, rightSum) > ) {
return std::max(leftSum, rightSum) + root->val;
} else {
return root->val;
}
} int maxPathSum(BinTreeNode* root) {
if (root == NULL) return ;
dfs(root);
return maxSum;
}

4. 二叉树最低公共祖先

思路:先求取 根节点到两个结点的路径序列,然后在这两个路径中查找最后一个公共结点即可

扩展:如何求 两个结点之间的距离呢? 可以先分别求得 根节点到这两个结点的路径,然后 最低公共祖先结点到 结点A的距离 + 最低公共祖先结点到 结点B的距离

struct BinTreeNode {
BinTreeNode* left;
BinTreeNode* right;
int val;
BinTreeNode(int value) : left(NULL), right(NULL), val(value) { }
}; BinTreeNode* BuildBinTree(BinTreeNode* root, int value) {
if (root == NULL) {
root = new BinTreeNode(value);
} else if (value <= root->val) {
root->left = BuildBinTree(root->left, value);
} else {
root->right = BuildBinTree(root->right, value);
}
return root;
} // 二叉树中序 递归
void Inorder(BinTreeNode* root) {
if (root == NULL) return;
Inorder(root->left);
std::cout << root->val << "-->";
Inorder(root->right);
} // 二叉树中序 非递归 栈
void InorderStk(BinTreeNode* root) {
if (root == NULL) return;
std::stack<BinTreeNode*> stk;
while (root != NULL || !stk.empty()) {
while (root != NULL) {
stk.push(root);
root = root->left;
}
BinTreeNode* cur = stk.top();
stk.pop();
std::cout << cur->val << "-->";
root = cur->right;
}
} // 二叉树中序 非递归 非栈
void InorderMorris(BinTreeNode* root) {
if (root == NULL) return;
BinTreeNode* cur = root;
BinTreeNode* prev = NULL;
while (cur != NULL) {
if (cur->left == NULL) {
std::cout << cur->val << "-->";
prev = cur;
cur = cur->right;
} else {
BinTreeNode* node = cur->left;
while (node->right != NULL && node->right != cur) {
node = node->right;
}
if (node->right == NULL) {
node->right = cur;
cur = cur->left;
} else {
std::cout << cur->val << "-->";
prev = cur;
cur = cur->right;
node->right = NULL;
}
}
}
} // 按值查找结点
BinTreeNode* GetNode(BinTreeNode* root, int value) {
if (root == NULL) return NULL;
if (root->val == value) {
return root;
} else if (value < root->val) {
return GetNode(root->left, value);
} else {
return GetNode(root->right, value);
}
} // 获取从根节点到某节点的路径
bool GetNodePath(BinTreeNode* root, BinTreeNode* pNode, std::vector<BinTreeNode*>& path) {
if (root == NULL || pNode == NULL) return false;
bool found = false;
if (root == pNode) {
path.push_back(root);
found = true;
return found;
}
path.push_back(root);
found = GetNodePath(root->left, pNode, path);
if (!found) { // 左子树未找到,继续右子树找
found = GetNodePath(root->right, pNode, path);
}
if (!found) { // 左右子树均未找到,此时才pop
path.pop_back();
}
return found;
} // 两个结点的最低公共祖先
BinTreeNode* GetLastCommonParent(BinTreeNode* root, BinTreeNode* pNode1, BinTreeNode* pNode2) {
if (root == NULL || pNode1 == NULL || pNode2 == NULL) {
return NULL;
}
std::vector<BinTreeNode*> path1;
std::vector<BinTreeNode*> path2;
bool bFound1 = GetNodePath(root, pNode1, path1);
bool bFound2 = GetNodePath(root, pNode2, path2);
if (bFound1 == false || bFound2 == false) {
return NULL;
}
std::vector<BinTreeNode*>::iterator iter1 = path1.begin();
std::vector<BinTreeNode*>::iterator iter2 = path2.begin();
for (; iter1 != path2.end() && iter2 != path2.end(); ++iter1, ++iter2) {
if (*iter1 != *iter2) {
break;
}
}
--iter1;
return *iter1;
}

我要好offer之 二叉树大总结的更多相关文章

  1. 我要好offer之 搜索算法大总结

    1. 二分搜索 详见笔者博文:二分搜索的那些事儿,非常全面 2. 矩阵二分搜索 (1) 矩阵每行递增,且下一行第一个元素大于上一个最后一个元素 (2) 矩阵每行递增,且每列也递增 3. DFS 深度优 ...

  2. 我要好offer之 C++大总结

    0. Google C++编程规范 英文版:http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml 中文版:http://zh-g ...

  3. 我要好offer之 链表大总结

    单链表是一种递归结构,可以将单链表看作特殊的二叉树(我把它叫做一叉树) 单链表的定义: /** * Definition for singly-linked list. * struct ListNo ...

  4. 我要好offer之 网络大总结

    1. TCP协议的状态机 TCP一共定义了11种状态,这些状态可以使用 netstat 命令查看 @左耳朵耗子 tcp系列教程: 上篇 下篇 2. TCP建立连接3次握手.释放连接4次握手 TCP包头 ...

  5. 《剑指offer》 二叉树的镜像

    本题来自<剑指offer>二叉树的镜像 题目: 操作给定的二叉树,将其变换为源二叉树的镜像. 二叉树的镜像定义:源二叉树 8 / \ 6 10 / \ / \ 5 7 9 11 镜像二叉树 ...

  6. 剑指Offer:二叉树打印成多行【23】

    剑指Offer:二叉树打印成多行[23] 题目描述 从上到下按层打印二叉树,同一层结点从左至右输出.每一层输出一行. 题目分析 Java题解 package tree; import java.uti ...

  7. 剑指Offer:二叉树中和为某一值的路径【34】

    剑指Offer:二叉树中和为某一值的路径[34] 题目描述 输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径.路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径. ...

  8. 剑指 Offer 34. 二叉树中和为某一值的路径 + 记录所有路径

    剑指 Offer 34. 二叉树中和为某一值的路径 Offer_34 题目详情 题解分析 本题是二叉树相关的题目,但是又和路径记录相关. 在记录路径时,可以使用一个栈来存储一条符合的路径,在回溯时将进 ...

  9. 剑指 Offer 34. 二叉树中和为某一值的路径

    剑指 Offer 34. 二叉树中和为某一值的路径 输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径.从树的根节点开始往下一直到叶节点所经过的节点形成一条路径. 示例: 给定如下 ...

随机推荐

  1. 判断一张图片有没有src值

    我一开始一直以为判断一张图片有没有src值就是undefined呀 我知道这个 但是做起来发现出现了问题 if($('.img').attr('src') == 'undefined'){ conso ...

  2. Quartz.NET开源作业调度框架系列(四):Plugin Job

    如果在Quartz.NET作业运行时我们想动态修改Job和Trigger的绑定关系,同时修改一些参数那么该怎么办呢?Quartz.NET提供了插件技术,可以通过在XML文件中对Job和Trigger的 ...

  3. Frameless - 用于预览 iOS8 原型的浏览器

    Frameless 是一个用于在 iOS8 中预览产品原型的浏览器.可以可以帮助那些需要一个简单的方法来预览 iOS 设备上的原型设计和开发效果.没有状态栏,通过手势控制浏览器的历史以及键盘的显示. ...

  4. [应用][js+css3]3D盒子导航[PC端]

    CSS3构建的3D盒子之导航应用 1.在用css3构建的盒子表面,放上iframe,来加载导航页面. 2.鼠标左键按下移动可旋转盒子,寻找想要的网址. 3.左键单机盒子表面,将全屏现实所点盒子表面的网 ...

  5. JavaScript学习笔记-JSON对象

    JSON 是一种用来序列化对象.数组.数值.字符串.布尔值和 null 的语法.它基于 JavaScript 语法,但是又有区别:一些 JavaScript 值不是 JSON,而某些 JSON 不是 ...

  6. servle

      基于HTTP协议下的,http请求和http响应.   http请求------请求的是服务器中的地方. 1.servlet就是解析http请求和发送http响应.   2.servlet是是一个 ...

  7. 一个解决表单中的文字和文本区域(textarea)上对齐的方法

    在进行表单布局的时候通常会遇到这样的情况 文本和textarea标签是底部对齐的 <p><em>邮箱</em><textarea style='height: ...

  8. javascript对象初探(一)--- 构造器函数

    我们可以通过构造器函数(简称构造函数)来创建对象: function Her(){ this.child = 'Jon'; } 为了使用该函数来创建对象,我们需要使用new操作符,例如: var sh ...

  9. 编译安装mysql(Ubuntu10 64位)

    选用较好的编译器和较好的编译器选项,这样应用可提高性能10-30%,这个对大多数程序都非常重要 Mysql的编译,不同的版本具体的配置方式是有差别的 旧版的配置形式参考 这个形式主要是使用config ...

  10. 使用cocoaPods一键集成第三方登录(新浪微博,qq,微信)

    第三方登录是现在app很常用的功能,而这个功能我已经写过两三次了...每次都写大同小异的代码真的是很痛苦,而且每次都要根据说明去添加那些依赖库,配置linkFlag什么的,完全是体力活,所以一直想把这 ...