前言
简单讲述二叉树相关知识,建议小伙伴们在理解基础概念的基础上,对照代码部分近一步加深理解,兼顾学习理论知识的同时巩固算法!!一起加油啊!~
种类
满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。也可以说深度为k,有2^k-1个节点的二叉树。
完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层(h从1开始),则该层包含 1~ 2^(h-1) 个节点。
注:优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系
二叉搜索树
二叉搜索树是一个有序树
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
平衡二叉树
AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
存储方式
顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在各个地址的节点串联一起。
链式存储
顺序存储(用得少)
用数组来存储二叉树,左孩子为 2 * i + 1,右孩子为 2 * i + 2;
深度优先遍历
可以理解为“一条路走到黑”,递归遍历方式,一般用栈来存储;如前中后序遍历。所谓前中后序遍历指的是访问根节点的位置顺序。
广度优先遍历
可以理解为像水波一样一层一层去遍历,如层序(迭代)遍历方式,一般用队列来存储
二叉树的定义代码版
Java 版本:
public class TreeNode{
int val;
TreeNode left;
TreeNode right;
TreeNode(){}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right){
this.val = val;
this.left = left;
this.right = right;
}
C++版本:
struct TreeNode{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
准备解题啦!
递归遍历
步骤:
1、确定递归函数的参数和返回值
2、确定终止条件
3、确定单层递归的逻辑
以前序为例, C++版本:
class Solution{
public:
void traversal(TreeNode *cur, vector<int>& vec){
if(cur == NULL) return;
//前序遍历
vec.push(cur->value); //根节点
traversal(cur->left,vec); //遍历左孩子
traversal(cur->right,vec); //遍历右孩子
}
vector<int> preorderTraversal(TreeNode* root){
vector<int> result;
traversal(root,result);
return result;
}
};
注:中序、后序遍历为上述最后三句代码交换位置
以前序为例,Java版本:
class Solution{
public List<Integer> preorderTarversal(TreeNode root) {
List<Interger> result = new ArrayList<Integer>();
preorder(root,result);
return result;
}
public void preorder(TreeNode root,List<Integer> list){
if (root == null) return;
list.add(root.val);
preorder(root.left, list);
preorder(root.left, list);
}
}
迭代遍历
迭代遍历和递归遍历不同,不能简单的进行语句交换,这是由实现迭代的数据结构——栈的特点决定的。
前序: C++版本:
// 前序遍历顺序:中-左-右,入栈顺序:中-右-左
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root){
stack<TreeNode*> st; //设置一个栈来存放中间结果
vector<int> result;
if ( root == NULL) return result;
st.push(root); //前序遍历先把根节点放进去
while(!st.empty()) {
TreeNode* node = st.top(); //获取“中”值
st.pop();
result.push_back(node->val); //“中”值放入结果中
//这里需要先放入右孩子,因为栈的结构是先进后出
//这里还需要判断左右孩子是否存在,因为空节点不入栈
if(node->right) st.push(Node->right); //如果有右孩子,则把右孩子放进去
if(node->left) st.push(Node->left); //如果有左孩子,则把左孩子放进去
}
return result;
}
};
前序:Java版本:
// 前序遍历顺序:中-左-右,入栈顺序:中-右-左
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if(root == null) return result;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()) {
TreeNode node = stack.pop(); //存放“中”
result.add(node.val);
if(node.right != null) stack.push(node.right); //存放“右”
if(node.left != null) stack.push(node.left); //存放“左”
return result;
}
}
}
在进行后序遍历之前,先进行一个分析如下,所以可以直接在前序遍历的基础上进行代码改进——调换左右顺序并进行数组反转。
后序:C++版本:
// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root){
stack<TreeNode*> st;
stack<int> result;
if (root == NULL) return result;
st.push(root);
while(!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node);
if(node->left) st.push(node->left);
if(node->right) st.push(node->right);
}
reverse(result.begin(),result.end());
return result;
}
};
后序:Java版本:
// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果
class Solution(){
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
Stack<TreeNode> st;
st.push(root);
while(!st.isEmpty()){
TreeNode node = st.pop();
TreeNode.add(node.val);
if(node.left!=null) st.add(node.left);
if(node.right!=null) st.add(node.right);
}
Collections.reverse(result);
return result;
}
}
中序:C++版:
// 中序遍历顺序: 左-中-右 入栈顺序: 左-右
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;
while(cur !=NULL || !st.empty()) {
if(cur != NULL) {
st.push(cur);
cur = cur->left; //这个时候就是不停放入左孩子(遍历左子树)
} else {
cur = st.top(); //到最底层以后开始弹出
st.pop();
result.push_back(cur->val); //把当前值加入结果序列
cur = cur->right; //每次把当前值弹出以后就去遍历它的右孩子
}
}
return result;
}
};
中序:Java版本:
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if(root == null) return result;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while(cur!=null || !stack.isEmpty()) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
cur = stack.pop();
result.add(cur.val);
cur = cur.right;
}
}
return result;
}
}
层序遍历
C++版本:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*>que; //创建队列存放节点
if(root != nullptr) que.push(root);
vector<vector<int>>result; //因返回结果是一层一层的,所以这里定义一个二维数组
while(!que.empty()){
int size = que.size();
vector<int>vec;
//这里不能用que.size()--作为判断条件,因为它在动态变化
while(size--){
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);
}
return result;
}
};
Java版本:
class Solution {
public List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) return resList;
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while (!que.isEmpty()) {
List<Integer> itemList = new ArrayList<Integer>();
int len = que.size();
while (len > 0) {
TreeNode tmpNode = que.poll();
itemList.add(tmpNode.val);
if (tmpNode.left != null) que.offer(tmpNode.left);
if (tmpNode.right != null) que.offer(tmpNode.right);
len--;
}
resList.add(itemList);
}
return resList;
}
}
求二叉树每一层的平均值
本质上还是层序遍历, 只需要在每一层的末尾计算一次平均值即可
C++ 版本:
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
vector<double> result;
queue<TreeNode*> que;
if(root == NULL) return result;
que.push(root);
while(!que.empty()){
double sum = 0;
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
sum += node->val;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
result.push_back(sum / size);
}
return result;
}
};
Java 版本:
class Solution {
public List<Double> averageOfLevels(TreeNode root) {
Deque<TreeNode> que = new LinkedList<>();
List<Double> result = new ArrayList<>();
if(root != null) que.add(root);
while(!que.isEmpty()){
Double sum = 0.0;
int size = que.size();
for(int i = 0;i < size; i++){
TreeNode node = que.poll();
sum += node.val;
if(node.left != null) que.add(node.left);
if(node.right != null) que.add(node.right);
}
result.add(sum / size);
}
return result;
}
}
求N叉树的层序遍历
解决问题的关键在于如何对节点的孩子进行插入操作,其实只需要进行一个循环判断就可以了~~~
另外还要注意这里对节点的定义!!!不要搞错了哈~
C++ 版本:
/*
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
queue<Node*> que;
vector<vector<int>> result;
if(root != NULL) que.push(root);
while(!que.empty()){
vector<int> cur;
int size = que.size();
while(size--){
Node* node = que.front();
que.pop();
cur.push_back(node->val);
for(int i = 0; i < node->children.size(); i++){
if(node->children[i]) que.push(node->children[i]);
}
}
result.push_back(cur);
}
return result;
}
};
Java 版本:
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public List<List<Integer>> levelOrder(Node root) {
Deque<Node> que = new LinkedList<>();
List<List<Integer>> result = new ArrayList<>();
if (root != null) que.add(root);
while(!que.isEmpty()){
int size = que.size();
List<Integer> cur = new ArrayList<>();
for(int i = 0; i< size; i++){
Node node = que.poll();
cur.add(node.val);
List<Node> children = node.children;
for (Node child : children) {
if (child != null) {
que.add(child);
}
}
}
result.add(cur);
}
return result;
}
}