データ構造とアルゴリズム演習 (3) 二分木


1.木

树是一种非线性的数据结构,是由n(n >=0)个结点组成的有限集合
如果n=0,树为空树。
如果n>0,除根节点外,其余结点被分成m(m>0)个互不相交的集合T1、T2、……、Tm,其中每一个集合Ti(1<= i <= m)又是一棵结构与树类似的子树。

ここに画像の説明を挿入します
関連概念:

  • ルートノード: 親ノードのないノード。
  • リーフノード: 子ノードを持たないノード。
  • 兄弟ノード: 同じ親ノードを持つノード。
  • ノードの次数: ノードが所有するサブツリーの数。たとえば、ノード A の次数は 3 です。B、Cは2
  • ツリー次数: ツリー内の各ノードの最大次数。たとえば、ノード A の次数が最も大きいため、ツリーの次数は 3 になります。
  • ノードのレベル: ルートの定義から始まり、ルートは第 1 レベル、ルートの子ノードは第 2 レベル、というようになります。
  • ツリーの高さまたは深さ: ツリー内のノードの最大レベル。上に示すように: ツリーの高さは 4 です。
  • フォレスト: m (m>0) 個の互いに素なツリーの集合をフォレストと呼びます。

2.二分木

二叉树(Binary tree)是每个节点最多有两个子节点的树。(删掉上面的D节点)

ここに画像の説明を挿入します

3.完全なバイナリツリー

 一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。
 如果一个二叉树的层数为n,且结点总数是2^n -1 ,则它就是满二叉树

ここに画像の説明を挿入します

4. 完全なバイナリツリー

完全二叉树是一种特殊的二叉树,它除了最后一层外,其他每一层都被完全填满,且最后一层的节点都靠左排列。
满二叉树一定是完全二叉树

ここに画像の説明を挿入します

5. バイナリ ツリー トラバーサル (プレオーダー、ミッドオーダー、ポストオーダー)

さまざまなルールに従って (出力親ノードの順序を参照)、次のように分割されます。

  • 事前順序トラバーサル: 最初に親ノードを出力し、次に左側のサブツリー、次に右側のサブツリーをトラバースします。

  • 順序走査: 最初に左側のサブツリーを走査し、次に親ノードを出力し、次に右側のサブツリーを走査します。

  • 事後走査: 最初に左側のサブツリーを走査し、次に右側のサブツリーを走査し、最後に親ノードを出力します。

    例如上面的完全二叉树:
        前序遍历:ABEFCG
        中序遍历:EBFAGC
        后序遍历:EFBGCA
    

コード:

package Tree;
public class BinaryTreeDemo {
    
    
    public static void main(String[] args) {
    
    
        BinaryTree binaryTree = new BinaryTree();
        Node a = new Node("A");
        Node b = new Node("B");
        Node c = new Node("C");
        Node e = new Node("E");
        Node f = new Node("F");
        Node g = new Node("G");
        //手动创建树
        binaryTree.setRoot(a);
        a.left=b;
        a.right=c;
        b.left=e;
        b.right=f;
        c.left=g;
        System.out.println("前序遍历:");
        binaryTree.preOrderM();
        System.out.println("中序遍历:");
        binaryTree.infixOrderM();
        System.out.println("后序遍历:");
        binaryTree.postOrderM();
    }
}
//创建树的属性和方法
class BinaryTree{
    
    
    //定义根节点
    Node root;

    public void setRoot(Node root) {
    
    
        this.root = root;
    }
    //前序遍历
    public void preOrderM() {
    
    
        if (this.root != null) {
    
    
            this.root.preOrder();
        } else {
    
    
            System.out.println("二叉树为空!无法遍历!");
        }
    }
    //中序遍历
    public void infixOrderM() {
    
    
        if (this.root != null) {
    
    
            this.root.infixOrder();
        } else {
    
    
            System.out.println("二叉树为空!无法遍历!");
        }
    }
    //中序遍历
    public void postOrderM() {
    
    
        if (this.root != null) {
    
    
            this.root.postOrder();
        } else {
    
    
            System.out.println("二叉树为空!无法遍历!");
        }
    }
}

