数据结构学习——树形结构之递归遍历二叉树

目录

一. 什么是二叉树

二. 二叉树分类

2.1、完全二叉树

2.2、满二叉树 

2.3、扩充二叉树

2.4、平衡二叉树

三. 二叉树的应用场景

四. 遍历方式

五. 为什么要研究遍历

六. 前序遍历

七. 中序遍历

八. 后序遍历

九. 数据结构专栏


一. 什么是二叉树

二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树”。


二. 二叉树分类

2.1、完全二叉树

  • 若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
  • 一维数组可以作为完全二叉树的存储结构,堆排序使用的数据结构就是完全二叉树。

2.2、满二叉树 

  • 国际标准定义是除了叶结点外每一个结点都有左右子结点的二叉树

  • 国内的定义是:除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。很显然,按照这个定义,上面的图示二叉树就不是满二叉树。

2.3、扩充二叉树

  • 扩充二叉树是对已有二叉树的扩充,扩充后的二叉树的节点都变为度数为2的分支节点。也就是说,如果原节点的度数为2,则不变,度数为1,则增加一个分支,度数为0的叶子节点则增加两个分支。

2.4、平衡二叉树

  • 是一棵空树或它的任意节点的左右两个子树的高度差的绝对值不超过1


三. 二叉树的应用场景

  • 普通的二叉树,很难构成现实的应用场景,但因其简单,常用于学习研究,平衡二叉树则是实际应用比较多的。常见于快速匹配、搜索等方面。
  • 常用的树有:AVL树、红黑树、B+树、Trie(字典)树。
    1、AVL树: 最早的平衡二叉树之一。应用相对其他数据结构比较少。windows对进程地址空间的管理用到了AVL树。
    2、红黑树: 平衡二叉树,广泛用在C++的STL中。如map和set都是用红黑树实现的。还有Linux文件管理。
    3、B/B+树: 用在磁盘文件组织 数据索引和数据库索引。
    4、Trie树(字典树): 用在统计和排序大量字符串,如自动机、M数据库索引。

四. 遍历方式

  • 前序遍历:root -> left -> right
  • 中序遍历:left -> root -> right
  • 后序遍历:left ->right -> root
  • 已知后序遍历和中序遍历,能确定前序遍历(也就是可以唯一确定一颗二叉树)。
  • 已知前序遍历和中序遍历,能确定后序遍历(也就是可以唯一确定一颗二叉树)。

五. 为什么要研究遍历

        当我们介绍数组、链表时,为什么没有着重研究他们的遍历过程呢?

        二叉树的遍历又有什么特殊之处?

        在计算机程序中,遍历本身是一个线性操作。所以遍历同样具有线性结构的数组或链表,是一件轻而易举的事。反观二叉树,是典型的非线性数据结构,遍历时需要把非线性关联的节点转化成一个线性的序列,以不同的方式来遍历,遍历出的序列顺序也不同。

        那么,二叉树都有哪些遍历方式呢?

        从节点之间位置关系的角度来看,二叉树的遍历分为3种。

  • 前序遍历。
  • 中序遍历。
  • 后序遍历。

六. 前序遍历

        前序遍历(DLR,lchild,data,rchild),是二叉树遍历的一种,也叫做先根遍历、先序遍历、前序周游,可记做根左右。前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。
前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问 根结点,然后遍历左子树,最后遍历右子树。

二叉树为空则结束返回,否则:
(1)访问根结点。
(2)前序遍历左子树 。
(3)前序遍历右子树 。
需要注意的是:遍历左右子树时仍然采用前序遍历方法。

如图所示:

前序遍历结果:ABDECF
     其实在遍历二叉树的时候有三次遍历, 比如前序遍历:A->B->D->D(D左子节点并返回到D)->D(D右子节点并返回到D)->B->E->E(左)->E(右)->->B->A->C->F->F(左)->F(右)->C->C(右),可以用递归的方式,递归的输出当前节点,然后递归的输出左子节点,最后递归的输出右子节点。直接看代码更能理解:

package test0910;

public class Test {
	public static void main(String[] args) {
		TreeNode[] node = new TreeNode[10];// 以数组形式生成一棵完全二叉树
		for (int i = 0; i < 10; i++) {
			node[i] = new TreeNode(i);
		}
		for (int i = 0; i < 10; i++) {
			if (i * 2 + 1 < 10)
				node[i].left = node[i * 2 + 1];
			if (i * 2 + 2 < 10)
				node[i].right = node[i * 2 + 2];
		}
		preOrderRe(node[0]);
	}

	public static void preOrderRe(TreeNode biTree) {
		if (biTree == null)
			return;
		else {
			System.out.print(biTree.value + " ");
			preOrderRe(biTree.left);
			preOrderRe(biTree.right);
		}
	}
}

//节点结构
class TreeNode {
	int value;
	TreeNode left;
	TreeNode right;

	TreeNode(int value) {
		this.value = value;
	}
}

七. 中序遍历

        中序遍历(LDR)是 二叉树遍历的一种,也叫做 中根遍历、中序周游。在二叉树中,先左后根再右。巧记:左根右。
中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树
二叉树为空则结束返回,
否则:
(1)中序遍历左子树
(2)访问根结点
(3)中序遍历右子树

如图所示:

中序遍历结果:DBEAFC


public class Test {
	public static void main(String[] args) {
		TreeNode[] node = new TreeNode[10];// 以数组形式生成一棵完全二叉树
		for (int i = 0; i < 10; i++) {
			node[i] = new TreeNode(i);
		}
		for (int i = 0; i < 10; i++) {
			if (i * 2 + 1 < 10)
				node[i].left = node[i * 2 + 1];
			if (i * 2 + 2 < 10)
				node[i].right = node[i * 2 + 2];
		}
		midOrderRe(node[0]);
	}

