二叉树

一、数组存储、链式存储与数存储

1,数组存储

  优点:通过下标方式访问元素速度快。对于有序数组,还可以通过二分查找提高检索速度。
  缺点:如果检索具体某个值,或者插入值(按一定顺序)会整体移动,效率低

  

2,链式存储

  优点:在一定程度上对数组存储有优化(比如:插入一个数值节点,只需要将插入节点连接到链表中即可,删除效率也很好)。

  缺点:在进行检索时,效率仍然较低(比如:检索某个值,需要从头节点开始遍历)

     

3,树存储

  能提高数据存储,读取的效率,比如利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入、删除和修改的速度。

    

二、二叉树

  源码:二叉树

1,数的常用术语

  • 节点
  • 根节点
  • 父节点
  • 子节点
  • 叶子节点 (没有子节点的节点)
  • 节点的权(节点值)
  • 路径(从 root 节点找到该节点的路线)
  • 子树
  • 树的高度(最大层数)
  • 森林 :多颗子树构成森林

2,二叉树的概念

  • 树有很多种, 每个节点最多只能有两个子节点的一种形式称为二叉树,二叉树的子节点分为左节点和右节点

         

  • 如果该二叉树的所有叶子节点都在最后一层, 并且结点总数= 2^n -1, n 为层数, 则我们称为满二叉树

     

  • 如果该二叉树的所有叶子节点都在最后一层或者倒数第二层, 而且最后一层的叶子节点在左边连续倒数第二层的叶子节点在右边连续, 我们称为完全二叉树

    

3,二叉树的遍历

a)代码思路

  • 前序遍历: 先输出父节点, 再遍历左子树和右子树
  • 中序遍历: 先遍历左子树, 再输出父节点,再遍历右子树
  • 后序遍历: 先遍历左子树, 再遍历右子树,最后输出父节点

b)代码实现

//编号
private int no;
//名称
private String name;
//左子节点
private Node left;
//右子节点
private Node right;

/**
 * 前序遍历 : 根 -> 左 -> 右
 */
public void preOrder(){
    if (this == null) {
        return;
    }
    if (this != null) {
        System.out.print(this.no + "	");
    }
    if (this.left != null) {
        this.left.preOrder();
    }
    if (this.right != null) {
        this.right.preOrder();
    }
}

/**
 * 中序遍历 : 左 -> 根 -> 右
 */
public void midOrder(){
    if (this == null) {
        return;
    }
    if (this.left != null) {
        this.left.midOrder();
    }
    if (this != null) {
        System.out.print(this.no + "	");
    }
    if (this.right != null) {
        this.right.midOrder();
    }
}

/**
 * 后序遍历 : 左 -> 右 -> 根
 */
public void postOrder(){
    if (this == null) {
        return;
    }
    if (this.left != null) {
        this.left.postOrder();
    }
    if (this.right != null) {
        this.right.postOrder();
    }
    if (this != null) {
        System.out.print(this.no + "	");
    }
}
View Code

4,二叉树的查找

a)代码思路

  • 将二叉树的前、中、后序遍历改为查找即可
  • 编码思路:
    • 如果查找到目标节点,直接返回,结束递归
    • 如果找不到,继续递归执行前(中、后)序查找

      

b)代码实现

//编号
private int no;
//名称
private String name;
//左子节点
private Node left;
//右子节点
private Node right;

/**
 * 后序查找
 */
public Node postSearch(int no) {
    Node node = null;
    if (this.left != null) {
        node = this.left.postSearch(no);
    }
    if (node != null) {
        return node;
    }
    if (this.right != null) {
        node = this.right.postSearch(no);
    }
    if (node != null) {
        return node;
    }
    System.out.println("后续查找");
    if (this.no == no) {
        return this;
    }
    return node;
}

/**
 * 中序查找
 */
public Node midSearch(int no) {
    Node node = null;
    //先向左找
    if (this.left != null ) {
        node = this.left.midSearch(no);
    }
    //找到就返回
    if (node != null) {
        return node;
    }
    System.out.println("中序查找~");
    //找当前节点,找到返回
    if (this.no == no) {
        return this;
    }
    //向右找
    if (this.right != null) {
        node = this.right.midSearch(no);
    }
    return node;
}

/**
 * 前序查找
 */
public Node preSearch(int no){
    System.out.println("前序查找~");
    if (this.no == no) {
        return this;
    }
    Node node = null;
    if (this.left != null) {
        node =  this.left.preSearch(no);
    }
    if (node != null) {
        return node;
    }
    if (this.right != null) {
        node =  this.right.preSearch(no);
    }
    return node;
}
View Code

5,二叉树的删除

a)删除要求

  • 如果删除的节点是叶子节点, 则删除该节点
  • 如果删除的节点是非叶子节点, 则删除该子树

