一、二叉树的遍历
二叉树是一种使用广泛的数据结构,掌握二叉树的遍历方式至关重要,以下列出了二叉树的四种遍历分析,其中前、中、后序遍历分别使用递归和非递归方式来实现。
=>层序遍历、前序遍历、中序遍历、后续遍历
二、前提条件
假设构造部分如下:
import java.util.Stack;
import java.util.Queue;
import java.util.LinkedList;
public class BST<E extends Comparable<E>> {
private class Node {
public E e;
public Node left, right;
public Node(E e) {
this.e = e;
left = null;
right = null;
}
}
测试函数如下:
public class Main {
public static void main(String[] args) {
BST<Integer> bst = new BST<>();
int[] nums = {5, 3, 6, 8, 4, 2};
for(int num: nums)
bst.add(num);
/////////////////
// 5 //
// / \ //
// 3 6 //
// / \ \ //
// 2 4 8 //
/////////////////
bst.preOrder();
System.out.println();
bst.inOrder();
System.out.println();
bst.postOrder();
System.out.println();
bst.levelOrder();
System.out.println();
}
}
三、分析与代码
1.层序遍历
/////////////////
// 5 //
// / \ //
// 3 6 //
// / \ \ //
// 2 4 8 //
/////////////////
层序遍历的顺序是:5,3,6,2,4,8
基本思路:层序遍历的基本思路是使用队列,将节点从根、左子树、右子树逐个入队,先入队的节点先出队。这样出队的顺序就是根、左子树(左子树出队的同时又将它的左子树和右子树入队,此时入队的元素排在上一个右子树后面)、右子树(右子树出队的同时又将它的左子树和右子树入队,此时入队的元素排在上一个左子树的两个子节点后面),在出队的同时,将出队元素的e值打印出来,通过这种不断出队入队的方式,就可以实现层序遍历。
// 二分搜索树的层序遍历
public void levelOrder(){
if(root == null)
return;
Queue<Node> q = new LinkedList<>();
q.add(root);
while(!q.isEmpty()){
Node cur = q.remove();
System.out.println(cur.e);
if(cur.left != null)
q.add(cur.left);
if(cur.right != null)
q.add(cur.right);
}
}
添加到二维链表存储结果中的两种方法:
1.使用两个队列存储,奇数层和偶数层交替存取,就可以生成每一层的路径
2.使用一个队列和size标记存储当前队列的元素个数,每次循环size次取出队列值为一层,下一层又是新的size,就可以生成二维路径
public List<LinkedList<Integer>> levelOrder(TreeNode root){
LinkedList<LinkedList<Integer>> res = new LinkedList<>();
if (root==null)return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
int size = queue.size();
LinkedList<Integer>list = new LinkedList<>();
while (size>0){
TreeNode cur = queue.poll();
list.add(cur.val);
if (cur.left!=null)queue.offer(cur.left);
if (cur.right!=null)queue.offer(cur.right);
size--;
}
res.add(list);
}
return res;
}
2.前序遍历
(1)递归实现:由于递归的方法较为简单,不做解释
// 二分搜索树的前序遍历
public void preOrder(){
preOrder(root);
}
// 前序遍历以node为根的二分搜索树, 递归算法
private void preOrder(Node node){
if(node == null)
return;
System.out.println(node.e);
preOrder(node.left);
preOrder(node.right);
}
(2)非递归实现:
/////////////////
// 5 //
// / \ //
// 3 6 //
// / \ \ //
// 2 4 8 //
/////////////////
前序遍历的顺序是:先根节点、后左子树、最后右子树
5,3,2,4,6,8
前序遍历的非递归算法使用到栈的数据结构,由于栈是先进后出,所以为了实现根、左、右的顺序,我们先将右节点压入栈、再将左节点压入栈,这样出栈的时候就会是先左再右。
每次向左深入左子树的时候,先将当前根节点pop出来,然后再压入右、左节点,这样可以避免重复使用。
public List<Integer> preOrder(TreeNode root){
LinkedList<Integer> list = new LinkedList<>();
if (root==null)return list;
Stack<TreeNode>stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode cur = stack.pop();
list.add(cur.val);
if (cur.right!=null)stack.push(cur.right);
if (cur.left!=null)stack.push(cur.left);
}
return list;
}
最后:程序返回一个链表,可以使用 System.out.println(bst.preOrderII()); 进行测试。
3.中序遍历
(1)递归实现:
// 二分搜索树的中序遍历
public void inOrder(){
inOrder(root);
}
// 中序遍历以node为根的二分搜索树, 递归算法
private void inOrder(Node node){
if(node == null)
return;
inOrder(node.left);
System.out.println(node.e);
inOrder(node.right);
}
(2)非递归实现:(记得import包)
/////////////////
// 5 //
// / \ //
// 3 6 //
// / \ \ //
// 2 4 8 //
/////////////////
前序遍历的顺序是:先左子树、后根节点、最后右子树
2,3,4,5,6,8
前序遍历和中序遍历一个很大的不同是每次都要先深入到最左边的节点,然后再从下往上把需要的值导出来
在解决这种问题的时候,我们最重要的是让代码跟着自己的思路走,将每一个节点都看成一个根节点(包括null),然后去找它的最深左节点(找的过程中要将过程节点push进栈,比如5,3,2),如果最深左节点为null,这时我们才把这个节点pop出来。
比如说:
5的最深左节点是2(stack.push(cur)),我们不妨把null也当成一个节点(cur=cur.left,由于此时为空,所以没有将节点压栈),这样的话5的最深左节点就是null(cur),这时2就相当于一个左节点为空的根节点,然后我们将它pop出来(cur=stack.pop()),然后将他的右节点重复上述过程(cur=cur.right),如果(cur==null),会继续执行下一个循环,cur=stack.pop(),即cur现在是3所在节点。
注意特殊情况:如果将5,pop()出来后,栈就为空了,所以最上面的判断条件应该为:
while(!stack.isEmpty()||cur!=null),因为此时cur不为空,只有将所有元素pop完,它才为空。
public List<Integer> inOrder(TreeNode root){
LinkedList<Integer>list = new LinkedList<>();
if (root==null)return list;
Stack<TreeNode>stack = new Stack<>();
TreeNode cur = root;
while (!stack.isEmpty()||cur!=null){
if (cur!=null){
stack.push(cur);
cur=cur.left;
}
else {
cur = stack.pop();
list.add(cur.val);
cur = cur.right;
}
}
return list;
}
4.后序遍历
(1)递归实现:(记得import包)
// 二分搜索树的后序遍历
public void postOrder(){
postOrder(root);
}
// 后序遍历以node为根的二分搜索树, 递归算法
private void postOrder(Node node){
if(node == null)
return;
postOrder(node.left);
postOrder(node.right);
System.out.println(node.e);
}
(2)非递归实现:(记得import包)
/////////////////
// 5 //
// / \ //
// 3 6 //
// / \ \ //
// 2 4 8 //
/////////////////
后序遍历的顺序是:先左子树、后右子树、最后根节点
2,4,3,8,6,5
分析:因为后序非递归遍历二叉树的顺序是先访问左子树,再访问右子树,最后访问根节点。当用堆栈来存储节点,必须分清返回根节点时,是从左子树返回的,还从右子树返回的。所以,使用辅助指针r,其指向最近访问过的节点。也可以在节点中增加一个标志域,记录是否已被访问。
//strcut TreeNode {
// ElemType data;
// TreeNode *left, *right;
// TreeNode() {
// left = right = NULL;
// }
//}
void PostOrder(TreeNode *root) {
TreeNode *p = root, *r = NULL;
stack<TreeNode*> s;
while (p || !s.empty()) {
if (p) {//走到最左边
s.push(p);
p = p->left;
}
else {
p = s.top();
if (p->right && p->right != r)//右子树存在,未被访问
p = p->right;
else {
s.pop();
visit(p->val);
r = p;//记录最近访问过的节点
p = NULL;//节点访问完后,重置p指针
}
}//else
}//while
}
最后:希望大家能够理解,有不清楚的欢迎交流。