树的常用术语
- 节点
- 根节点
- 父节点
- 子节点
- 叶子节点:没有子节点的节点
- 节点的权:节点值
- 路径:从root节点找到该节点的路线
- 层
- 子树
- 树的高度:最大层数
- 森林:多颗子树构成森林
二叉树的概念
- ①树有很多种,么个节点最多只能有两个子节点的一种形式的树称为二叉树
- ②二叉树的子节点分为左节点和右节点
- ③如果该二叉树的所有叶子节点都在最后一层,并且节点总数= 2^n-1,n为层数,则我们称为满二叉树
- ④如果该二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,我们称为完全二叉树,如果把G节点删除,就不是完全二叉树了,因为叶子节点不连续了

1. 二叉树遍历
- 前序遍历:==先输出父节点==,再遍历左子树和右子树
- 中序遍历:先遍历左子树,==再输出父节==点,再遍历右子树
- 后序遍历:先遍历左子树,再遍历右子树,==最后输出父节点==
- 小结:看输出父节点的顺序,就确定是前序、中序还是后序
思路分析
前序遍历
- 先输出当前节点(root节点)
- 如果左子节点不为空,则递归继续前序遍历
- 如果右子节点不为空,则递归继续前序遍历
中序遍历
- 如果当前节点的左子节点不为空,则递归继续中序遍历
- 输出当前节点
- 如果当前节点的右子节点不为空,则递归继续中序遍历
后序遍历
- 如果当前节点的左子节点不为空,则递归继续后序遍历
- 如果当前节点的右子节点不为空,则递归继续后序遍历
- 输出当前节点
代码实现
节点HeroNode
public class HeroNode{
private int no;
private String name;
private HeroNode left;
private HeroNode right;
public HeroNode(int hNo,String hName){
this.no = hNo;
this.name = hName;
}
//前序遍历
public void preOrder(){
System.out.println(this);//先输出父节点
//递归向左子树前序遍历
if (this.left!=null){
this.left.preOrder();
}
//递归向右子树前序遍历
if (this.right!=null){
this.right.preOrder();
}
}
//中序遍历
public void infixOrder(){
//递归向左子树中序遍历
if (this.left!=null){
this.left.infixOrder();
}
//输出父节点
System.out.println(this);//先输出父节点
//递归向右子树中序遍历
if (this.right!=null){
this.right.infixOrder();
}
}
//后序遍历
public void postOrder(){
//递归向左子树后序遍历
if (this.left!=null){
this.left.postOrder();
}
//递归向右子树后序遍历
if (this.right!=null){
this.right.postOrder();
}
//输出父节点
System.out.println(this);//先输出父节点
}
+ set();
+ get();
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
'}';
}
}
二叉树BinaryTree
public class BinaryTree {
private HeroNode root;
public void setRoot(HeroNode root) {
this.root = root;
}
//前序遍历
public void preOrder(){
if (this.root!=null) {
root.preOrder();
}else {
System.out.println("当前二叉树为空,无法遍历");
}
}
//中序遍历
public void infixOrder(){
if (this.root!=null) {
root.infixOrder();
}else {
System.out.println("当前二叉树为空,无法遍历");
}
}
//后序遍历
public void postOrder(){
if (this.root!=null) {
root.postOrder();
}else {
System.out.println("当前二叉树为空,无法遍历");
}
}
}
测试代码
public class BinaryTreeDemo {
public static void main(String[] args) {
BinaryTree binaryTree = new BinaryTree();
HeroNode root = new HeroNode(1,"宋江");
HeroNode node2 = new HeroNode(2,"吴用");
HeroNode node3 = new HeroNode(3,"卢俊义");
HeroNode node4 = new HeroNode(4,"公孙胜");
HeroNode node5 = new HeroNode(5,"关胜");
root.setLeft(node2);
node3.setLeft(node5);
node3.setRight(node4);
root.setRight(node3);
binaryTree.setRoot(root);
System.out.println("前序遍历");
binaryTree.preOrder();//1 2 3 5 4
System.out.println("中序遍历");
binaryTree.infixOrder();//2 1 5 3 4
System.out.println("后序遍历");
binaryTree.postOrder();//2 5 4 3 1
}
}
2. 二叉树查找
思路分析
前序查找
- 1 先判断当前节点的no是否等于要查找的节点no
- 2 如果相等,则返回当前节点
- 3 如果不相等,则判断当前节点的左子节点是否为空,如果不为空,则递归前序查找
- 4 如果左递归前序查找,找到节点,则返回;如果未找到,则继续判断当前的节点的右节点是够为空,如果不空,则继续向右递归查找
- 5 如果找到了,返回节点,否则返回null
中序查找
- 1 判断当前节点的左子节点是否为空,如果不为空,则递归中序查找
- 2 如果找到,则返回当前节点;
- 3 如果没有找到,就和当前节点进行比较,如果相等,则返回当前节点,否则继续向右递归中序查找;
- 4 如果右递归中序查找找到,就返回,否则返回null
后序查找
- 1 判断当前节点的左子节点是否为空,如果不为空,则递归后序查找
- 2 如果找到,则返回当前节点;
- 3 如果没有找到,就判断当前节点的右子节点是否为空,如果不为空,则右递归进行后序查找,如果找到,就返回
- 4 如果没找到,就和当前节点进行比较,如果相等则返回,否则返回null
代码实现
HeroNode代码
//前序遍历查找
public HeroNode preOrderSearch(int no){
System.out.println("进入前序遍历");
//先比较当前节点
if (this.no==no){
return this;
}
//否则遍历查找左子节点
HeroNode resultNode = null;
if (this.left!=null){
resultNode = this.left.preOrderSearch(no);
}
if (resultNode!=null){//左子树找到了
return resultNode;
}
//否则遍历查找右子节点
if (this.right!=null){
resultNode = this.right.preOrderSearch(no);
}
return resultNode;
}
//中序遍历查找
public HeroNode infixOrderSearch(int no){
HeroNode resultNode = null;
//左子节点
if (this.left!=null){
resultNode = this.left.infixOrderSearch(no);
}
if (resultNode!=null){
return resultNode;
}
System.out.println("进入中序遍历");
//当前节点
if (this.no == no){
return this;
}
//右子节点
if (this.right!=null){
resultNode = this.right.infixOrderSearch(no);
}
return resultNode;
}
//后续遍历查找
public HeroNode postOrderSearch(int no){
HeroNode resultNode = null;
//左子节点
if (this.left!=null){
resultNode = this.left.postOrderSearch(no);
}
if (resultNode!=null){//左子节点找到
return resultNode;
}
//否则遍历查找右子节点
if (this.right!=null){
resultNode = this.right.postOrderSearch(no);
}
if (resultNode!=null){//右子节点找到
return resultNode;
}
System.out.println("进入后序遍历");
//都没找到,比较当前节点
if (this.no == no){
return this;
}
return resultNode;
}
BinaryTree代码
//前序遍历查找
public HeroNode preOrderSearch(int no){
if (this.root!=null) {
return root.preOrderSearch(no);
}else {
System.out.println("当前二叉树为空,无法查找");
return null;
}
}
//中序遍历查找
public HeroNode infixOrderSearch(int no){
if (this.root!=null) {
return root.infixOrderSearch(no);
}else {
System.out.println("当前二叉树为空,无法查找");
return null;
}
}
//后序遍历查找
public HeroNode postOrderSearch(int no){
if (this.root!=null) {
return root.postOrderSearch(no);
}else {
System.out.println("当前二叉树为空,无法查找");
return null;
}
}
测试代码
... main{
//查找
System.out.println("前序查找~~ "+binaryTree.preOrderSearch(5));//遍历4次
System.out.println("中序查找~~ "+binaryTree.infixOrderSearch(5));//遍历3次
System.out.println("后序查找~~ "+binaryTree.postOrderSearch(5));//遍历2次
}
3. 二叉树删除
思路分析
规定
- 如果删除的节点是叶子节点,则删除该节点
- 如果删除的节点是非叶子节点,则删除该子树
思路
如果树是空树root,如果只有一个root节点并且是要删除的节点,则等价于将二叉树置空;
否则进行以下操作:
- 1) 我们举例的二叉树是单向的,所以只能判断子节点是否需要删除,而不能判断当前节点是不是需要删除的节点(因为无法获取父节点并且将父节点指向当前节点置为null)
- 2) 如果当前节点的左子节点不为空,并且左子节点的编号就是需要删除的节点,就将this.left=null;并且返回,结束递归删除
- 3) 如果当前节点的右子节点不为空,并且右子节点的编号就是需要删除的节点,就将this.right=null;
- 4) 如果2、3步操作都没有删除节点,那么我们就需要向左/右子树递归删除
- 5) 如果4步都没有删除节点,那么我们就需要向右/左子树递归删除
代码实现
HeroNode代码
//递归删除节点
//规定
//- 如果删除的节点是叶子节点,则删除该节点
//- 如果删除的节点是非叶子节点,则删除该子树
public void deleteNode(int no){
/**
* 1) 我们举例的二叉树是单向的,所以只能判断子节点是否需要删除,而不能判断当前节点是不是需要删除的节点(因为无法获取父节点并且将父节点指向当前节点置为null)
* 2) 如果当前节点的左子节点不为空,并且左子节点的编号就是需要删除的节点,就将this.left=null;并且返回,结束递归删除
* 3) 如果当前节点的右子节点不为空,并且右子节点的编号就是需要删除的节点,就将this.right=null;
* 4) 如果2、3步操作都没有删除节点,那么我们就需要向左/右子树递归删除
* 5) 如果4步都没有删除节点,那么我们就需要向右/左子树递归删除
*/
//2
if (this.left!=null && this.left.no==no){
this.left = null;
return;
}
//3
if (this.right!=null && this.right.no==no){
this.right = null;
return;
}
//4
if (this.left!=null){
this.left.deleteNode(no);
}
//5
if (this.right!=null){
this.right.deleteNode(no);
}
}
BinaryTree代码
//删除节点
public void deleteNode(int no){
if (root!=null){
if (root.getNo()==no){
root = null;
}else {
root.deleteNode(no);
}
}else {
System.out.println("空树,不能删除");
}
}
测试代码
...main{
//删除
System.out.println("删除前");
binaryTree.preOrder();
System.out.println("删除后");
binaryTree.deleteNode(5);
binaryTree.preOrder();
}
练习
要求: 如果要删除的节点不是叶子节点,现在不希望将该非叶子节点为根节点的子树删除,指定以下规则:
- 如果搞非叶子节点A只有一个节点B,则子节点B代替节点A
- 如果该非叶子节点A有左子节点B和右子节点C,则让左子节点替代节点A
增加删除节点方法deleteNode2
//要求
//如果要删除的节点不是叶子节点(即有子节点),将子节点上移,左子节点优先
public void deleteNode2(int no){
//2
if (this.left!=null && this.left.no==no){
if (this.left.getLeft()==null&&this.left.getRight()==null){//如果是叶子节点,直接删
this.left = null;
}else {
if (this.left.getLeft()!=null){
this.left = this.left.getLeft();
}else {
this.left = this.left.getRight();
}
}
return;
}
//3
if (this.right!=null && this.right.no==no){
if (this.right.getLeft()==null&&this.right.getRight()==null) {//如果是叶子节点,直接删
this.right = null;
}else {
if (this.right.getLeft()!=null){
this.right = this.right.getLeft();
}else {
this.right = this.right.getRight();
}
}
return;
}
//4
if (this.left!=null){
this.left.deleteNode(no);
}
//5
if (this.right!=null){
this.right.deleteNode(no);
}
}