【数据结构与算法】——树

本文详细介绍了二叉树的三种遍历方式——前序遍历、中序遍历和后序遍历,包括递归和非递归实现,并给出了相关算法代码。此外,讨论了二叉搜索树的特点和在查找、添加和删除操作中的时间复杂度,强调了平衡二叉树的重要性。最后,提到了二叉树的广度优先搜索及其在完全二叉树中的应用。
摘要由CSDN通过智能技术生成

树的概念:一棵非空的树中有一个根节点,根节点上面不存在父节点,下面有若干子节点,子节点下面又有其自身的子节点,如果子节点下面不存在自身的子节点,那么该子节点为叶节点,这样的结构称为树。
二叉树:每个节点最多只有两个子节点,是一种典型的具有递归性质的数据结构,其根节点可能有子节点,子节点又是对应子树的根节点,它可能也有自己的子节点。

二叉树的数据结构可定义为:

public class TreeNode {
	int val;
	TreeNode left;
	TreeNode right;
	
	TreeNode (int x) {
		this.val = x;
	}
}

二叉树的深度优先搜索:

中序遍历:顺序:左子树、根节点、右子树
前序遍历:顺序:根节点、左子树、右子树
后序遍历:顺序:左子树、右子树、根节点

中序遍历
递归代码:

public List<Integer> inorderTraversal(TreeNode root) {
	List<Integer> nodes = new LinkedList<>();
	dfs(root, nodes);
	return nodes;
}

private void dfs(TreeNode root, List<Integer> nodes) {
	if (root != null) {
		dfs(root.left, nodes);
		nodes.add(root.val);
		dfs(root.right, nodes);
	}
}

缺点:可能会导致调用栈溢出的问题
把递归代码改写成迭代的代码通常需要用到栈
思想:顺着左子节点的指针方向找到最左节点,沿途遇到的节点压入栈中,找到最左节点后,依次弹出可得父节点并遍历,将当前指针指向父节点的右子节点,再遍历其右子节点,直到所有节点都遍历完为止。

public List<Integer> inorderTraversal (TreeNode root) {
	List<Integer> nodes = new LinkedList<>();
	Stack<TreeNode> stack = new Stack<>();
	TreeNode cur = root;
	while (cur != null || !stack.isEmpty()) {
		while (cur != null) {
			stack.push(cur);
			cur = cur.left;
		}
		cur = stack.pop();
		nodes.add(cur.val);
		cur = cur.right;
	}
	return nodes;
}

前序遍历
递归代码:

public List<Integer> preorderTraversal(TreeNode root) {
	List<Integer> nodes = new LinkedList<>();
	dfs(root, nodes);
	return nodes;
}

private void dfs(TreeNode root, List<Integer> nodes) {
	if (root != null) {
		node.add(root.val);
		dfs(root.left);
		dfs(root.right);
	}
}

与中序遍历的不同点:向下搜寻最左指针时沿途的节点边遍历边压入栈。

public List<Integer> preorderTraversal(TreeNode root) {
	List<Integer> result = new LinkedList<>();
	Stack<TreeNode> stack = new Stack<>();
	TreeNode cur = root;
	while (cur != null || !stack.isEmpty()) {
		while (cur != null) {
			result.add(cur.val);
			stack.push(cur);
			cur = cur.left;
		}
		cur = stack.pop();
		cur = cur.right;
	}
	return result;
}

后序遍历
递归代码:

public List<Integer> postorderTraversal(TreeNode root) {
	List<Integer> nodes = new LinkedList<>();
	dfs(root, nodes);
	return nodes;
}

private void dfs(TreeNode root, List<Integer> nodes) {
	if (root != null) {
		dfs(root.left, nodes);
		dfs(root.right, nodes);
		nodes.add(root.val);
	}
}

思想:根据其右子树之前有没有遍历过来确定是否应该遍历当前节点
如果此前右子树已经遍历过,那么在右子树中最后一个遍历的节点应该是右子树的根节点,也就是当前节点的右子节点。
可以记录遍历的前一个节点。如果一个节点存在右子节点并且右子节点正好等于前一个被遍历的的节点,说明它的右子节点已经被遍历过,可以遍历当前节点了。

public List<Integer> postorderTraversal(TreeNode root) {
	List<Integer> result = new LinkedList<>();
	Stack<TreeNode> stack = new Stack<>();
	TreeNode cur = root;
	TreeNode prev = null;
	while (cur != null || !stack.isEmpty()) {
		while (cur != null) {
			stack.push(cur);
			cur = cur.left;
		}
		cur = stack.peek();
		if (cur.right != null && cur.right != prev) {
			cur = cur.right;
		} else {
			result.add(cur.val);
			stack.pop();
			prev = cur;
			cur = null;//父节点已经存放至栈中
		}
	}
	return result;
}

例题
在这里插入图片描述
思路:可采用后序遍历,判断左右节点是否为null及该节点的值是否为0,如果符合条件的话,将该节点置为null
在这里插入图片描述
在这里插入图片描述
思路:前序遍历:根、左子树、右子树;中序遍历:左子树、根、右子树
根据前序遍历的最左边的根节点可以将中序遍历分为左右两树,即可获取左右两边子树的长度,再根据该长度将前序遍历数组也分为左右两树,递归,直到前序遍历左边界>右边界
技巧:为了能快速获知根在中序遍历中的位置可以使用HashMap进行存储
前序遍历
在这里插入图片描述
在这里插入图片描述
思路:后序遍历
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
思路:分三种情况:①两个节点分别在根左边和根右边;②一个节点是另外一个节点的父节点
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
思路:左边是平衡树,右边是平衡树,左右子树高度相差不超过1
在这里插入图片描述
在这里插入图片描述
思路:先找到与tree2根节点相等的点,再判断从这个点开始是否与tree1的子树匹配
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
二叉搜索树

二叉搜索树是一类特殊的二叉树,它的左子节点总是小于或等于根节点,而右子节点总是大于或等于根节点
由于中序遍历按照节点值递增的顺序遍历二叉搜索树的每个节点,因此中序遍历是解决二叉搜素树的最常用思路
在普通的二叉树中根据节点值查找对应的节点需要遍历这棵二叉树,因此时间复杂度为O(n)
如果二叉搜素树的高度为h,那么在二叉搜索树中根据节点值查找对应节点的时间复杂度为O(h)

public TreeNode searchBST(TreeNode root, int val) {
	TreeNode cur = root;
	while (cur != null) {
		if (cur.val == val) break;
		if (cur.val < val) cur = cur.right;
		else cur = cur.left;
	}
	return cur;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
中序遍历
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
思路:中序遍历:找出第一对左边大于右边的节点对,要交换的第一个节点为左边节点
找出第二对左边大于右边的节点对,要交换的第二个节点为右边节点
交换这两个节点的值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
二叉树的广度优先遍历

二叉树的广度优先搜索是从上到下按层遍历二叉树,从二叉树的根节点开始,依次遍历二叉树的每层。
通常基于队列来实现二叉树的广度优先搜索
从二叉树的根节点开始,先把根节点放入一个队列之中,然后每次从队列中取出一个节点遍历,如果该节点有左右子节点,则分别将它们添加到队列当中。重复这个过程,直到所有节点都遍历完为止,此时队列为空。
如果关于二叉树的面试题提到层这个概念时,可以尝试使用广度优先搜索来解决这个问题

实现二叉树广度优先搜索:

public List<Integer> bfs(TreeNode root) {
	Queue<TreeNode> queue = new LinkedList<>();
	if (root != null) {
		queue.offer(root);
	}
	List<Integer> result = new ArrayList<>();
	while (!queue.isEmpty()) {
		TreeNode node = queue.poll();
		result.add(node.val);
		if (node.left != null ) queue.offer(node.left);
		if (node.right != null) queue.offer(node.right);
	}
	return result;
}

时间复杂度为O(n),空间复杂度为O(n)
利用二叉树的广度优先搜索,很容易知道每层最左边或最右边的节点,或者每层的最大值、最小值等
在这里插入图片描述
思路:
完全二叉树特点:在完全二叉树中只有最下面的一层可能是不满的,其他层都是满的,如果最下面一层不是满的,则从左到右找到该层的第1个空缺位置并添加新的节点
在完全二叉树中添加新节点顺序看起来是从上到下按层从左到右添加的,所以我们可以每次在完全二叉树中按照广度优先搜索的顺序找出第1个左子节点或右子节点还有空缺的节点
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
思路:用一个变量来保存每一层最左边节点的值。在遍历二叉树时,每当遇到新的一层时就将变量的值更新为该层第一个节点的值。当遍历完整棵二叉树后,变量的值就是最后一次更新的值,也就是最后一层的第1个节点的值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
思路:二叉树的右侧视图其实就是从上到下每层最右边的节点
在这里插入图片描述
在这里插入图片描述

如果需要区分二叉树不同的层,那么至少有两种方法可以实现。第一种方法是用两个变量来表示当前层和下一层节点的数目。如果当前遍历的层的节点数目变成0,那么这一层所有的节点都已经遍历完,可以开始遍历下一层的节点。第二种方法是用两个队列分别存放当前层和下一层的节点。如果当前层对应的队列被清空,那么该层所有的节点就已经被遍历完,可以开始遍历下一层

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
TreeSet和TreeMap的应用

如果二叉搜索树有n个节点,深度为h,那么查找、添加和删除操作的时间复杂度都是O(h).
如果二叉搜索树是平衡的,那么深度h近似等于logn
但在极端情况下(如每个节点只有一个子节点),树的深度h等于n-1,此时二叉搜索树的查找、添加和删除操作的时间复杂度都退化成O(n)
二叉搜索树是否平衡对二叉搜索树的时间效率至关重要
Java根据红黑树这种平衡的二叉树实现TreeSet和TreeMap两种数据结构

TreeSet实现了接口Set,它内部的平衡二叉树中的每个节点只包含一个值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值