今天开始学习二叉树相关内容。刚开始学习二叉树,就像学习数据结构一样,先学比较经典的二叉树的三种深度遍历方式,分别是前序遍历,中序遍历,以及后续遍历。我在学数据结构的时候,光学会了最简单的递归法遍历。今天在刷题的时候,学会了使用非递归法也就是迭代法来遍历。前中后序遍历,顾名思义,就是根节点的遍历的顺序。
LeetCode144.二叉树的前序遍历
前序遍历,即根节点在最前面被遍历,遍历顺序为根左右。
public static void preOrder(TreeNode root,List<Integer> list){
if (root==null){
return;
}
list.add(root.val);
preOrder(root.left,list);
preOrder(root.right,list);
}
public static List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list=new ArrayList<>();
preOrder(root,list);
return list;
}
使用递归法,按照leetcode的题目要求,需要使用两个函数解决问题。第一个函数preOrder
,就是做了递归的操作,将二叉树按照根左右的顺序将其每一个节点遍历出来。在第二个函数中,按题目要求,将其加入列表中。递归操作就是,先遍历根节点,然后递归遍历其左子树,然后递归遍历其右子树。
递归法较为简单,接下来则是较为复杂的迭代法。
public static void PreOrder2(TreeNode root,List<Integer> list) {
Stack<TreeNode> stack = new Stack<>();
if (root != null) {
stack.push(root);
} else {
return;
}
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
list.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
}
递归操作其本质也是使用了栈这种数据结构。所以在迭代法中,我们需要借助一个栈进行操作。首先,判断root是否为空,如果为空则说明该二叉树没有节点,直接返回空列表即可。否则,将root入栈。接着进入循环,如果栈中元素不为空,则说明该二叉树没有被遍历完,在这种条件下,应该不断循环进行遍历。首先将栈顶元素出栈,放入列表中,如果其右子节点存在,则将右子节点入栈,如果其左子节点存在,则将左子节点入栈。如此不断循环,则会按照根左右的顺序遍历每一个节点。其中有两点注意的地方,第一,这是先后两个if的判断,并不是一个if……else……这样的两个必选其一的判断。第二,这里的先后顺序,为什么前序遍历明明是根左右的顺序,但是先是右子节点入栈,之后才是左子节点入栈呢?这是因为栈这种数据结构的特点是,先入后出,如果我们要先左后右,那么入栈顺序则要相反,变成先右后左。
LeetCode94.二叉树的中序遍历
中序遍历的顺序是左根右。所以使用递归法时,先递归遍历其左子树,然后遍历根节点,最后递归遍历右子树。
public static void inOrder(TreeNode root,List<Integer> list){
if (root==null){
return;
}
inOrder(root.left,list);
list.add(root.val);
inOrder(root.right,list);
}
public static List<Integer> inorderTraversal(TreeNode root){
List<Integer> list=new ArrayList<>();
inOrder(root,list);
return list;
}
和前序遍历一样,为了实现LeetCode的题目要求,同样需要使用两个函数,一个负责递归遍历,另一个负责完成存入列表的操作。
接下来是中序遍历的迭代法。
public static void InOrder2(TreeNode root,List<Integer> list){
if(root==null){
return;
}
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();
list.add(cur.val);
cur=cur.right;
}
}
}
首先判断根节点是否为空,如果为空说明该二叉树没有节点,返回空列表即可。我们同样需要借助栈这种数据结构,接着定义一个指针,先指向根节点。当该指针不为空,或者栈不为空时,都将进行不断循环,如果该指针不为空,则将其指向节点入栈,该指针指向此节点的左子节点。如果该指针为空,说明该指针上一个指向的元素没有左子节点或者右子节点,那么将其出栈,并将指针重新指向该节点,将该节点加入列表后,指向其右子节点。不断循环以上操作。中序遍历的操作和前序遍历的操作并不一样,需要多加区分。
LeetCode145.二叉树的后序遍历
递归法和另外两层遍历相同思路,后序遍历的顺序是左右根。需要先递归遍历其左子树,再递归遍历其右子树,最后遍历根节点。
public static void postOrder(TreeNode root,List<Integer> list){
if (root==null){
return;
}
postOrder(root.left,list);
postOrder(root.right,list);
list.add(root.val);
}
public static List<Integer> postorderTraversal(TreeNode root){
List<Integer> list=new ArrayList<>();
postOrder(root,list);
return list;
}
几乎一模一样的思路,来实现leetcode题目中的后序遍历的要求。
接下来我们看迭代法,后序遍历有一些技巧可以被利用。在前序遍历时,我们提到需要注意的点有,左右节点入栈顺序的问题,因为前序遍历需要先左后右,所以需要先右后左的入栈。但是如果我们颠倒其入栈顺序,则会变成根右左的顺序进行遍历,再将存好的列表进行反转,就会从根右左变成左右根的顺序,即后序遍历。
public static void PostOrder2(TreeNode root, List<Integer> list) {
Stack<TreeNode> stack = new Stack<>();
if (root!=null){
stack.push(root);
}else {
return;
}
while (!stack.isEmpty()){
TreeNode node=stack.pop();
list.add(node.val);
if (node.left!=null){
stack.push(node.left);
}
if (node.right!=null){
stack.push(node.right);
}
}
Collections.reverse(list);
}