【数据结构】二叉树

本文详细介绍了二叉树的前序、中序、后序遍历(递归与非递归实现),层次遍历,以及如何验证一个数是否为搜索二叉树。此外,还讨论了在二叉树中寻找节点的后继和前驱节点的方法,并提供了几个经典的LeetCode二叉树问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实现二叉树前、中、后序以及按层遍历

1、递归版:

package codingTest5;

public class PreInPosTraversal {
	public static class Node{
		int val;
		Node left;
		Node right;
		
		public Node() {}
		
		public Node(int data) {
			this.val = data;
		}
	}
	
	public static void PreTraversal(Node head) {
		if(head == null) {
			return;
		}
		System.out.print(head.val + " ");
		PreTraversal(head.left);
		PreTraversal(head.right);
	}
	
	public static void InTraversal(Node head) {
		if(head == null) {
			return;
		}
		InTraversal(head.left);
		System.out.print(head.val + " ");
		InTraversal(head.right);
	}
	
	public static void PosTraversal(Node head) {
		if(head == null) {
			return;
		}
		PosTraversal(head.left);
		PosTraversal(head.right);
		System.out.print(head.val + " ");
	}
	
	public static void main(String[] args) {
		Node head = new Node(3);
		head.left = new Node(4);
		head.left.left = new Node(5);
		head.right = new Node(6);
		head.right.right = new Node(7);
		PreTraversal(head);
		System.out.println("----");
		Node head2 = new Node(3);
		head2.left = new Node(4);
		head2.left.left = new Node(5);
		head2.right = new Node(6);
		head2.right.right = new Node(7);
		InTraversal(head2);
		System.out.println("----");
		Node head3 = new Node(3);
		head3.left = new Node(4);
		head3.left.left = new Node(5);
		head3.right = new Node(6);
		head3.right.right = new Node(7);
		PosTraversal(head3);
		System.out.println("----");
	}
}

2、非递归版 

使用栈结构的原因:

        当前节点走向左孩子,走向右孩子,只有从上到下,从左到右,而没有逆序的方式。而栈具有天然的“先进后出”的特性,因此选择它。

1)先序遍历:

先序遍历是:根--左--右

如果一开始头结点不为空:

        首先新建一个栈,把根节点入栈;

        当栈不为空时,

        每次都使栈顶元素弹出,打印该元素的值。

        并访问该元素,如果该元素的右子树存在,就先将右元素入栈;如果该元素的左子树存在,就将该元素入栈。这样弹出的

时候,就是先弹左再弹右。

2)中序遍历:

中序遍历是:左--根--右

如果一开始头结点不为空:

        新建一个栈,

        如果栈不为空,且当前节点不为空,就继续循环:

        (1)当前节点不为空,则当前节点入栈;当前节点往它的左子节点移动;

        (2)当前节点为空,则弹出栈顶元素作为当前的头结点,打印该结点的值,并将当前节点往其右子节点移动。

3)后序遍历:

后序遍历是:左--右--根

        前序(根--左--右)遍历入栈的顺序是先右再左。此时我们想到的是,可以反过来,先左再右。这样就可以做到中--左--右。

这样把这个存到栈里,再一一弹出,就可以得到我们后序遍历所需的“左--右--根”。每次先不打印,而是先把要打印的数,存到

一个栈里,然后依次弹出并打印栈中元素。

如果一开始头结点不为空:

        准备两个栈:

        栈1,头结点先存入栈1。

        只要栈1不为空时,弹出栈中元素,并将该元素存入栈2。左不为空先压左,右不为空再压右,直到结束循环。

        当栈2不为空时,逐个弹出栈内元素。

package codingTest5;

import java.util.Stack;

import codingTest5.PreInPosTraversal.Node;

public class PreInPosTraversal2 {
	private static void PreTraversal(Node head) {
		System.out.println("PosTraversal:");
		if (head != null) {
			Stack<Node> stack = new Stack<Node>();
			stack.push(head);
			while (!stack.isEmpty()) {
				head = stack.pop();
				System.out.print(head.val);
				if (head.right != null) {
					stack.push(head.right);
				}

				if (head.left != null) {
					stack.push(head.left);
				}
			}
		}

		System.out.println();
	}

	private static void InTraversal(Node head) {
		System.out.println("InTraversal:");
		if(head != null) {
			Stack<Node> stack = new Stack<Node>();
			while(!stack.isEmpty() || head != null) {
				if(head != null) {
					stack.push(head);
					head = head.left;
				}else {
					head= stack.pop();
					System.out.print(head.val);
					head = head.right;
				}
			}
			System.out.println();
		}
		
	}

