二叉树的定义形式有:节点指针和数组
i
,那么其左孩子的下标即i*2+1
,右孩子的下标即为i*2+2
二叉树的常见遍历形式有:前序遍历、后序遍历、中序遍历和层序遍历
根据二叉树的定义不同,又可分为不同类型的二叉树,常见的有:
除了叶节点,其他每一个节点都有左右节点
(节点不为空),同时要保证父子节点的顺序关系
。左节点 < 父节点,右节点 > 父节点
的条件,其中序遍历的结果为递增序列。|其左右节点的树的高度的差值| <= 1
更多有关二叉树的理论基础可查阅:《代码随想录》二叉树理论基础
对于二叉树的遍历在《代码随想录》中都有非常详细的解释,我也是阅读学习之后再来解题的,所以在下面的解题过程中就不加以赘述了,仅贴出实现不同遍历形式的程序代码。
Java解法(递归,前序遍历):
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
this.preorder(root, ans);
return ans;
}
public void preorder(TreeNode root, List<Integer> list){
if(null == root){
return;
}
list.add(root.val);
this.preorder(root.left, list);
this.preorder(root.right, list);
}
}
Java解法(递归,中序遍历):
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
this.inorder(root, ans);
return ans;
}
public void inorder(TreeNode root, List<Integer> list){
if(null == root){
return;
}
this.inorder(root.left, list);
list.add(root.val);
this.inorder(root.right, list);
}
}
Java解法(递归,后序遍历):
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
this.postorder(root, ans);
return ans;
}
public void postorder(TreeNode root, List<Integer> list){
if(null == root){
return;
}
this.postorder(root.left, list);
this.postorder(root.right, list);
list.add(root.val);
}
}
Java解法(迭代,前序遍历):
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
this.preorder(root, ans);
return ans;
}
public void preorder(TreeNode root, List<Integer> list){
Stack<TreeNode> stack = new Stack<>();
if(null != root) stack.push(root);
while(!stack.isEmpty()){
root = stack.pop();
list.add(root.val);
if(null != root.right) stack.push(root.right);
if(null != root.left) stack.push(root.left);
}
}
}
Java解法(迭代,中序遍历):
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
this.inorder(root, ans);
return ans;
}
public void inorder(TreeNode root, List<Integer> list){
Stack<TreeNode> stack = new Stack<>();
while(null != root || !stack.isEmpty()){
if(null != root){
stack.push(root);
root = root.left;
}else{
root = stack.pop();
list.add(root.val);
root = root.right;
}
}
}
}
Java解法(迭代,后序遍历):
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
this.postorder(root, ans);
return ans;
}
public void postorder(TreeNode root, List<Integer> list){
Stack<TreeNode> stack = new Stack<>();
if(null != root) stack.push(root);
while(!stack.isEmpty()){
root = stack.pop();
list.add(root.val);
if(null != root.left) stack.push(root.left);
if(null != root.right) stack.push(root.right);
}
Collections.reverse(list);
}
}
我们发现迭代法实现的先中后序,其实风格也不是那么统一,除了先序和后序,有关联,中序完全就是另一个风格了,一会用栈遍历,一会又用指针来遍历。那么如何针对三种不同的遍历方式,使用迭代法是可以写出统一风格的代码?
可以利用标记法来做到统一迭代:
Java解法(统一迭代,前序遍历):
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
this.preorder(root, ans);
return ans;
}
public void preorder(TreeNode root, List<Integer> list){
Stack<TreeNode> stack = new Stack<>();
if(null != root) stack.push(root);
while(!stack.isEmpty()){
root = stack.peek();
if(null != root){
stack.pop(); // 需要先弹出节点,避免后续重复访问
// 节点按照右左根的顺序进栈,后续出栈顺序为根左右(前序遍历)
if(null != root.right) stack.push(root.right);
if(null != root.left) stack.push(root.left);
stack.push(root);
stack.push(null); // 对需要处理的节点,在其后面跟上空指针作为标记
}else{
stack.pop(); // 遇到标记时,先弹出标记
// 再弹出下一个节点进行处理
root = stack.pop();
list.add(root.val);
}
}
}
}
Java解法(统一迭代,中序遍历):
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
this.inorder(root, ans);
return ans;
}
public void inorder(TreeNode root, List<Integer> list){
Stack<TreeNode> stack = new Stack<>();
if(null != root) stack.push(root);
while(!stack.isEmpty()){
root = stack.peek();
if(null != root){
stack.pop(); // 需要先弹出节点,避免后续重复访问
// 节点按照右根左的顺序进栈,后续出栈顺序为左根右(中序遍历)
if(null != root.right) stack.push(root.right);
stack.push(root);
stack.push(null); // 对需要处理的节点,在其后面跟上空指针作为标记
if(null != root.left) stack.push(root.left);
}else{
stack.pop(); // 遇到标记时,先弹出标记
// 再弹出下一个节点进行处理
root = stack.pop();
list.add(root.val);
}
}
}
}
Java解法(统一迭代,后序遍历):
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
this.postorder(root, ans);
return ans;
}
public void postorder(TreeNode root, List<Integer> list){
Stack<TreeNode> stack = new Stack<>();
if(null != root) stack.push(root);
while(!stack.isEmpty()){
root = stack.peek();
if(null != root){
stack.pop();// 需要先弹出节点,避免后续重复访问
// 节点按照根右左的顺序进栈,后续出栈顺序为左右根(后序遍历)
stack.push(root);
stack.push(null);// 对需要处理的节点,在其后面跟上空指针作为标记
if(null != root.right) stack.push(root.right);
if(null != root.left) stack.push(root.left);
}else{
stack.pop();// 遇到标记时,先弹出标记
// 再弹出下一个节点进行处理
root = stack.pop();
list.add(root.val);
}
}
}
}
二叉树结构也是在编程中常见的数据结构之一,例如堆其实就是一个树结构,以及哈希表中也运用到了红黑树来优化哈希表的存储结构等等。
通过今天的练习,我第一次了解并学习到了二叉树的统一迭代遍历算法,利用标记法来遍历二叉树的方法真的是非常巧妙,同时通过迭代算法的练习,也加深了对递归是如何模拟一个栈,以及递归算法如何转变为迭代算法有了一个初步的思路:
门径初窥书海奥, 欣喜若狂凯歌还。