//创建节点对象的属性和方法
class  Node{
    
    
    String name;
    Node left;
    Node right;

    public Node(String name) {
    
    
        this.name = name;
    }
    //重写toString方法
    @Override
    public String toString() {
    
    
        return "Node{" +
                "name='" + name + '\'' +
                '}';
    }

    //前序遍历
    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);
    }
}
事前注文トラバーサル 順序トラバーサル 事後トラバーサル
ここに画像の説明を挿入します ここに画像の説明を挿入します ここに画像の説明を挿入します

バイナリ ツリーのノードまたはツリーの削除

  • 削除したノードがリーフノードの場合は、ノードを削除します
  • 削除されたノードが非リーフ ノードの場合は、サブツリーを削除します

コードの実装: ツリー B とリーフ ノード E を削除します。

package Tree;
public class BinaryTreeDemo {
    
    
    public static void main(String[] args) {
    
    
        BinaryTree binaryTree = new BinaryTree();
        Node a = new Node("A");
        Node b = new Node("B");
        Node c = new Node("C");
        Node e = new Node("E");
        Node f = new Node("F");
        Node g = new Node("G");
        //手动创建树
        binaryTree.setRoot(a);
        a.left=b;
        a.right=c;
        b.left=e;
        b.right=f;
        c.left=g;
        System.out.println("前序遍历:");
        binaryTree.preOrderM();
        //删除某个节点
        System.out.println("删除节点E");
        binaryTree.deleteNode("E");
        System.out.println("查看删除后的节点");
        binaryTree.preOrderM();
        System.out.println("删除树B");
        binaryTree.deleteNode("B");
        System.out.println("查看删除后的节点");
        binaryTree.preOrderM();
    }
}
//创建树的属性和方法
class BinaryTree{
    
    
    //定义根节点
    Node root;

    public void setRoot(Node root) {
    
    
        this.root = root;
    }
    //前序遍历
    public void preOrderM() {
    
    
        if (this.root != null) {
    
    
            this.root.preOrder();
        } else {
    
    
            System.out.println("二叉树为空!无法遍历!");
        }
    }
    //中序遍历
    public void infixOrderM() {
    
    
        if (this.root != null) {
    
    
            this.root.infixOrder();
        } else {
    
    
            System.out.println("二叉树为空!无法遍历!");
        }
    }
    //中序遍历
    public void postOrderM() {
    
    
        if (this.root != null) {
    
    
            this.root.postOrder();
        } else {
    
    
            System.out.println("二叉树为空!无法遍历!");
        }
    }
    //删除节点,先判断根节点是不是所需要的节点
    public  void deleteNode(String name){
    
    
       if (this.root!=null){
    
    
           if (this.root.name==name){
    
    
               this.root=null;
           }else {
    
    
               this.root.delNode(name);
           }
       }else {
    
    
           System.out.println("二叉树为空!无法删除节点");
       }
    }
}
//创建节点对象的属性和方法
class  Node{
    
    
    String name;
    Node left;
    Node right;

    public Node(String name) {
    
    
        this.name = name;
    }
    //重写toString方法
    @Override
    public String toString() {
    
    
        return "Node{" +
                "name='" + name + '\'' +
                '}';
    }
    //前序遍历
    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);
    }
    //删除node方法
    public void delNode(String name) {
    
    
        if (this.left!=null&&this.left.name==name){
    
    
            this.left=null;
            return;
        }
        if (this.right!=null&&this.right.name==name){
    
    
            this.right=null;
            return;
        }
        //上述是root底下的两个节点。若这两个都不是我们要的那个节点,需再递归
        if (this.left!=null){
    
    
            this.left.delNode(name);
        }
        if (this.right!=null){
    
    
            this.right.delNode(name);
        }
    }
}

6. バイナリツリーの逐次保存

 顺序存储二叉树是二叉树的一种存储方式。
 将二叉树存储在一个数组中,通过存储元素的下标反映元素之间的父子关系。