	public static void midOrderRe(TreeNode biTree) {
		// 中序遍历递归实现
		if (biTree == null)
			return;
		else {
			midOrderRe(biTree.left);
			System.out.print(biTree.value + " ");
			midOrderRe(biTree.right);
		}
	}
}

// 节点结构
class TreeNode {
	int value;
	TreeNode left;
	TreeNode right;

	TreeNode(int value) {
		this.value = value;
	}
}

八. 后序遍历

        后序遍历(LRD)是 二叉树遍历的一种,也叫做 后根遍历、后序周游,可记做左右根。后序遍历有 递归算法和非递归算法两种。在二叉树中,先左后右再根。巧记:左右根。
后序遍历首先遍历左子树,然后遍历右子树,最后访问根结点,在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点。即:
二叉树为空则结束返回,
否则: 
(1)后序遍历左子树
(2)后序遍历右子树
(3)访问根结点

如图所示:

后序遍历结果:DEBFCA


public class Test {
	public static void main(String[] args) {
		// 以数组形式生成一棵完全二叉树
		TreeNode[] node = new TreeNode[10];
		for (int i = 0; i < 10; i++) {
			node[i] = new TreeNode(i);
		}
		for (int i = 0; i < 10; i++) {
			if (i * 2 + 1 < 10)
				node[i].left = node[i * 2 + 1];
			if (i * 2 + 2 < 10)
				node[i].right = node[i * 2 + 2];
		}
		postOrderRe(node[0]);
	}

	public static void postOrderRe(TreeNode biTree) {
		// 后序遍历递归实现
		if (biTree == null) {
			return;
		} else {
			postOrderRe(biTree.left);
			postOrderRe(biTree.right);
			System.out.print(biTree.value + " ");
		}
	}
}

//节点结构
class TreeNode {
	int value;
	TreeNode left;
	TreeNode right;

	TreeNode(int value) {
		this.value = value;
	}
}

九. 数据结构专栏

https://blog.csdn.net/weixin_53919192/category_11908333.html?spm=1001.2014.3001.5482https://blog.csdn.net/weixin_53919192/category_11908333.html?spm=1001.2014.3001.5482

后文有介绍二叉树以及非递归遍历二叉树的内容,有兴趣的朋友可以去看看:树形结构之非递归遍历二叉树 

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
二叉树是一种非常基础和重要的数据结构,对于理解递归和非递归遍历方式也有很大的帮助。本文将介绍二叉树的前序、中序、后序的递归和非递归遍历方式,并对它们进行比较和分析。 ### 前序遍历 前序遍历的顺序是:先遍历根节点,然后遍历左子树,最后遍历右子树。递归方式的代码如下: ```python def preorder_recursive(root): if not root: return print(root.val) preorder_recursive(root.left) preorder_recursive(root.right) ``` 非递归方式的代码如下: ```python def preorder_iterative(root): if not root: return stack = [root] while stack: node = stack.pop() print(node.val) if node.right: stack.append(node.right) if node.left: stack.append(node.left) ``` 可以看出,非递归方式使用了一个栈来模拟递归过程。首先把根节点入栈,然后每次取出栈顶元素,并打印它的值。如果有右孩子,就把右孩子入栈;如果有左孩子,就把左孩子入栈。这样,就能保证在遍历到一个节点时,它的左子树已经全部遍历完了。 ### 中序遍历 中序遍历的顺序是:先遍历左子树,然后遍历根节点,最后遍历右子树。递归方式的代码如下: ```python def inorder_recursive(root): if not root: return inorder_recursive(root.left) print(root.val) inorder_recursive(root.right) ``` 非递归方式的代码如下: ```python def inorder_iterative(root): if not root: return stack = [] node = root while stack or node: if node: stack.append(node) node = node.left else: node = stack.pop() print(node.val) node = node.right ``` 可以看出,非递归方式同样使用了一个栈来模拟递归过程。首先把根节点入栈,并把指针指向左子树的最底层。然后每次取出栈顶元素,并打印它的值。如果有右孩子,就把右孩子入栈;如果没有右孩子,就返回到它的父节点。 ### 后序遍历 后序遍历的顺序是:先遍历左子树,然后遍历右子树,最后遍历根节点。递归方式的代码如下: ```python def postorder_recursive(root): if not root: return postorder_recursive(root.left) postorder_recursive(root.right) print(root.val) ``` 非递归方式的代码如下: ```python def postorder_iterative(root): if not root: return stack = [root] res = [] while stack: node = stack.pop() res.append(node.val) if node.left: stack.append(node.left) if node.right: stack.append(node.right) return res[::-1] ``` 可以看出,非递归方式同样使用了一个栈来模拟递归过程。首先把根节点入栈,在遍历完左子树和右子树之后,把它的值加入到结果列表中。最后,把结果列表翻转,就得到了后序遍历的结果。 ### 比较和分析 递归和非递归遍历方式的时间复杂度都是 O(n),空间复杂度也都是 O(n)。但是,非递归方式需要使用一个栈来模拟递归过程,所以空间复杂度比递归方式要高。而且,非递归方式的代码比较难理解和实现,需要仔细分析。 另外,对于前序和后序遍历,非递归方式的代码比递归方式要复杂一些,需要添加一些特判来保证遍历顺序的正确性。而中序遍历的非递归方式比较简单,只需要一个栈和一个指针即可。 总的来说,递归方式比较简洁和易于理解,但是可能会因为递归深度过大而导致栈溢出。非递归方式虽然可以避免这个问题,但是代码比较复杂,需要一定的实现技巧和思维难度。所以,在实际应用中,根据具体情况选择合适的遍历方式比较重要。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hulake_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值