二叉树及其算法
树的概念
- 树-是n(n>=0)个结点的有限集。n=0时称为空树.
- 根节点-在一颗非空树中根节点有且只有一个
- 叶节点或终端节点-无子节点(度为零)的节点:F
- 分支节点或非分支节点-有子节点(度不为0)的节点: B, C
- 子树-当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、…、Tn,其中每一个集合本身又是一棵树,并且称为根的子树.
- 结点的度-节点所拥有的子节点的数目称为节点的度
- 节点关系-
子节点为其根节点的孩子节点(子节点),
根节点为其双亲结点(父节点)
同一双亲结点下的孩子节点为兄弟节点
节点的祖先为从根到该节点上所有锁经节点
节点的子孙为其子树中任一节点 - 节点层次-每一行为一层
在这里插入代码片
二叉树
1. 二叉树图形特点
- 二叉树定义- 或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成.
- 二叉树的度-每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点.
- 子树
-左子树和右子树是有顺序的,次序不能任意颠倒。
-即使树中某结点只有一棵子树,也要区分它是左子树还是右子树
2.二叉树的数学特点
1)在二叉树的第i层上最多有2^(i-1) 个节点 。(i>=1)
2)二叉树中如果深度为k,那么最多有(2^k)-1个节点。(k>=1)
3)n0=n2+1 n0表示度数为0的节点数,n2表示度数为2的节点数。
4)在完全二叉树中,具有n个节点的完全二叉树的深度为[log2n]+1,其中[log2n]是向下取整。
5)若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点有如下特性:
1. 若 i=1,则该结点是二叉树的根,无双亲, 否则,编号为 [i/2] 的结点为其双亲结点;
2. 若 2i>n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子结点;
3. 若 2i+1>n,则该结点无右孩子结点, 否则,编号为2i+1 的结点为其右孩子结点。
3. 二叉树的种类
- 左斜🌲
- 右斜🌲
- 满二叉树
在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
满二叉树的特点有:
1)叶子只能出现在最下一层。出现在其它层就不可能达成平衡。
2)非叶子结点的度一定是2。
3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多 - 完全二叉树 实际上就是从最后一层从右至左依次删除满二叉树的叶节点即可
对一颗具有n个结点的二叉树按层编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
1)叶子结点只能出现在最下层和次下层。
2)最下层的叶子结点集中在树的左部。
3)倒数第二层若存在叶子结点,一定在右部连续位置。
4)如果结点度为1,则该结点只有左孩子,即没有右子树。
5)同样结点数目的二叉树,完全二叉树深度最小。
注:满二叉树一定是完全二叉树,但反过来不一定成立。
4. 二叉树的遍历方式
4.1 先序遍历
当第一次到达结点时就输出结点数据,按照先向左在向右的方向访问.
ABDHIEJCFG
// 递归:自己调用自己
public void preOrderTraverse1(TreeNode root) {
if (root != null) {
System.out.print(root.val+" ");
preOrderTraverse1(root.left);//每一层都先运行了左边的直至左边运行完
preOrderTraverse1(root.right);//从右边开始从下往上运行到根节点的右边
}
}
借助堆栈先入后出的优势进行上到下的遍历 通过定义PNode不为空进堆栈并等于左子节点,为空父节点出堆栈等于右子节点来实现从左到右的遍历
// 迭代-while循环借助堆栈
public void preOrderTraverse2(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode pNode = root;
while (pNode != null || !stack.isEmpty()) {
if (pNode != null) {
System.out.print(pNode.val+" ");
stack.push(pNode);
pNode = pNode.left;
} else { //pNode == null && !stack.isEmpty()
TreeNode node = stack.pop();
pNode = node.right;
}
}
}
4.2 中序遍历
其实和前序排列的前进方向相同只不过是第二次到达在输出所以从叶子节点开始的
HDIBJEAFCG
// 递归只是输出位置变了
public void inOrderTraverse1(TreeNode root) {
if (root != null) {
inOrderTraverse1(root.left);
System.out.print(root.val+" ");
inOrderTraverse1(root.right);
}
}
当前节点不为空,当前节点入栈,当前节点向左走
只有当前节点为空时,才弹出一个节点(出栈),打印,然后当前节点向右走
// 迭代 只是输出位置变了
public void inOrderTraverse2(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode pNode = root;
while (pNode != null || !stack.isEmpty()) {
if (pNode != null) {
stack.push(pNode);
pNode = pNode.left;
} else { //pNode == null && !stack.isEmpty()
TreeNode node = stack.pop();
System.out.print(node.val+" ");//第二次到根节点
pNode = node.right;
}
}
}
4.3 后序排列
和前中序的访问顺序仍然相同,不同的是第三次访问节点再输出
从根结点出发,则第一次到达结点A,故输出A;
继续向左访问,第一次访问结点B,故输出B;
按照同样规则,输出D,输出H;
当到达叶子结点H,返回到D,此时已经是第二次到达D,故不在输出D,进而向D右子树访问,D右子树不为空,则访问至I,第一次到达I,则输出I;
I为叶子结点,则返回到D,D左右子树已经访问完毕,则返回到B,进而到B右子树,第一次到达E,故输出E;
向E左子树,故输出J;
按照同样的访问规则,继续输出C、F、G;
HIDJEBFGCA
// 递归只是输出位置变了
public void inOrderTraverse1(TreeNode root) {
if (root != null) {
inOrderTraverse1(root.left);
inOrderTraverse1(root.right);
System.out.print(root.val+" "); //左右节点各访问一遍就是第三次了
}
}
因为先序遍历的顺序是 中左右, 后序遍历的顺序是 左右中
采用先序遍历的变形,首先实现一个 中右左即改写的前序排列
然后加入list后倒序输出
public void postOrderTraverse(Node node) {
if (node == null) {
return;
}
Node temp = node;
Stack<Node> stack = new Stack<>();
List<Node> nodes = new ArrayList<>();
while (temp != null || !stack.isEmpty()) {
if (temp != null) {
nodes.add(temp);
stack.push(temp);
temp = temp.rightChild;
} else {
Node popNode = stack.pop();
temp = popNode.leftChild;
}
}
for (int i = nodes.size() - 1; i >= 0; i--) {
System.out.print(nodes.get(i).data + " ");
}
}
4.4 层次遍历
用LinkedList 的 deque接口实现方法 offer和poll实现先入先出
public void levelTraverse(TreeNode root) {
if (root == null) {
return;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
System.out.print(node.val+" ");
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
4.5 深度优先遍历
就是前序遍历
5. 二叉树的其他算法
5.1 对称树
从根开始为空则返回true
若左右节点不同时过叶节点则返回false
若左右节点不等则返回false
若左右节点同时越过子节点则返回true
class Solution {
public boolean isSymmetric(TreeNode root) {
return root == null ? true : recur(root.left, root.right);
}
boolean recur(TreeNode L, TreeNode R) {
if(L == null && R == null) return true;
if(L == null || R == null || L.val != R.val) return false;
return recur(L.left, R.right) && recur(L.right, R.left);
}
}
5.2 平衡高度二叉树
想写个获得任意节点高度的function
然后先判断根节点的高度是否平衡
进而判断左子节点和右子节点是否高度平衡
class Solution {
// Recursively obtain the height of a tree. An empty tree has -1 height
private int height(TreeNode root) {
// An empty tree has height -1
if (root == null) {
return -1;
}
return 1 + Math.max(height(root.left), height(root.right));
}
public boolean isBalanced(TreeNode root) {
// An empty tree satisfies the definition of a balanced tree
if (root == null) {
return true;
}
// Check if subtrees have height within 1. If they do, check if the
// subtrees are balanced
return Math.abs(height(root.left) - height(root.right)) < 2
&& isBalanced(root.left)
&& isBalanced(root.right);
}
};
5.3 监控树
核心为从下往上遍历同时尽可能把摄像机放到父节点
1 如果该节点的孩子节点未全部覆盖则增加摄像头
2 如果该节点没有父节点且未被覆盖则增加摄像头
class Solution {
int ans;
Set<TreeNode> covered;
public int minCameraCover(TreeNode root) {
ans = 0;
covered = new HashSet();
covered.add(null);
dfs(root, null);
return ans;
}
public void dfs(TreeNode node, TreeNode par) {
if (node != null) {
dfs(node.left, node);
dfs(node.right, node); //后序遍历
if (par == null && !covered.contains(node) ||
!covered.contains(node.left) ||
!covered.contains(node.right)) {
ans++;//优先放到父节点
covered.add(node);
covered.add(par);
covered.add(node.left);
covered.add(node.right);
}
}
}
}
5.4 验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
class Solution {
public boolean isValidBST(TreeNode root) {
return dfs(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
boolean dfs(TreeNode node, long lower, long upper) {
if(node!=null){
System.out.print(lower);
System.out.print("-----");
System.out.print(node.val);
System.out.print("----");
System.out.println(upper);
}
if (node == null)
return true;
if (node.val <= lower)
return false;
if (node.val >= upper)
return false;
if (!dfs(node.left, lower, node.val))
return false;
if (!dfs(node.right, node.val, upper))
return false;
return true;
}
}