java 递归_Java数据结构——树的遍历算法(递归和非递归实现)

d7be37cef77c8c12f6df2a5dfb5dfa06.png

树从存储方式上可分为顺序树和链式树

这节我们主要说的是链式树。

1. 定义结点

public 

2. 树的创建

定义一个Tree类,里面主要实现树的创建,和一些遍历操作,先部分展示代码并讲解,在最后会给出全部源码。

我们为了养成良好的习惯,建议把每个东西归结成一个类,比如树,栈,队列,都分开定义,到用的时候,直接new就行了。

先定义数据结构

1.树的结构:

public 

2.栈的结构:(树的非递归遍历会用到)

public 

3.循环队列的结构:(树的层序遍历会用到)

public 

4. 树的递归创建方式:(使用先序创建)

// 递归创建树(树的头节点)

3. 树的递归遍历

我们利用三种遍历的特点,根据他们的打印顺序总结出来:

先序:根左右

中序:左跟右

后序:左右跟

3.1 树的递归先序遍历

// 先序遍历(递归)

3.2 树的递归中序遍历

// 中序遍历(递归)

3.3 树的递归后序遍历

// 后序遍历(递归)

递归的遍历特别简单,只需要知道递归的原理就OK了,不做详解。

4. 树的非递归遍历

树的非递归遍历,还会用到栈,利用栈的先进后出的特点,实现树的遍历。

4.1 树的非递归先序遍历

这块我会给出两种方法,原理基本相同。

(1)根据先序打印特点:根左右。

我们用到压栈和出栈来模拟这个过程:

  1. 由于栈是先进后出的,所以我们先访问根节点,压栈。
  2. 然后判断栈不为空,然后出栈,打印。
  3. 对出栈的结点进行判断,他有没有右孩子,如果有,把右孩子压栈。
  4. 再判断有没有左孩子,如果有,把左孩子压栈。
  5. 完成后,再跳回第2步,一直循环
// 先序遍历(非递归)

(2)第二种只是把压栈的顺序,进行了一点修正,其他的都差不多,但是,为了方便记忆非递归的先序,中序,后序,建议学习第二种的算法。

	// 先序遍历(非递归)
	void nonPreOrder2(Node tree) {

		if (tree == null) {
			System.out.println("树不存在");
			return;
		}
		Node temp = null;
		temp = tree;
		while (!stack.IsEmpty() || temp != null) {
			while (temp != null) {
				System.out.println(temp.name);
				stack.push(temp);
				temp = temp.lchild;
			}
			temp = stack.pop();
			temp = temp.rchild;
		}
	}

4.2 树的非递归中序遍历

根据中序打印特点:左跟右。

我们用到压栈和出栈来模拟这个过程:

  1. 由于栈是先进后出的,所以我们先访问根节点,用temp结点记录。
  2. 然后判断栈不为空或者temp不为空,进入循环。
  3. 先将temp的所有左子树全部压栈。
  4. 然后出栈栈顶结点,打印
  5. 然后再判断出栈的这个结点有没有右孩子,如果有,将右孩子入栈
  6. 再将右孩子结点 的所有左孩子全部入栈,即从第3步开始循环。
// 中序遍历(非递归)

中序的非递归遍历,和上述的先序的第二种非递归遍历比较,会发现,只改变了打印的位置,其他的都没有变化,所以建议记住,先序的第二种算法。

4.2 树的非递归后序遍历

后序我会给出,自己总结的三种方法,各有特点,其中第一种可以和前两对应起来,改动较小,对应编程能力差的同学,可以着重学习,后两种方法也很nice,想要多学点的同学,也可以学习。

(1)第一种:根据后序打印特点:左右跟。

我们根据先序的过程,先访问根节点,再访问左孩子,最后访问右孩子,根据具体的顺序,我们可以发现其实后序只需要,把先序打印的过程,换成一个如另一个栈的过程,所以我们需要两个栈,再把先将左孩子压栈,后将右孩子压栈,变成先将右孩子压栈,再将左孩子压栈,最后我们的第二个栈中就得到了后序的倒序,我们再将第二个栈出栈,打印就行。

// 后序遍历(非递归3)

后两种方法,都需要明白一个特点:

我们可以观察前序、中序、后序的过程,根据他们是第几次访问根节点然后再打印的特点,我们可以发现:

  1. 先序都是先访问根节点,然后打印,然后再访问他的左孩子,把他的左孩子又看出根节点,然后打印,又去访问他的左孩子,如果左孩子不存在,去访问他的右孩子,把他的右孩子又看成根节点,一直重复上面的步骤。(这块的根节点指的是每一个结点都看成根节点,不是特指树的根节点)。
  2. 中序都是先访问根节点,然后访问所有的左孩子,左孩子全部访问结束,再回来访问根节点,打印根节点,再去访问右孩子,又去访问右孩子的所有左孩子,一直重复上面步骤。
  3. 后序都是先访问根节点,再访问他的全部左孩子,左孩子全部访问结束,再回来访问他的根节点,又访问他的全部右孩子,右孩子全部访问结束,再反过来打印根节点。(这块的根节点指的是每一个结点都看成根节点,不是特指树的根节点)。

总结一个特点:

  1. 先序第一次访问一个结点时打印
  2. 中序第二次访问一个结点时打印
  3. 后序第三次访问一个结点时打印

后序什么时候打印,可以根据他是不是访问完右孩子过来的,如果是访问完右孩子过来的,就让他打印,否则去遍历他的右孩子。

下面的两种方法都是根据,这个特点完成的,这是有点稍微的区别。

(2)第二种:多创建一个结点,用来记录出栈的元素,进行判断

// 后序遍历(非递归1)

(3)第三种:利用在结点中,多一个属性,用来记录是第几次访问

// 后序遍历(非递归2)

5. 树的层序遍历

用到了队列

// 层序遍历

6. 计算树的深度

// 递归计算树的深度

7. 反转二叉树

7.1 递归方法

// 反转二叉树(递归)

7.2 非递归算法

用到栈

// 反转二叉树(非递归)

8. 统计结点数(递归)

// 统计结点数(递归)

9. 统计叶子结点数(递归)

//统计叶子结点数(递归)

10. 判断一个结点是否是一个子树上的结点(递归)

//找一个结点是否是一个子树的结点

11. 代码的综合

对上面讲述所有代码算法的综合

树的创建和操作:

//class Tree

import 

栈的结构:

//class Seqstack

public 

循环队列的结构:

//class Seqqueue

public 

给个测试用例:

//class Client

public 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值