二叉树基础知识
树(tree)是一个非空的优先元素集合,其中一个元素为根(root,层次中最高层的元素),余下的元素为子树(subtree),最高层下一级的元素是剩下元素所构成的子树的根。
二叉树(binary tree)是有限个元素的集合(可以为空)。
当二叉树t非空时,其中有一个称为根的元素,余下的元素(如果有)被组成2个二叉树,分别称为t的左子树和右子树。
二叉树和树的区别:
二叉树可以为空,树不能为空;
二叉树中每个元素都恰好又两颗子树(其中一个或两个可能为空),但树种每个元素可有若干子树;
在二叉树中每个元素的子树都是有序的,可以用左、右子树区别,但树的子树间是无序的。
二叉树的特性:
1.包含n(n>0)个元素的二叉树边数为n-1;
2.若二叉树的高度为h(h≥0),则该二叉树最少有h个元素,最多有2h-1个元素;
3.包含n个元素的二叉树的高度最大为n,最小为⌈log_2 (n+1)⌉;
4.当高度为h的二叉树恰好有2h-1个元素时,称其为满二叉树(full binary tree);
5.对高度为h的满二叉树中的元素按从上到下、从左到右的顺序从1到2h-1进行编号,从满二叉树中删除k个元素,编号为2h-i(1≤i≤k),所得到的二叉树被称为完全二叉树(complete binary tree)。有n个元素的完全二叉树的深度为「log_2 (n+1)⌉
6.设完全二叉树中一元素的序号为i,(1≤i≤n),则有以下关系成立:
1)当i=1时,该元素为二叉树的根。若i>1,则该元素父节点的编号为⌊i/2⌋;
2)当2i>n时,该元素无左孩子。否则,其左孩子的编号为2i;
3)当2i+1>n,该元素无右孩子。否则,其右孩子编号为2i+1。
二叉树的公式化描述:
在公式化描述方法中,按照二叉树对元素的编号方式,将二叉树的元素存储在数组中。如下图,缺少的元素由白圈和方格描述:
但是当缺少很多元素时,这种方法非常浪费空间。
一个有n个元素的二叉树可能最多需要2n-1个空间来存储,当每个节点都是其他节点的右孩子时,存储空间达到最大。这种情况的二叉树称为右斜二叉树(right-skewed)。
二叉树的链表描述:
二叉树最常用的描述方式是用链表或指针。每个元素都用一个有两个指针域的节点表示,这两个域为LeftChild和RightChid。除此两个指针域外,每个节点还有一个data域。
二叉树的遍历方式:
1.前序遍历(递归法、迭代法)
2.中序遍历(递归法、迭代法)
3.后序遍历(递归法、迭代法)
4.层次遍历(迭代法)
前中后可以理解为中间节点的遍历顺序
其中前中后序遍历属于深度优先搜索(DFS,depth-first search),层次遍历属于广度优先搜索(BFS,breadth-first search)。
二叉树的递归遍历
递归算法是一种直接或者间接调用自身函数或者方法的算法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
递归算法的三要素:
1.确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
2.确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
3.确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
按照这种方法,代码随想录给出了前序遍历、中序遍历和后续遍历的代码:
前序遍历(中左右):
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) { //传入参数为二叉树头节点指针和一个存放结果的数组
if (cur == NULL) return; //当前遍历的节点为空,本层递归结束
vec.push_back(cur->val); // 先取中节点的数值放进数组中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
中序遍历(左中右):
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
vec.push_back(cur->val); // 中
traversal(cur->right, vec); // 右
}
后续遍历(左右中):
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
vec.push_back(cur->val); // 中
}
题目1:144.二叉树的前序遍历
代码没什么意思
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val);
traversal(cur->left, vec);
traversal(cur->right, vec);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root,result);
return result;
}
};
题目2:94.二叉树的中序遍历
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
vec.push_back(cur->val); // 中
traversal(cur->right, vec); // 右
}
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
题目3:145.二叉树的后序遍历
class Solution {
public:
void traversal(Node* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val);
for (int i = 0; i < cur->children.size(); i++) {
traversal(cur->children[i], vec);
}
}
vector<int> preorder(Node* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
题目4:589.N叉树的前序遍历
和二叉树的前序遍历差不多
class Solution {
public:
void traversal(Node* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val);
for (int i = 0; i < cur->children.size(); i++) {
traversal(cur->children[i], vec);
}
}
vector<int> preorderTraversal(Node* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
题目5:589.N叉树的后序遍历
class Solution {
public:
void traversal(Node* cur, vector<int>& vec) {
if (cur == NULL) return;
for (int i = 0; i < cur->children.size(); i++) {
traversal(cur->children[i], vec);
}
vec.push_back(cur->val);
}
vector<int> postorder(Node* root) {
vector<int> result;
traversal(root, result);
return result;
}
};