二叉树理论基础篇
二叉搜索树
前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树
二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在各个地址的节点串联一起。
数组来存储二叉树如何遍历的呢?
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
但是用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。
所以大家要了解,用数组依然可以表示二叉树
二叉树的遍历方式
二叉树主要有两种遍历方式:
深度优先遍历:先往深走,遇到叶子节点再往回走。
广度优先遍历:一层一层的去遍历。
这两种遍历是图论中最基本的两种遍历方式,后面在介绍图论的时候 还会介绍到。
那么从深度优先遍历和广度优先遍历进一步拓展,才有如下遍历方式:
深度优先遍历
前序遍历(递归法,迭代法)
中序遍历(递归法,迭代法)
后序遍历(递归法,迭代法)
广度优先遍历
层次遍历(迭代法)
之前我们讲栈与队列的时候,就说过栈其实就是递归的一种实现结构,也就说前中后序遍历的逻辑其实都是可以借助栈使用非递归的方式来实现的。
二叉树的定义
链式存储的二叉树节点的定义方式:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
二叉树的定义 和链表是差不多的,相对于链表 ,二叉树的节点里多了一个指针, 有两个指针,指向左右孩子
二叉树的递归遍历
真所谓一入递归深似海,每次写递归算法 ,都是靠玄学来写代码,代码能不能编过都靠运气。
思考
//带有递归性质的前中后遍历
每次写递归,都要按照这三要素来写:
1、确定递归函数的参数和返回值;
2、确定终止条件;
3、确定单层递归的逻辑(即每一层递归需要处理的信息,在这里也就会重复调用自己来实现递归过程)
//前序遍历练手
1、void返回类型 vector来存放节点的数值 cur为根节点:
void traversal(TreeNlde* cur, vector
2、当前遍历的节点为空时--那么本层的递归就要结束了
if(cur == NULL) return;
3、前序遍历是-中左右,所以单层递归的逻辑为先取中节点的数值
vec.push_back(cur->val);//数组放入我们便利过的元素
traversal(cur->left, vec);//向左去遍历
traversal(cur->right,vec); //同上
代码展示
`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); // 中
}`
二叉树的迭代遍历
//非递归的前后序遍历--即迭代遍历
递归的实现就是:每一次递归调用都会把函数的
局部变量、参数值和返回地址等压入调用栈中,然后递归返
回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为
什么可以返回上一层位置的原因。
此时大家应该知道我们用栈也可以是实现二叉树的前后中序遍历了
思路
//前序遍历是中左右,每次先处理的是中间节点,那么先将根节点
//放入栈中,然后将右孩子加入栈,再加入左孩子。
为什么要先加入 右孩子,再加入左孩子呢?
//因为这样出栈的时候才是中左右的顺序
代码展示
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;
}
};
后序遍历就简单了:先序遍历是中左右,后续遍历是左右中
1、调整一下先序遍历的代码顺序,就变成中右左的遍历顺序
2、然后在反转result数组,输出的结果顺序就是左右中
if (node->left) st.push(node->left);
// 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
if (node->right) st.push(node->right); // 空节点不入栈
}
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
中序遍历迭代算法
//迭代的过程:
1、处理:将元素放进result数组
2、访问:遍历节点
前序遍历的顺序是中左右,先访问的元素是中间节点,要处
理的元素也是中间节点。
//因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。
代码
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root){
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;//cur初始为根节点
while(cur != NULL || !st.empty()){
// 当指针遍历为空且栈也为空时 即遍历终止
if(cur != NULL){
st.push(cur); //把当前访问的元素加入到栈里
cur = cur->left; 继续往左走 (一路向左)
}
else{即当指针为空的时候,我们就要从栈里弹出元素(最近的)
cur = st.top(); st.pop();//cur用来记录,pop弹出
result.push_back(cur->val);//处理元素(放进数组)
cur = cur->right; 遍历当前指针右孩子
}
}
return result;
}
};
标签:遍历,cur,递归,随想录,vec,节点,二叉树
From: https://www.cnblogs.com/lijian-allan/p/17228558.html