	private static void PosTraversal(Node head) {
		System.out.println("PosTraversal:");
		if(head != null) {
			Stack<Node> stack1 = new Stack<>();
			Stack<Node> stack2 = new Stack<>();
			stack1.push(head);
			while(!stack1.isEmpty()) {
				head = stack1.pop();
				stack2.push(head);
				if(head.left != null) {
					stack1.push(head.left);
				}
				if(head.right != null) {
					stack1.push(head.right);
				}
			}
			
			while(!stack2.isEmpty()) {
				System.out.print(stack2.pop().val);
			}
		}
		System.out.println();
	}

	public static void main(String[] args) {
		Node head = new Node(1);
		head.left = new Node(2);
		head.left.left = new Node(4);
		head.left.right = new Node(5);
		head.right = new Node(3);
		head.right.left = new Node(6);
		head.right.right = new Node(7);
		PreTraversal(head);
		System.out.println("----");
		Node head2 = new Node(1);
		head2.left = new Node(2);
		head2.left.left = new Node(4);
		head2.left.right = new Node(5);
		head2.right = new Node(3);
		head2.right.left = new Node(6);
		head2.right.right = new Node(7);
		InTraversal(head2);
		System.out.println("----");
		Node head3 = new Node(1);
		head3.left = new Node(2);
		head3.left.left = new Node(4);
		head3.left.right = new Node(5);
		head3.right = new Node(3);
		head3.right.left = new Node(6);
		head3.right.right = new Node(7);
		PosTraversal(head3);
		System.out.println("----");
	}

}

层次遍历

BFS   

package codingTest5;

import java.util.LinkedList;
import java.util.Queue;

import codingTest5.PreInPosTraversal.Node;

public class levelTraversal {
	public static class Node{
		int val;
		Node left;
		Node right;
		
		public Node() {}
		
		public Node(int data) {
			this.val = data;
		}
	}
	
	public static void levelTraversal(Node node) {
		Queue<Node> queue = new LinkedList<Node>();
		if(node != null) {
			queue.add(node);
			while(!queue.isEmpty()) {
				Node now = queue.poll();
				System.out.print(now.val + " ");
				if(now.left != null) {
					queue.add(now.left);
				}
				if(now.right != null) {
					queue.add(now.right);
				}
			}
		}
	}
	
	public static void main(String[] args) {
		Node head = new Node(1);
		head.left = new Node(2);
		head.left.left = new Node(4);
		head.left.right = new Node(5);
		head.right = new Node(3);
		head.right.left = new Node(6);
		head.right.right = new Node(7);
		levelTraversal(head);
	}
}

 

 

验证一个数是否为搜索二叉树:

1、搜索二叉树:

        二叉搜索树上任意一个节点为头的子树,左子树都比它小,右子树都比它大。即有:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

中序遍历下,二叉树的节点数值是依次升序的,那么这种树就是搜索二叉树。(通常搜索二叉树中不包含重复节点)

所以可以考虑改一个非递归版的中序遍历,在遍历的过程中,保证其升序即可。将打印的时机,替换为比较的时机即可。用有一个变量记录上一个变量是啥。就可以判断,中序遍历的过程中,是否是升序的。

package codingTest5;

import java.util.Stack;

import codingTest5.precursorNode.Node;

public class isValidBST {
	public static class TreeNode {
		int val;
		TreeNode left;
		TreeNode right;

		TreeNode(int x) {
			val = x;
		}
	}
	
	public static boolean isValidBST(TreeNode node) {
		if(node == null) {
			return true;
		}
		
		Stack<TreeNode> stack = new Stack<TreeNode>();
		TreeNode cur = node;
		TreeNode pre = null;
		
		while(!stack.isEmpty() || cur != null) {
			if(cur != null) {
				stack.push(cur);
				cur = cur.left;
			}else{
				cur = stack.pop();
				if(pre != null && cur.val <= pre.val) {
					return false;
				}
				pre = cur;
				cur = cur.right;
			}
		}
		return true;
	}
	

 

在二叉树中寻找某个节点的后继、前驱节点:

1、定义:

后继节点:在二叉树中序遍历的序列中,一个节点的下一个节点叫做它的后继节点;

前驱节点:在二叉树中序遍历的序列中,一个节点的前一个节点叫做它的前驱节点。

2、节点的结构

Node类中包含一个Node类型的parent指针。每一个节点可以通过Node指针找到它的父节点。(头结点的父节点总会指向null)

3、寻找后继节点:

如果一个节点有右子树,那么这个节点的后继节点,一定是右子树上最左边的节点;

如果一个节点没有右子树,那么这个节点的后继节点,则:

        假设当前节点是其父节点的左孩子,那么其父节点就是当前节点的后继节点;

        假设当前节点是其父节点的右孩子,那么就逐个向上找,直到找到当前节点是其父节点的左孩子时,这个父节点就是起始节点的后继。

package codingTest5;

public class successorNode {
	public static class Node{
		int val;
		Node left;
		Node right;
		Node parent;
		
		public Node(int data) {
			this.val = data;
		}
	}
	
	public static Node getSuccessorNode(Node node) {
		if(node == null) {
			return node;
		}
		
		if(node.right != null) {
			return getLeftMost(node.right);
		}else {
			Node parent = node.parent;
			while(parent != null && parent.left != node ) {
				node = parent;
				parent = node.parent;
			}
			return parent;
		}
	}

	private static Node getLeftMost(Node node) {
		if(node == null) {
			return node;
		}
		while(node.left != null) {
			node = node.left;
		}
		return node;
	}
	
	public static void main(String[] args) {
		Node head = new Node(6);
		head.parent = null;
		head.left = new Node(3);
		head.left.parent = head;
		head.left.left = new Node(4);
		head.left.left.parent = head.left;
		head.left.right = new Node(5);
		head.left.right.parent = head.left;
		head.right = new Node(7);
		head.right.parent = head;
		
		Node test = head.left;
		System.out.println(test.val + " next: " + getSuccessorNode(test).val);
		
		Node test2 = head.right;
		System.out.println(test2.val + " next: " + getSuccessorNode(test2));
	}
}

4、寻找前驱节点:

如果一个节点有左子树,那么这个节点的前驱节点,一定是左子树上最右边的节点;

如果一个节点没有左子树,那么这个节点的前驱节点,则:

        假设当前节点是其父节点的右孩子,则该父节点就是当前节点的前驱节点;

        假设当前节点是其父节点的左孩子,则逐个往上寻找,知道找到当前节点是其父孩子的右孩子时,这个父节点就是起始节点的前驱。

package codingTest5;

import codingTest5.successorNode.Node;

public class precursorNode {
	public static class Node{
		int val;
		Node parent;
		Node left;
		Node right;
		
		public Node(int data) {
			this.val = data;
		}
	}
	
	public static Node precursorNode(Node node){
		if(node == null) {
			return node;
		}
		
		if(node.left != null) {
			return getMostRight(node.left);
		}else {
			Node parent = node.parent;
			while(parent != null && parent.right != node) {
				node = parent;
				parent = node.parent;
			}
			return parent;
		}
	}
	
	public static Node getMostRight(Node node) {
		if(node == null) {
			return null;
		}
		while(node.right != null) {
			node = node.right;
		}
		
		return node;
	}
	
	public static void main(String[] args) {
		Node head = new Node(6);
		head.parent = null;
		head.left = new Node(3);
		head.left.parent = head;
		head.left.left = new Node(4);
		head.left.left.parent = head.left;
		head.left.right = new Node(5);
		head.left.right.parent = head.left;
		head.right = new Node(7);
		head.right.parent = head;
		
		Node test = head.left.left;
		System.out.println(test.val + " before: " + precursorNode(test));
		
		Node test1 = head.left;
		System.out.println(test1.val + " before: " + precursorNode(test1).val);

		Node test2 = head.left.right;
		System.out.println(test2.val + " before: " + precursorNode(test2).val);
		
		Node test3 = head;
		System.out.println(test3.val + " before: " + precursorNode(test3).val);

		Node test4 = head.right;
		System.out.println(test4.val + " before: " + precursorNode(test4).val);


	}
}

 

 

leetcode

1、Maximum Depth of Binary Tree(二叉树的最大深度)

package codingTest5;

import java.util.LinkedList;
import java.util.Queue;

/*
 * 
 * 给定一个二叉树,找出其最大深度。
 * 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
 * 
 * */
public class maxDepthofBinaryTree {
	public static class TreeNode{
		int val;
		TreeNode left;
		TreeNode right;
		
		public TreeNode(int data) {
			this.val = data;
		}
		
	}
	
