解题过程中的二叉树主要有两种形式:满二叉树和完全二叉树。
满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。
什么是完全二叉树?
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树。
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
如图:
C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn,注意我这里没有说unordered_map、unordered_set,unordered_map、unordered_set底层实现是哈希表。
二叉树可以链式存储,也可以顺序存储。
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在各个地址的节点串联一起。
链式存储:
顺序存储:
顺序存储用于存储完全二叉树。
用数组来存储二叉树如何遍历的呢?
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
二叉树主要有两种遍历方式:
深度优先遍历:先往深走,遇到叶子节点再往回走。
广度优先遍历:一层一层的去遍历。
那么从深度优先遍历和广度优先遍历进一步拓展,才有如下遍历方式:
深度优先遍历
前序遍历(递归法,迭代法)
中序遍历(递归法,迭代法)
后序遍历(递归法,迭代法)
广度优先遍历
层次遍历(迭代法)
最后再说一说二叉树中深度优先和广度优先遍历实现方式,我们做二叉树相关题目,经常会使用递归的方式来实现深度优先遍历,也就是实现前中后序遍历,使用递归是比较方便的。
之前我们讲栈与队列的时候,就说过栈其实就是递归的一种实现结构,也就说前中后序遍历的逻辑其实都是可以借助栈使用递归的方式来实现的。
而广度优先遍历的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
这里其实我们又了解了栈与队列的一个应用场景了。
我们来看看链式存储的二叉树节点的定义方式。
C++代码如下:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
二叉树的前序遍历
二叉树的后序遍历
二叉树的中序遍历
递归算法的三个要素。
1.确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
2.确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
3.确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
在这里我们以先序遍历为例:
确定递归函数的参数和返回值:因为要打印出前序遍历节点的数值,所以参数里需要传入vector来放节点的数值,除了这一点就不需要再处理什么数据了也不需要有返回值,所以递归函数返回类型就是void,代码如下:
void Preorder(TreeNode* cur, vector<int>& vec)
确定终止条件:在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要结束了,所以如果当前遍历的这个节点是空,就直接return,代码如下:
void Preorder(TreeNode* root,vector<int>&result)
{
if(root!=nullptr)
{
result.push_back(root->val);
Preorder(root->left,result);
Preorder(root->right,result);
}
else
{
return ;
}
}
确定单层递归的逻辑:前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值,代码如下:
if(root!=nullptr)
{
result.push_back(root->val);
Preorder(root->left,result);
Preorder(root->right,result);
}
二叉树的前序遍历
二叉树的后序遍历
二叉树的中序遍历
前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。
为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root)
{
stack<TreeNode*>st;
vector<int> result;
if(root==nullptr)return result;
st.push(root);
while(!st.empty())
{
TreeNode* cur = st.top();
st.pop();
result.push_back(cur->val);
if(cur->right!=nullptr) st.push(cur->right);
if(cur->left!=nullptr) st.push(cur->left);
}
return result;
}
};
再来看后序遍历,先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了,如下图:
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*>st;
vector<int> result;
if(root==nullptr)return result;
st.push(root);
while(!st.empty())
{
TreeNode* cur = st.top();
st.pop();
result.push_back(cur->val);
//改动的地方
if(cur->left!=nullptr) st.push(cur->left);
if(cur->right!=nullptr) st.push(cur->right);
}
reverse(result.begin(),result.end());
return result;
}
};
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root)
{
vector<int>result;
stack<TreeNode*> st;
TreeNode* cur = root;
while(cur!=nullptr || !st.empty())
{
if(cur!=nullptr)
{
st.push(cur);
cur = cur->left;
}
else
{
cur = st.top();
st.pop();
result.push_back(cur->val);
cur = cur->right;
}
}
return result;
}
};
力扣题目链接
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。
class Solution {
public:
vector<vector<int>>result;
vector<int>path;
queue<TreeNode*> dq;
void travelsel(TreeNode* node)
{
dq.push(node);
while(!dq.empty())
{
int len = dq.size();
while(len--)
{
TreeNode* cur = dq.front();
dq.pop();
path.push_back(cur->val);
if(cur->left!=nullptr) dq.push(cur->left);
if(cur->right!=nullptr) dq.push(cur->right);
}
result.push_back(path);
path.clear();
}
}
vector<vector<int>> levelOrder(TreeNode* root)
{
if(root==nullptr)return result;
travelsel(root);
return result;
}
};
力扣题目链接
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
vector<vector<int>> result;
while (!que.empty()) {
int size = que.size();
vector<int> vec;
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
result.push_back(vec);
}
reverse(result.begin(), result.end()); // 在这里反转一下数组即可
return result;
}
};
力扣题目链接
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
力扣题目链接
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
class Solution {
public:
vector<vector<int>> levelOrder(Node* root)
{
vector<vector<int>>result;
queue<Node*> que;
if(root!=nullptr)
{
que.push(root);
while(!que.empty())
{
int size = que.size();
vector<int>path;
while(size--)
{
Node* cur = que.front();
path.push_back(cur->val);
que.pop();
for(int i = 0 ; i < cur->children.size();i++)
{
que.push(cur->children[i]);
}
}
result.push_back(path);
}
}
return result;
}
};
力扣题目链接
力扣题目链接
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
class Solution {
public:
Node* connect(Node* root)
{
queue<Node*>que;
if(root!=nullptr)
{
que.push(root);
while(!que.empty())
{
int size = que.size();
Node*pre;
Node*cur;
for(int i = 0 ; i < size ;++i)
{
if(i == 0)
{
pre = que.front();
que.pop();
cur = pre;
}
else
{
cur = que.front();
que.pop();
pre->next = cur;
pre = cur;
}
if(cur->left!=nullptr) que.push(cur->left);
if(cur->right!=nullptr) que.push(cur->right);
}
}
}
return root;
}
};
力扣题目链接
力扣
力扣题目链接
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
力扣题目链接
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
力扣链接
力扣题目链接
给定一个二叉树,判断它是否是高度平衡的二叉树。
力扣题目链接
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> paths;
vector<int> path;
void getpath(TreeNode* node)
{
if(node!=nullptr)
{
path.push_back(node->val);
}
if(node->left == nullptr && node->right==nullptr)
{
paths.push_back(path);
}
else
{
if(node->left!=nullptr) getpath(node->left);
if(node->right!=nullptr) getpath(node->right);
}
path.pop_back();
}
string int_to_string(vector<int> v)
{
string s;
for(int i = 0 ; i < v.size() ; i++)
{
s += to_string(v[i]);
if(i < v.size()-1)
{
s += "->";
}
}
return s;
}
vector<string> binaryTreePaths(TreeNode* root)
{
vector<string> result;
getpath(root);
for(int i = 0 ; i < paths.size() ; i++)
{
result.push_back(int_to_string(paths[i]));
}
return result;
}
};