数据结构中树结构是一个非常重要的结构,树在文件存储、索引实现、数据查找方面有着广泛应用。树的层次遍历、前序遍历、中序遍历、后序遍历方式都需要我们非常熟悉的掌握,而对于像我这样的人来说,以前总是觉得树的遍历很简单,使用递归的方式几行代码就写完了,然后当要求使用非递归的方式进行前序中序后序遍历时只知道使用栈来实现,就在昨天 以前我都觉得非递归方式实现并没有那么难,然后当自己真正开始写的时候才知道并没有那么简单。于是在实现了这些递归与非递归方式以后我来写了这篇文章,提供给大家一些思路也是对自己学习遍历的总结吧!
1.前序遍历:
首先上代码:
//递归前序遍历
public void preScanIt(TreeNode node){
if (node == null){
return;
}
System.out.print(node.val+" ");
preScanIt(node.leftChild);
preScanIt(node.rightChild);
}
//非递归前序遍历
public void preScan(TreeNode node){
Stack<TreeNode> stack = new Stack<>();
stack.add(node);
while (!stack.isEmpty()){
TreeNode temp = stack.pop();
System.out.print(temp.val+" ");//每次访问结点的左右结点前需要先将结点的值输出(或者存储到最后的结果中)
if (temp.rightChild != null){
stack.add(temp.rightChild);
}
if (temp.leftChild != null){
stack.add(temp.leftChild);
}
}
}
前序遍历相对简单,因为前序遍历每次都是先访问父节点,然后再访问左右孩子结点。在递归方式中,我们只需要每次递归访问左右孩子之前先将父节点保存在遍历的结果序列中去。在非递归方式中使用栈来进行结点值的存储,此时前序遍历相较于中序和后序遍历较为简单,因为他只需要每次从栈中取出一个结点,然后将结点内容输出,再将它的孩子结点按照先左后右加入栈中即可。
2.中序遍历
//递归中序遍历
public void midScanIt(TreeNode node){
if (node == null){
return;
}
midScanIt(node.leftChild);
System.out.print(node.val+" ");
midScanIt(node.rightChild);
}
//非递归中序遍历
public void midScan(TreeNode node){
Stack<TreeNode> stack = new Stack<>();
TreeNode p = node;
while (!stack.isEmpty() || p != null){
if(p != null){//先访问所有的左结点
stack.add(p);
p = p.leftChild;
}else {
p = stack.pop();//如果左结点为空,或者已经将左结点遍历完了,那么将结点值输出
System.out.print(p.val+" ");
p = p.rightChild;//然后访问右节点
}
}
}
后序遍历的递归遍历很简单就不用再说了。
非递归方式遍历的思想我觉得很重要,可以学习一下。
现在以棵树为例说明非递归遍历的步骤:
第i步 | 栈中内容 | 当前结点p指向 | 输出序列 |
---|---|---|---|
0 | null | node(根节点) | |
1 | 1 | 2 | |
2 | 1,2 | 4 | |
3 | 1,2,4 | 6 | |
4 | 1,2,4,6 | 8 | |
5 | 1,2,4,6,8 | null(8的左子) | |
6 | 1,2,4,6 | null(8的右子) | 8 |
7 | 1,2,4 | 10(6的右孩子) | 8,6 |
8 | 1,2,4,10 | null | 8,6 |
9 | 1,2,4 | null | 8,6,10 |
10 | 1,2 | 5(4的右孩子) | 8,6,10,4 |
11 | 1,2,5 | null(5的左孩子) | 8,6,10,4 |
12 | 1,2 | 9(5的左孩子) | 8,6,10,4,5 |
13 | 1,2,9 | null(9的左孩子) | 8,6,10,4,5 |
14 | 1,2 | null(9的右孩子) | 8,6,10,4,5,9 |
15 | 1 | null(2的右孩子) | 8,6,10,4,5,9,2 |
16 | null | 3(1的右孩子) | 8,6,10,4,5,9,2 ,1 |
17 | 3 | 7 | 8,6,10,4,5,9,2 ,1 |
18 | 3,7 | null(7的左孩子) | 8,6,10,4,5,9,2,1 |
19 | 3 | 10(7的左孩子) | 8,6,10,4,5,9,2,1,7 |
20 | 3,10 | null(10的左孩子) | 8,6,10,4,5,9,2,1,7 |
21 | 3 | null(10的右孩子) | 8,6,10,4,5,9,2,1,7 ,10 |
22 | null | null(3的右孩子) | 8,6,10,4,5,9,2,1,7,10,3 |
此遍历方法的思想很好,可以用来解决很多有关树的问题
3.后序遍历
//递归后续遍历
public void nextScanIt(TreeNode node){
if (node == null){
return;
}
nextScanIt(node.leftChild);
nextScanIt(node.rightChild);
System.out.print(node.val+" ");
}
//非递归后序遍历
public void nextScan(TreeNode node){
//标记是从左子树还是右子树返回
Stack<Boolean> flag = new Stack<>();
//存储结点
Stack<TreeNode> stack = new Stack<>();
TreeNode p = node;
while (!stack.isEmpty() || p != null){
if (p != null){
stack.add(p);
p = p.leftChild;
flag.add(false);//标记在左子树
}else {
if (flag.pop()){//从右子树返回,也就是上一个结点是将要访问结点的右子树
p = stack.pop();
System.out.print(p.val+" ");
p = null;//这里最好的方式就是设置为0,因为以p为节点的子树已经遍历完成,而这颗树并不知道是上一个结点的左子树还是右子树
}else {//从左子树返回,左子树返回的时候不能输出结点,要先访问他的右孩子
p = stack.peek();
p = p.rightChild;
flag.add(true);
}
}
}
}
后序遍历思想和中序遍历几乎一样,只是加上了一个标志符用来表示当前指向的结点是父亲结点的左孩子还是右孩子结点,如果是从左孩子结点返回的,那么就不对父亲结点做操作去先访问父亲的右孩子结点;如果是从右孩子结点返回,那么直接输出该结点的值。
4.层次遍历
层次遍历与前序中序后序遍历不同,采用队列的方式进行遍历
//层次遍历
public void levelScan(TreeNode node){
Queue<TreeNode> queue = new LinkedList<>();
queue.add(node);
while (!queue.isEmpty()){
TreeNode temp = queue.poll();
System.out.print(temp.val + " ");
if (temp.leftChild != null){//如果左孩子不为空,加入队列
queue.add(temp.leftChild);
}
if (temp.rightChild != null){//如果哟孩子不为空,加入队列
queue.add(temp.rightChild);
}
}
}
测试程序:
public static void main(String[] args) {
ScanTree scanTree = new ScanTree();
TreeNode t1 = new TreeNode(1);
TreeNode t2 = new TreeNode(2);
TreeNode t3 = new TreeNode(3);
TreeNode t4 = new TreeNode(4);
TreeNode t5 = new TreeNode(5);
TreeNode t6 = new TreeNode(6);
TreeNode t7 = new TreeNode(7);
TreeNode t8 = new TreeNode(8);
TreeNode t9 = new TreeNode(9);
TreeNode t10 = new TreeNode(10);
TreeNode t11 = new TreeNode(11);
t1.leftChild = t2;
t1.rightChild = t5;
t2.leftChild = t3;
t3.leftChild = t8;
t8.rightChild = t9;
t3.rightChild = t4;
t4.leftChild = t10;
t4.rightChild = t11;
t5.rightChild = t6;
t6.leftChild = t7;
System.out.println("递归前序遍历:");
scanTree.preScanIt(t1);
System.out.println("\n非递归前序遍历:");
scanTree.preScan(t1);
System.out.println("\n递归中序遍历:");
scanTree.midScanIt(t1);
System.out.println("\n非递归中序遍历:");
scanTree.midScan(t1);
System.out.println("\n递归后序遍历:");
scanTree.nextScanIt(t1);
System.out.println("\n非递归后序遍历:");
scanTree.nextScan(t1);
System.out.println("\n层次遍历:");
scanTree.levelScan(t1);
}
结果: