系列文章目录
一、二叉树的概念
1.1为什么会存在树结构?
高效的查找与搜素语义:例如企业管理中,想要查找一个员工只需要5次,但如果是一个线性结构,最坏情况需要找300次
1.2树的基本概念
-
线性数据结构——线性表,逻辑上元素之间相连,呈先行排列。例如数组,链表,栈,队列等
-
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
-
树的度:一个节点产生的子树个数称为节点的度,例如下图根节点D有3个子树,则该节点有3个度;一个树中,某个最大节点的度,成为该树的度,例如图中度最大的节点为根节点D,所以该二叉树的度为3
1.3辨别树与非树
从根节点出发有M个子集,这些子集之间互不相交,相交就不是树结构
二、二叉树
2.1概念
2.2二叉树重要性质(笔试常考选择题)
注意事项:重点记忆第三点(数据结构必考)
推导过程:
一:二叉树中只存在三种情况的节点,一种是一个子树n1,一种是两个子树n2,还有一种没有子树的n0。三种类型节点相加之后等于总结点个数n。
二:边和节点存在关系x=n-1,只有一个子树的有一条边,两个子树的有两条边,n1+2n2=n-1这是边长关系
两式相减就得出点三条结论:度为0的节点一定比度为2的节点多一个
2.3常见二叉树
满二叉树
区分是不是完全二叉树的关键点:完全二叉树节点编号与满二叉树是否完全一致,并且完全二叉树只能是在最深层不满,其它层数的节点都是满的。
练习:辨别下列图中是不是完全二叉树
不是,因为编号与满二叉树节点编号不一致。也不满足只能最深层节点数不满,图中第二层的节点数不满,关键点就是编号看是否一致。
二分搜索树
2.4完全二叉树编号问题(考点)
2.5二叉树遍历问题(递归)
关于"序"可以理解为访问输出根节点的次序,前就是第一次遇到就输出,以此类推。
前序遍历:根 左 右
中序遍历:左 根 右
后序遍历:左 右 根
层序遍历:从左往右,从上到下
三中遍历方式特点总结(刷题用的上)
代码练习:
/*
* 二叉树基本操作
* */
class TreeNode<E>{
//当前节点的值
E val;
//左子树的根
TreeNode<E> left;
//右子树的根
TreeNode<E> right;
public TreeNode(E val) {
this.val = val;
}
}
public class MyBinTree<E> {
public TreeNode<Character> root;
// 建立一个测试二叉树
public void build() {
TreeNode<Character> node = new TreeNode<>('A');
TreeNode<Character> node1 = new TreeNode<>('B');
TreeNode<Character> node2 = new TreeNode<>('C');
TreeNode<Character> node3 = new TreeNode<>('D');
TreeNode<Character> node4 = new TreeNode<>('E');
TreeNode<Character> node5 = new TreeNode<>('F');
TreeNode<Character> node6 = new TreeNode<>('G');
TreeNode<Character> node7 = new TreeNode<>('H');
node.left = node1;
node.right = node2;
node1.left = node3;
node1.right = node4;
node4.left = node6;
node6.right = node7;
node2.right = node5;
root = node;
}
代码示例:掌握递归本质
/**
* 传入一颗二叉树根节点root。按照前序遍历根左右方式进行输出
* */
public void preOrder(TreeNode root){
if(root==null){
return;
}
//打印根
System.out.print(root.val+" ");
//左子树
preOrder(root.left);
//右子树
preOrder(root.right);
}
/**
* 传入一颗二叉树的根节点root。就能按照中序遍历左根右的方式进行输出
* @param root
*/
public void inOrder(TreeNode root) {
if (root == null) {
return;
}
// 先打印左子树,交给子函数
inOrder(root.left);
// 打印根
System.out.print(root.val + " ");
// 最后打印右子树
inOrder(root.right);
}
/**
* 传入一颗二叉树的根节点root。就能按照后序遍历左右根的方式进行输出
* @param root
*/
public void postOrder(TreeNode root) {
if (root == null) {
return;
}
// 先打印左子树,交给子函数
postOrder(root.left);
// 再打印右子树
postOrder(root.right);
// 最后打印根
System.out.print(root.val + " ");
}
}
总结:
2.6二叉树的遍历问题(迭代)
1.前序遍历
易错点:深度优先是借助栈的数据结构,所以压栈时,如果节点左右子节点都不为空应该先压右节点,在下次循环时就可以先弹出左节点。
public List<Integer> preOrder(TreeNode root){
List<Integer> ret=new ArrayList<>();
if(root==null){
return ret;
}
Deque<TreeNode> stack=new LinkedList<>();
stack.push(root);
while (!stack.isEmpty()){
//先访问根节点
TreeNode cur= stack.pop();
ret.add(cur.val);
//将右子树先压入栈中
if(cur.right!=null){
stack.push(cur.right);
}
//再处理左子树
if(cur.left!=null){
stack.push(cur.left);
}
}
return ret;
}
}
2.中序遍历
核心思路:左根右,先一路向左走到底,碰到第一个左子树为空的根节点出栈
public List<Integer> inorderTraversal(TreeNode root){
List<Integer> ret=new ArrayList<>();
if(root==null){
return ret;
}
Deque<TreeNode> stack=new ArrayDeque<>();
TreeNode cur=root;
//以下两个条件一个不满足都不可以停止循环
while(cur!=null||!stack.isEmpty()){
//先一路向左走到底
while (cur!=null){
stack.push(cur);
cur=cur.left;
}
//cur已经为空,弹出栈顶元素,遇到第一个左子树为空的节点
cur= stack.pop();
ret.add(cur.val);
//继续访问右子树
cur=cur.right;
}
return ret;
}
}
总结:1.第一个大的while加上cur!=null的原因是此时根节点不入栈,但栈是空的,所以表示循环还没有结束。但是cur=cur.right可能出现空节点,此时cur=null但是栈不等于空,所以他俩只要一个条件不满足循环都还要继续。
2.一开始根节点不能先入栈,要一路走到底。
3.内层while循环就是为了一路向左子树走到底
解题思路:
3.后序遍历
难点:如何判断右树也走完了?