	//非递归版
	//利用层次遍历,记录总层数,即为二叉树的总深度
	public static int maxDepthofBinaryTree2(TreeNode root) {
		if(root == null) {
			return 0;
		}
		
		Queue<TreeNode> queue = new LinkedList<TreeNode>();
		int res = 0;
		queue.offer(root);
		while(!queue.isEmpty()) {
			++res;
			for(int i = queue.size(); i > 0; --i) {
				TreeNode t = queue.poll();
				if(t.left != null) {
					queue.offer(t.left);
				}
				if(t.right != null) {
					queue.offer(t.right);
				}
			}
		}
		
		return res;
	}
	
	
	//递归版
	public static int maxDepthofBinaryTree1(TreeNode root) {
		//为什么之前这里写成:
		//int left, right=0;
		//while(root != null){
		//left = maxDepthofBinaryTree(root.left);
	    //right = maxDepthofBinaryTree(root.right);
		//}
		//就不对呢? 
		
		if(root == null) {
			return 0;
		}
		
	    int left = maxDepthofBinaryTree1(root.left);
	    int right = maxDepthofBinaryTree1(root.right);
		
		int maxDepth =  left >= right? left + 1: right + 1;
		
		return maxDepth;
	}
	
	public static void main(String[] args) {
		TreeNode head = new TreeNode(5);
		head.left = new TreeNode(6);
		head.left.left = new TreeNode(6);
		head.right = new TreeNode(1);
		System.out.println(1);
		//System.out.print(maxDepthofBinaryTree1(head));
		System.out.print(maxDepthofBinaryTree2(head));
	}
}

2、Invert Binary Tree(翻转二叉树)

package codingTest5;

import java.util.LinkedList;
import java.util.Queue;

public class InvertBinaryTree {
	public class TreeNode{
		int val;
		TreeNode left;
		TreeNode right;
		
		public TreeNode(int data) {
			this.val = data;
		}
	}
	
	//递归版本:
	//先序遍历这棵树的每个节点,如果遍历到的节点有子节点,就交换它的两个子节点;
	//当交换完所有非叶节点的左右子节点后,就可以的得到树的镜像。
	//之前一直有bug,是因为我还写了这样一句判断:
	//if(root.left == null || root.right == null){
	//  return null; 
	//}
	//可是注意:当根节点只有一个数,且没有左右节点的时候,它就直接返回null了,这是不对的!应该返回该节点自身!
//	public static TreeNode invertTree(TreeNode root) {
//		if(root == null) {
//			return null;
//		}
//		
//		TreeNode temp = root.left;
//		root.left = root.right;
//		root.right = temp;
//		
//		if(root.left != null) {
//			invertTree(root.left);
//		}
//		
//		if(root.right != null) {
//			invertTree(root.right);
//		}
//		
//		return root;
//	}
	
	
	//非递归版
	//使用一个队列来辅助遍历树
	//按层遍历树的节点,一边遍历一边交换当前节点的左右节点
	//层次遍历,根节点不为 null 将根节点入队,
	//判断队不为空时,节点出队,
	//交换该节点的左右孩子,如果左右孩子不为空,将左右孩子入队
	public static TreeNode invertTree(TreeNode root) {
		if(root == null) {return null;}
		Queue<TreeNode> queue = new LinkedList<TreeNode>();
		queue.add(root);
		
		while(!queue.isEmpty()) {
			TreeNode current = queue.poll();//移除并返问队列头部的元素
			TreeNode temp = current.left;
			current.left = current.right;
			current.right = temp;
			if(current.left != null) {
				queue.add(current.left);
			}
			if(current.right != null) {
				queue.add(current.right);
			}
		}
		
		return root;
	}
	
}

 

3、Validate Binary Search Tree(验证二叉查找树) 与上面复习的部分一样,不再赘述

4、Path Sum(路径总和)

package codingTest5;

import codingTest5.maxDepthofBinaryTree.TreeNode;

/**
 * 
 *给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
 *说明: 叶子节点是指没有子节点的节点。
 *示例: 
 *给定如下二叉树,以及目标和 sum = 22,

              5.
             /  \
            4.   8
           /    / \
          11.   13  4
         /  \        \
        7    2.       1
 *返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。
 * 
 * */

public class PathSum {
	public static class TreeNode{
		int val;
		TreeNode left;
		TreeNode right;
		
		public TreeNode(int data) {
			this.val = data;
		}
	}
	
	public static boolean hasPathSum(TreeNode root, int sum) {
		if(root == null) {
			return false;
		}
		
		if(root.val == sum && root.left == null && root.right == null) {
			return true;
		}else {
			return(hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val));
		}
	 }
	
	public static void main(String[] args) {
		TreeNode head = new TreeNode(5);
		head.left = new TreeNode(6);
		head.left.left = new TreeNode(6);
		head.right = new TreeNode(1);
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值