ここに画像の説明を挿入します
特徴:

  • バイナリ ツリーの順次ストレージでは通常、完全なバイナリ ツリーのみが考慮されます。
  • 配列 arr を走査する場合でも、可能です (前順、中順、後順の走査)。
  • 添字 n を持つ要素の左側の子ノードは添字 2*n+1 です
  • 添字 n を持つ要素の右側の子ノードは添字 2*n+2 です
  • インデックス n の要素の親ノードは (n-1)/2 です。

シーケンシャル ストレージ バイナリ ツリー トラバーサル (プレオーダー、インオーダー、ポストオーダー)

public class ArrayBinaryTreeDemo {
    
    
    public static void main(String[] args) {
    
    
        String [] arr={
    
    "A","B","C","E","F","G"};
        ArrayBinaryTree tree = new ArrayBinaryTree(arr);
        tree.preOrder();
        System.out.println();
        tree.infixOrder();
        System.out.println();
        tree.postOrder();
    }
}
//实现顺序存储二叉树
class ArrayBinaryTree{
    
    
    String[] arr;

    public ArrayBinaryTree(String[] arr) {
    
    
        this.arr = arr;
    }
    public  void  preOrder(){
    
    
        this.preOrder(0);
    }
    public  void infixOrder(){
    
    
        this.infixOrder(0);
    }
    public  void  postOrder(){
    
    
        this.postOrder(0);
    }
    //实现前序遍历
    public  void  preOrder(int index){
    
    
        if (arr==null || arr.length==0){
    
    
            System.out.println("数组为空");
            return;
        }
        System.out.print(arr[index]+" ");
        if ((index * 2 + 1) < arr.length) {
    
    
            preOrder(index * 2 + 1);
        }
        //再递归右子树
        if ((index * 2 + 2) < arr.length) {
    
    
            preOrder(index * 2 + 2);
        }
    }
    //实现中序遍历
    public  void  infixOrder(int index){
    
    
        if (arr==null || arr.length==0){
    
    
            System.out.println("数组为空");
            return;
        }
        if ((index * 2 + 1) < arr.length) {
    
    
            infixOrder(index * 2 + 1);
        }
        System.out.print(arr[index]+" ");
        //再递归右子树
        if ((index * 2 + 2) < arr.length) {
    
    
            infixOrder(index * 2 + 2);
        }
    }
    //后序遍历
    public  void  postOrder(int index){
    
    
        if (arr==null || arr.length==0){
    
    
            System.out.println("数组为空");
            return;
        }
        if ((index * 2 + 1) < arr.length) {
    
    
            postOrder(index * 2 + 1);
        }
        //再递归右子树
        if ((index * 2 + 2) < arr.length) {
    
    
            postOrder(index * 2 + 2);
        }
        System.out.print(arr[index]+" ");
    }
}

操作結果:
ここに画像の説明を挿入します

7. スレッド化されたバイナリ ツリー

对于n个结点的二叉树,在二叉链存储结构中有n+1个空链域,利用这些空链域存放在某种遍历次序下该结点的前驱结点和后继结点的指针,这些指针称为线索,加上线索的二叉树称为线索二叉树。
根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。

注: 手がかりリンク リストは、特定の走査シーケンスでノードの先行ノードと後続ノードを直接見つけることができないという問題を解決し、バイナリ リンク リストの左右の子を見つける問題を解決します。

スレッド化されたバイナリ ツリーの走査: スレッド化後、各ノードの方向が変わるため、元の走査方法は使用できません。この時点で、スレッド化されたバイナリ ツリーを走査するには新しい方法を使用する必要があります。各ノードは 1 つの単位で走査できます。線形的な方法なので、再帰法を使用する必要がなく、走査の効率も向上します。

順序どおりの手掛かりバイナリ ツリー

例: 順序付き手がかりバイナリ ツリー

ここに画像の説明を挿入します

