二叉树基础---你必须会的二叉树知识

二叉树作为面试中高频出现的数据结构,本文将介绍二叉树基础。
对于二叉树,最常见的面试问题就是:请描述一棵二叉树,二叉树的XX遍历的递归算法与非递归算法。这些都是基础,也就是说是必须要掌握的内容。
本篇博文就致力于梳理清楚,这些基础问题。希望有所收获。

很多人会问,二叉树在我们平时开发中用的很少啊,为什么面试官那么爱问,以及算法题中出现的那么平凡,很多人甚至没有办法立刻想到二叉树的应用有哪些?那么就从这个问题开始本文吧。

1. 实际开发中哪里用到二叉树了呢?

  • 用的最多的应该是平衡二叉树,有种特殊的平衡二叉树红黑树,查找、插入、删除的时间复杂度最坏为O(logn)
  • 堆排序,堆排序是利用这种数据结构而设计的一种排序算法。
    • 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
  • Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。
  • 还有哈夫曼树编码方面的应用,他是一种压缩算法。
  • B-Tree,B±Tree在文件系统中的应用。

2.二叉树

花花酱说过,树是用递归定义的数据结构,凡是树的题目都可以用递归做。当然递归本质上还是利用了程序执行时候的入栈与出栈操作。我们也可以在代码中模拟这种操作,因此树的题目同样可以使用非递归的方式进行解答

用语言描述一棵二叉树?

二叉树是递归定义的数据结构,其结点最多只能有2个子节点,他们被称为左右子节点。
写到这里,我发现了一篇写的非常好的遍历二叉树文章(见参考文献二),既然前人已经写了那么好的文章了,那我就直接参考。下面记录二叉树的三种遍历方式。
定义二叉树

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    public TreeNode(int val) {
        this.val = val;
    }
}

前序遍历(递归)

public void preOrder(TreeNode root) {
   if (root == null) {
        return;
    }
    System.out.println(root.val);
    preOrder(root.left);
    preOrder(root.right);
}

中序遍历(递归)

public void inOrder(TreeNode root) {
    if (root==null) {
        return;
    }
    inOrder(root.left);
    System.out.println(root.val);
    inOrder(root.right);
}

后序遍历(递归)

public void postOrder(TreeNode root) {
    if (root==null) {
        return;
    }
    postOrder(root.left);
    postOrder(root.right);
    System.out.println(root.val);
}

前序遍历(非递归)

public void preOrder(TreeNode root) {
	if(root==null) {
		return;
	}
	Stack<TreeNode> stack = new Stack();
	while(root!=null) {
		System.out.println(root.val);
		stack.push(root);
		root = root.left;
	}
	while(!stack.isEmpty()) {
		TreeNode top = stack.peek();
		TreeNode node = top.right;
		stack.pop();
		while(node!=null) {
			System.out.println(node.val);
			stack.push(node);
			node = node.left;
		}
	}
}

中序遍历(非递归)

public void inOrder(TreeNode root) {
	if(root==null) {
		return;
	}
	Stack<TreeNode> stack = new Stack();
	while(root!=null) {
		stack.push(root);
		root = root.left;
	}
	while(!stack.isEmpty()) {
		TreeNode top = stack.peek();
		TreeNode node = top.right;
		System.out.println(top.val);
		stack.pop();
		while(node!=null) {
			stack.push(node);
			node = node.left;
		}
	}
}

后序遍历(非递归)

后续遍历相对复杂,原因就在于出栈的情况不一样了。

在先序和中序遍历过程中,只要左子树处理完毕实际上栈顶元素就可以出栈了,但是后续遍历情况不同,什么是后续遍历?只有左子树和右子树都遍历完毕才可以处理当前节点,这是后续遍历,那么我们该如何知道当前节点的左子树和右子树都处理完了呢?

显然我们需要某种方法记录下遍历的过程,实际上我们只需要记录下遍历的前一个节点就足够了。

如果我们知道了遍历过程中的前一个节点,那么我们就可以做如下判断了:

  • 如果前一个节点是当前节点的右子树,那么说明右子树遍历完毕可以pop了
  • 如果前一个节点是当前节点的左子树而且当前节点右子树为空,那么说明可以pop了
  • 如果当前节点的左子树和右子树都为空,也就是叶子节点那么说明可以pop了
public void postOrder(TreeNode root) {
    if(root!=null) {
        return;
    }
    Stack<TreeNode> stack = new Stack();
    TreeNode before = null;
    while(root!=null) {
        stack.push(root);
        root = root.left;
    }
    while(!stack.isEmpty()) {
        TreeNode top = stack.peek();
        if(top.left==null&&top.right==null ||
            before==top.right ||
            before==top.left&&top.right==null) {
            System.out.println(top.val);
            before = top;
            stack.pop();
        } else {
            TreeNode node = top.right;
            while(node!=null) {
                stack.push(node);
                node = node.left;
            }
        }
    }
}

下一篇文章,主要讨论的是堆排序。

参考文献

  1. 二叉树实际应用场景有哪些?
  2. 彻底理解二叉树的遍历
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章