b)代码思路

  • 先判断根节点 root 是不是待删除的节点,如果是,则删除根节点,否则开始执行递归
  • 判断当前节点(this)的左节点是否为待删除的节点,如果是,删除 this.left ,然后返回,结束递归
  • 判断当前节点(this)的左节点是否为待删除的节点,如果是,删除 this.right,然后返回,结束递归
  • 否则继续执行左递归,左递归执行完后,执行右递归

c)代码实现

/**
 * 删除节点
 */
public void del(int no) {
    if (this.left != null) {
        if (this.left.no == no) {
            this.left = null;
            return;
        } else {
            this.left.del(no);
        }
    }
    if (this.right != null ) {
        if (this.right.no == no) {
            this.right = null;
            return;
        } else {
            this.right.del(no);
        }
    }
}
View Code
/**
 * 删除节点
 */
public void del(int no) {
    if (root == null) {
        System.out.println("当前二叉树为空!");
        return;
    }
    if (root.getNo() == no) {
        root = null;
        return;
    }
    root.del(no);
}
View Code

三、顺序存储二叉树

  源码:顺序存储二叉树

1,顺序存储二叉树与数组的转换

  • 基本说明:从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组

  • 要求:

    • 二叉树的结点,要求以数组的方式来存放 arr : [1, 2, 3, 4, 5, 6, 7]
    • 要求在遍历数组 arr时,仍然可以用前序遍历,中序遍历和后序遍历的方式完成结点的遍历

        

2,顺序存储二叉树的特点

  • 顺序二叉树通常只考虑完全二叉树
  • 顺序存储二叉树中第 n 个元素的左子节点对应的数组下标为 2 * n + 1
  • 顺序存储二叉树中第 n 个元素的右子节点对应的数组下标为 2 * n + 2
  • 顺序存储二叉树中第 n 个元素的父节点对应的数组下标为 (n-1) / 2
  • n 的含义: 表示二叉树中的第几个元素(按0开始编号,如上图所示)

3,代码实现

/**
 * 前序
 */
public void preOrder(int index) {
    if (arr == null || arr.length == 0) {
        System.out.println("当前数组为空!");
        return;
    }
    System.out.println(arr[index]);
    if ((2 * index + 1) < arr.length) {
        preOrder(2 * index + 1);
    }
    if ((2 * index + 2) < arr.length) {
        preOrder(2 * index + 2);
    }
}

四、线索化二叉树

  源码:线索化二叉树

1,原理

  通过考察各种二叉链表,无论儿叉树的形态怎样,空链域的个数总是多过非空链域的个数。准确的说,n各结点的二叉链表共同拥有2n个链域,非空链域为n-1个,但当中的空链域却有n+1个。例如以下图所看到的。

         

   可以通过充分利用二叉链表中的空指针域,存放节点在某种遍历方式下的前驱和后继节点的指针。我们把这种指向前驱和后继的指针成为线索加上线索的二叉链表成为线索链表,对应的二叉树就成为“线索二叉树(Threaded Binary Tree)” 。

2,构建线索化二叉树(中序)

a)思路

  在原始二叉树结点基础上,添加字段isLeftThreadisRightThread,从而判定指针域对应的是子节点还是前驱或后续线索

//节点存储结构
static class Node {
    String data;        //数据域
    Node left;          //左指针域
    Node right;         //右指针域
    boolean isLeftThread = false;   //左指针域类型  false:指向子节点、true:前驱或后继线索
    boolean isRightThread = false;  //右指针域类型  false:指向子节点、true:前驱或后继线索
}

  将最终的二叉树构建为如下图所示的线索二叉树

    

b)代码实现

/**
 * 中序线索化二叉树
 *
 * @param node 节点
 */
public void inThreadOrder(Node node) {
    if (node == null) {
        return;
    }

    //处理左子树
    inThreadOrder(node.left);

    //左指针为空,将左指针指向前驱节点
    if (node.left == null) {
        node.left = preNode;
        node.isLeftThread = true;
    }

    //前一个节点的后继节点指向当前节点
    if (preNode != null && preNode.right == null) {
        preNode.right = node;
        preNode.isRightThread = true;
    }
    preNode = node;

    //处理右子树
    inThreadOrder(node.right);
}

3,遍历线索化二叉树

a)思路

  • 找到线索二叉树的最左子节点
  • 从左往右遍历各节点while(node!=null){node=node.right...}
    • 如果右指针是线索指针直接打印,
    • 如果右指针是节点则查找该右子树的最左结点

b)代码实现

/**
 * 中序遍历线索二叉树,按照后继方式遍历(思路:找到最左子节点开始)
 *
 * @param node
 */
public void inThreadList(Node node) {
    //1、找中序遍历方式开始的节点
    while (node != null && !node.isLeftThread) {
        node = node.left;
    }

    while (node != null) {
        System.out.print(node.data + ", ");

        //如果右指针是线索
        if (node.isRightThread) {
            node = node.right;

        } else {    //如果右指针不是线索,找到右子树开始的节点
            node = node.right;
            while (node != null && !node.isLeftThread) {
                node = node.left;
            }
        }
    }
}
原文地址:https://www.cnblogs.com/bbgs-xc/p/14133144.html