public class ThreadedBinaryTreeDemo {
    
    
    public static void main(String[] args) {
    
    
        ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
        MyNode a = new MyNode("A");
        MyNode b = new MyNode("B");
        MyNode c = new MyNode("C");
        MyNode e = new MyNode("E");
        MyNode f = new MyNode("F");
        MyNode g = new MyNode("G");
        //手动创建树
        threadedBinaryTree.setRoot(a);
        a.left=b;
        a.right=c;
        b.left=e;
        b.right=f;
        c.left=g;
        System.out.println("未线索化前e节点的前驱节点和后驱");
        System.out.println("F号结点的前驱结点为:"+e.left);//3
        System.out.println("F号结点的后继结点为:"+e.right);//1
        System.out.println("中序线索化后e节点的前驱节点和后驱");
        threadedBinaryTree.infixThreadedNodes();
        System.out.println("F号结点的前驱结点为:"+e.left);//3
        System.out.println("F号结点的后继结点为:"+e.right);//1
    }
}
//定义能实现线索化的二叉树
class ThreadedBinaryTree {
    
    
    MyNode root;
    MyNode pre=null;//指向当前节点的前驱节点  递归过程中pre总是保留前一个节点
    //为了实现线索化,需要创建指向当前节点的前驱结点的指针
    public void setRoot(MyNode root) {
    
    
        this.root = root;
    }
    public void infixThreadedNodes() {
    
    
        this.infixThreadedNodes(root);
    }
    //编写对二叉树进行中序线索化的方法
    public void infixThreadedNodes(MyNode node) {
    
    
        if (node == null) {
    
    //节点为空 不能线索化
            return;
        }
            //线索化左子树
            infixThreadedNodes(node.left);
            if (node.left==null){
    
    
                node.left=pre;
                node.leftType=1;
            }
            //处理后继节点
            if (pre!=null && pre.right==null){
    
    
                pre.right=node;
                pre.rightType=1;
            }
            //每处理一个节点,让当前节点是下一个节点的前驱节点
            pre=node;
            //线索化右子树
            infixThreadedNodes(node.right);
    }
}
class  MyNode{
    
    
    String name;
    MyNode left;
    MyNode right;
    //说明
    //1.如果leftType==0 表示指向的是左子树,为1 表示指向前驱节点
    //2.如果rightType==0 表示指向的是右子树,为1 表示指向后继节点
    int leftType;
    int rightType;
    public MyNode(String name) {
    
    
        this.name = name;
    }
    //重写toString方法
    @Override
    public String toString() {
    
    
        return "Node{" +
                "name='" + name + '\'' +
                '}';
    }
}

操作結果:
ここに画像の説明を挿入します

予約注文の手掛かりバイナリ ツリー

ここに画像の説明を挿入します

 //编写对二叉树进行前序线索化的方法
    public void preThreadedNodes(MyNode node) {
    
    
        if (node == null) {
    
    
            return;
        }
        //线索化当前节点
        //处理前驱节点     左指针为空 则将左指针指向前驱节点
        if (node.left == null) {
    
    
            node.left=pre;
            node.leftType=1;
        }
        //处理后继节点     前一个节点的后继节点指向当前节点
        if (pre != null && pre.right == null) {
    
    
            pre.right=node;
            pre.rightType=1;
         }
        //更新pre
        pre = node;
        //线索化左子树
        if (node.leftType == 0) {
    
    
            preThreadedNodes(node.left);
        }
        //线索化右子树
        if (node.rightType == 0) {
    
    
            preThreadedNodes(node.right);
        }
    }

ここに画像の説明を挿入します

事後手がかりバイナリ ツリー

ここに画像の説明を挿入します

 //编写对二叉树进行后序线索化的方法
    public void postThreadedNodes(MyNode node) {
    
    
        if (node == null) {
    
    
            return;
        }
        //线索化左子树
        if (node.leftType == 0) {
    
    
            postThreadedNodes(node.left);
        }
        //线索化右子树
        if (node.rightType == 0) {
    
    
            postThreadedNodes(node.right);
        }
        //线索化当前节点
        if (node.left == null) {
    
    
            node.left=pre;
            node.leftType=1;
        }
        if (pre != null && pre.right == null) {
    
    
            pre.right=node;
            pre.rightType=1;
        }
        //更新pre
        pre = node;
    }

ここに画像の説明を挿入します

おすすめ

転載: blog.csdn.net/qq_45637894/article/details/131046585