Java实现二叉树的遍历

二叉树的基本概念

  1. 概念
  • 二叉树是一种特殊的树。二叉树的每个结点最多有两个子结点,左边的叫左子结点,右边的叫右子结点
  • 二叉树要么为空,要么由根结点、左子树、右子树组成,而左子树和右子树分别是一颗二叉树
  1. 特点
  • 若一个父节点的下标是parent,它的左孩子节点下标是2 × parent + 1 , 右孩子节点下标是2 × parent + 2
  • 若一个左孩子节点的下标是leftChild,它的父节点下标是(leftChild - 1)/ 2

二叉树的存储结构

  1. 顺序存储结构——数组
  • 使用数组存储,按照某种顺序把二叉树的节点放到数组中对应的位置上
  • 如果某一个节点的左子结点或右子结点空缺,则数组的相应位置也空出来
  • 为什么这样设计呢???因为这样可以更方便地在数组中定位二叉树的子结点和父结点

在这里插入图片描述

  1. 链式存储结构——类
  • 根据面向对象,将二叉树的结点抽象为一个类
  • 类中包含当前结点的数据,以及对左子结点和右子结点的引用

在这里插入图片描述

/**
 * 树的节点
 */
 class TreeNode {
	int data;  //当前结点的数据
	TreeNode leftChild;  // 左子结点
	TreeNode rightChild; // 右子结点

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

如何构建一颗二叉树?

  • 构建二叉树的过程,实际上是将一组顺序的值 依次 转化为node结点的过程。
  • 再简单点,就是把数组中的值,转换为TreeNode对象的过程

在这里插入图片描述

  • 那么,如图所示的二叉树,按照一定的顺序(先序遍历)转换为数组
329nullnull10nullnull8null4
  • java构建二叉树
	/**
	 * 创建二叉树:3,2,9,null,null,10,null,null,8,null,4
	 */
	public static TreeNode createBinaryTree(LinkedList<Integer> inputList) {
		TreeNode node = null;
		if (inputList == null || inputList.isEmpty()) {
			return null;
		}
		Integer data = inputList.removeFirst();
		if (data != null) {
			node = new TreeNode(data);
			node.leftChild = createBinaryTree(inputList);
			node.rightChild = createBinaryTree(inputList);
		}
		return node;
	}

二叉树的遍历方式

深度优先遍历(前序遍历、中序遍历、后序遍历)

  1. 前序遍历 (根、左、右)
    在这里插入图片描述
  • 所谓的前序遍历就是先访问根节点,再访问左节点,最后访问右节点
	/**
	 * 先序遍历 - 递归实现方式
	 * @param node
	 */
	public static void preOrder(TreeNode node) {
		if (node == null) {
			return;
		}
		System.out.print(node.data + " ");
		preOrder(node.leftChild);
		preOrder(node.rightChild);
	}

先序遍历-栈的实现思路:

  • 从根结点开始入栈,获取当前结点的左子树入栈,直到最左,没有左子结点为止
  • 如果栈不为空,将栈中结点出栈,并找到该结点的右子结点,以该结点为根结点继续循环
  • 如果该结点没有右子结点,且栈不为空,则继续出栈,并找到该结点的右子结点继续循环
  • 关键在于,在找左子结点的过程中,打印结点的值
	/**
	 * 先序遍历-栈的实现方式
	 * @param node
	 */
	public static void preOrderStack(TreeNode node) {
		Stack<TreeNode> stack = new Stack<>();
		TreeNode tempNode = node;
		while (tempNode != null || !stack.isEmpty()) {
			// 左子树
			while (tempNode != null) {
				System.out.print(tempNode.data + " ");
				stack.push(tempNode);
				tempNode = tempNode.leftChild;
			}
			// 右子树
			if (!stack.isEmpty()) {
				tempNode = stack.pop();
				tempNode = tempNode.rightChild;
			}
		}
		System.out.println();
	}
  1. 中序遍历 (左、根、右)
    中序遍历
  • 所谓的中序遍历就是先访问左节点,再访问根节点,最后访问右节点
	/**
	 * 中序遍历 - 递归实现方式
	 * @param node
	 */
	public static void midOrder(TreeNode node) {
		if (node == null) {
			return;
		}
		midOrder(node.leftChild);
		System.out.print(node.data + " ");
		midOrder(node.rightChild);
	}

中序遍历-栈的实现思路:

  • 从根结点开始入栈,获取当前结点的左子树入栈,直到最左,没有左子结点为止
  • 如果栈不为空,将栈中结点出栈,并找到该结点的右子结点,以该结点为根结点继续循环
  • 如果该结点没有右子结点,且栈不为空,则继续出栈,并找到该结点的右子结点继续循环
  • 关键在于,在找右子结点之前,打印结点的值
	/**
	 * 中序遍历-栈的实现方式
	 * @param node
	 */
	public static void midOrderStack(TreeNode node) {
		Stack<TreeNode> stack = new Stack<>();
		TreeNode tempNode = node;
		while (tempNode != null || !stack.isEmpty()) {
			// 左节点
			while (tempNode != null) {
				stack.push(tempNode);
				tempNode = tempNode.leftChild;
			}

			// 右节点
			if (!stack.isEmpty()) {
				tempNode = stack.pop();
				System.out.print(tempNode.data + " ");
				tempNode = tempNode.rightChild;
			}

		}
		System.out.println();
	}
  1. 后序遍历 (左、右、根)
    后序遍历
  • 所谓的后序遍历就是先访问左节点,再访问右节点,最后访问根节点
	/**
	 * 后序遍历 - 递归实现方式
	 * @param node
	 */
	public static void lastOrder(TreeNode node) {
		if (node == null) {
			return;
		}
		lastOrder(node.leftChild);
		lastOrder(node.rightChild);
		System.out.print(node.data + " ");
	}

后序遍历-栈的实现思路:

  • 从根结点开始入栈,获取当前结点的左子树入栈,直到最左,没有左子结点为止
  • 从栈顶开始遍历栈中结点,判断栈顶元素是否存在右子结点
  • 如果存在并且没有被访问,则将右子结点入栈;否则,就访问栈顶元素
  • 其关键就在于判断是否该结点的右子结点是否被访问过
	/**
	 * 后序遍历-栈的实现方式
	 * @param node
	 */
	public static void lastOrderStack(TreeNode node) {
		Stack<TreeNode> stack = new Stack<>();
		TreeNode tempNode = node;
		TreeNode preNode = null;
		while (tempNode != null || !stack.isEmpty()) {

			// 左节点
			while (tempNode != null) {
				stack.push(tempNode);
				tempNode = tempNode.leftChild;
			}

			// 右节点
			if (!stack.isEmpty()) {
				// 取栈顶的元素
				tempNode = stack.peek();
				// 没有右子结点,或者右子结点已经被访问过
				if (tempNode.rightChild == null || tempNode.rightChild == preNode) {
					// 可以访问栈顶元素
					tempNode = stack.pop();
					System.out.print(tempNode.data + " ");
					// 标记上一次访问的节点
					preNode = tempNode;
					tempNode = null;
				}
				// 存在没有被访问的右子结点
				else {
					tempNode = tempNode.rightChild;
				}

			}
		}
	}

广度优先遍历(层序遍历)

层序遍历

  • 所谓的层序遍历就是从根结点从上往下逐层遍历,在同一层,按左到右的顺序逐个访问

实现思路

  • 利用队列先进先出的特征,先让根结点入队
  • 遍历队列,并打印出根结点的值
  • 如果根结点存在左右子结点,将根结点的左右子结点依次按照从左到右的顺序入队
  • 根据队列先进先出的特征,会一层一层出队并打印结点的值
	/**
	 * 层序遍历 - 队列实现方式
	 * @param node
	 */
	public static void layerOrder(TreeNode root) {
		Queue<TreeNode> queue = new LinkedList<TreeNode>();
		queue.offer(root);  //入队
		while (!queue.isEmpty()) {
			TreeNode node = queue.poll();  //出队
			System.out.print(node.data + " ");
			if (node.leftChild != null) {
				queue.offer(node.leftChild); //左子节点入队
			}
			if (node.rightChild != null) {
				queue.offer(node.rightChild);//右子节点入队
			}
		}
	}

运行代码

	public static void main(String[] args) {
		LinkedList<Integer> list = new LinkedList<>(Arrays.asList(3, 2, 9, null, null, 10, null, null, 8, null, 4));
		TreeNode node = createBinaryTree(list);

		System.out.print("先序遍历-递归:");
		preOrder(node);
		System.out.println();
		
		System.out.print("中序遍历-递归:");
		midOrder(node);
		System.out.println();
		
		System.out.print("后序遍历-递归:");
		lastOrder(node);
		System.out.println();
		
		System.out.print("先序遍历-栈:");
		preOrderStack(node);
		
		System.out.print("中序遍历-栈:");
		midOrderStack(node);
		
		System.out.print("后序遍历-栈:");
		lastOrderStack(node);
		System.out.println();
		
		System.out.print("层序遍历:");
		layerOrder(node);
	}

}

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小妖云汐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值