今天跟着左程云算法课0017和0018。复习了二叉树前序遍历(中左右)、中序遍历(左中右)、后序遍历的实现(左右中),分别用递归和迭代实现。
通用的树结点类代码:
public static class TreeNode{
public int val;
public TreeNode left;
public TreeNode right;
public TreeNode(int n) {
val=n;
left=null;
right=null;
}
}
一、递归实现三序遍历
用递归迭代实现三序遍历的代码非常简单。我觉得核心的点在与跟左神过了一遍递归的流程,知道在具体的实现中递归的计算过程是怎样的,在子树操作结束后他还会再次回到父节点,在遍历过程中,一个非空结点需要经历三次。这是比较有意义的,比汉诺塔更适合入门者去理解递归。代码实现非常简单,代码如下:
(1)递归实现前序遍历
//先序遍历
public static void preOrder(TreeNode head) {
if(head==null) {
return;
}
System.out.print(head.val+" ");
preOrder(head.left);
preOrder(head.right);
}
(2)递归实现中序遍历
//中序遍历
public static void inOrder(TreeNode head) {
if(head==null) {
return;
}
inOrder(head.left);
System.out.print(head.val+" ");
inOrder(head.right);
}
(3)递归实现后序遍历
//后序遍历
public static void postOrder(TreeNode head) {
if(head==null) {
return;
}
postOrder(head.left);
postOrder(head.right);
System.out.print(head.val+" ");
}
二、迭代实现三序遍历
迭代法实现三序遍历基本上都用到栈结构。
(1)迭代实现前序遍历
前序遍历的顺序是中左右,先打印自己,再顾及左树和右树。那么栈是一个先进后出的结构,因此将一个节点弹出后,要先push它的right节点,再push它的left节点,这样出栈的时候就是先左再右了。想清楚之后代码实现也是比较简单的(实在想不清楚就画个简单的树过一遍代码的流程),代码如下:
//迭代实现树的先序遍历
public static void preOrder(TreeNode head) {
if(head!=null) {
Stack<TreeNode> stack=new Stack<TreeNode>();
stack.push(head);
while(!stack.isEmpty()) {
head=stack.pop();
System.out.print(head.val+" ");
//先把右树压进去
if(head.right!=null) {
stack.push(head.right);
}
//再把左树压进去
if(head.left!=null) {
stack.push(head.left);
}
}
System.out.println();
}
}
(2)迭代实现中序遍历
中序遍历的顺序是左中右,核心思想是先处理左树,再处理自己,最后再处理右树。具体步骤为:1)子树左边界进栈(左边界要进完);2)栈顶节点弹出,head指向当前弹出结点,打印,head指向head.right,重复步骤1;3)head为空且栈空时,结束。
用迭代实现中序遍历比较不容易想到,过一遍流程就能想通了,依然是画一个简单的树然后照着代码过一遍流程,代码如下:
//迭代实现树的中序遍历
public static void inOrder(TreeNode head) {
if(head!=null) {
Stack<TreeNode> stack=new Stack<TreeNode>();
while(!stack.isEmpty()||head!=null) {
if(head!=null) {
stack.push(head);
head=head.left;
}
else {
head=stack.pop();
System.out.print(head.val+" ");
head=head.right;
}
}
}
System.out.println();
}
(3)迭代实现后序遍历
我们在用迭代实现前序遍历时,专门先压right再压left为了实现中左右的顺序,那么如果先压left再压rigth,就实现了中右左的顺序;我们后序遍历顺序是左右中,是变形后的前序完全倒过来。因此设置了两个栈,在前序遍历打印的时候替换成入栈2,整个流程结束后将栈2的元素以此出栈。
核心在于由前序遍历的中左右->中右左->左右中;代码如下:
//迭代实现树的后序遍历(使用两个栈)
public static void postOrder(TreeNode head) {
if(head!=null) {
Stack<TreeNode> stack =new Stack<>();
Stack<TreeNode> collect=new Stack<>();
stack.push(head);
while(!stack.isEmpty()) {
head=stack.pop();
collect.push(head);
if(head.left!=null) {
stack.push(head.left);
}
if(head.right!=null) {
stack.push(head.right);
}
}
while(!collect.isEmpty()) {
System.out.print(collect.pop()+" ");
}
System.out.println();
}
}
三、时空复杂度分析
使用递归和迭代实现三序遍历的时间复杂度都是O(1),递归实现中每个结点会回到3次,迭代实现中每个结点进栈出栈的次数也是有限的,也就是说每个结点折腾的次数是有限的,所以递归和迭代实现三序遍历的时间复杂度都是O(1)。空间复杂度都是O(h),h是二叉树的高度。