专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

数据结构 - 树以及Java代码实现

我们知道数据结构根据数据的存储方式分为线性结构和非线性结构,而树就属于非线性结构。

树是由n(n>0)个有限结点组成的具有层次结构的集合。 当n=0时,叫做空树。

把这种数据结构叫做树是因为它看起来像一棵“倒挂的树”,即根朝上,叶朝下的树。

它具有以下特征:

  • 没有父结点的树叫做根结点
  • 每个结点可以有一个或多个子结点
  • 每个非根结点只有一个父结点

65_1.png

树的基本术语

  • 结点的度:结点的子树个数
  • 树的度:树中所有结点的度的最大值
  • 路径和路径长度:从结点n1到nk的路径是一个结点序列n1、n2、… 、nk

    其中ni是ni+1的父结点。路径所包含的边的个数叫做路径的长度

  • 结点的层次:规定根结点的层次为1层,其他任一结点的层次为其父结点的层次加一
  • 树的深度:树中所有结点的层次最大值是这棵树的深度

二叉树

二叉树其实就是树的一种特殊情况。区别在于:

  • 二叉树的每个结点最多只能有两个结点
  • 二叉树中的结点是区分左右的。顺序不能颠倒

二叉树拥有如下性质:

  • 在二叉树的第i层最多有2i-1个结点
  • 深度为k的二叉树,最多有2k - 1 个结点 (k>=1)
  • n0 = n2 + 1,n0为度数为0的结点数,n2 度数为2的结点数

斜树

所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。

左斜树

65_2.png

右斜树

65_3.png

满二叉树

在一棵二叉树中。如果所有结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。

65_4.png

完全二叉树

对一颗具有n个结点的二叉树按层编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。

65_5.png

完全二叉树的性质:

  • 具有n个结点的完全二叉树的深度[log2n] + 1,其中[log2n]是向下取整。

二叉树的存储结构

二叉树的结点可以使用一维数组进行存储,或者是链表进行存储。

顺序存储

二叉树的顺序存储就是利用一维数组存储二叉树的结点,结点的存储位置就是数组的下标索引。

由此,我们知道二叉树的顺序存储结构的优点是查找效率高,而且增加和删除结点的效率高。

缺点就是当二叉树结构不是完全二叉树时,容易出现空间浪费的问题。

例如:当出现斜树的情况时,那么数组中大部分的位置都是没有存储结点的,大大浪费了空间。

65_6.png

对应一维数组的存储如下:

65_7.png

链式存储

二叉树的链式存储就是使用链表进行结点的存储。

结点的结构存在一个数据域,两个指针域。

65_8.png

二叉树的遍历

二叉树的遍历是指从二叉树的根结点出发,按照某种次序依次访问二叉树中的所有结点,使得每个结点被访问一次,且仅被访问一次。

二叉树的访问次序可以分为四种:

  • 前序遍历
  • 中序遍历
  • 后序遍历
  • 层序遍历

前序遍历

前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。

中序遍历

中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。在遍历左、右子树时,仍然先遍历左子树,然后访问根结点,最后遍历右子树。

后序遍历

后序遍历首先遍历左子树,然后遍历右子树,最后访问根结点,在遍历左、右子树时,仍然先遍历左子树,然后遍历右子树,最后遍历根结点。

层次遍历

层次遍历就是按照树的层次自上而下的遍历二叉树。

二叉树的代码实现

顺序存储

package com.xgc.tree.binarytree.sequentialstoreage;

public class BinaryTree {

    Object[] arr;

    public void buildTree(Object[] arr) {
        this.arr = arr;
    }

    /**
     * 先序遍历
     * @param root 树的根结点
     */
    public void preOrderTree() {
        preOrderTree(0);
    }

    private void preOrderTree(int index) {
        if (arr==null || arr.length == 0) return;
        System.out.print(arr[index] + " ");
        if((index*2+1)<arr.length){
            preOrderTree(index*2+1);
        }
        if((index*2+2)<arr.length){
            preOrderTree(index*2+2);
        }
    }

    //中序和后序遍历 与 先序遍历类似,这里就不写了
    //层次遍历就更简单了,遍历数组就可以了
}

链式存储

package com.xgc.tree.binarytree.linkedstoreage;

/**
 * 二叉树的结点
 * @author xgc
 *
 */
public class TreeNode {

    Object data;
    //左节点
    TreeNode left;
    //右节点
    TreeNode right;

    public TreeNode(Object data) {
        this.data = data;
    }

    public TreeNode(TreeNode left , Object data, TreeNode right) {
        this.left = left;
        this.data = data;
        this.right = right;
    }

}

package com.xgc.tree.binarytree.linkedstoreage;

/**
 * 二叉树的结点
 * @author xgc
 *
 */
public class TreeNode {

    Object data;
    //左节点
    TreeNode left;
    //右节点
    TreeNode right;

    public TreeNode(Object data) {
        this.data = data;
    }

    public TreeNode(TreeNode left , Object data, TreeNode right) {
        this.left = left;
        this.data = data;
        this.right = right;
    }

}

测试:

package com.xgc.tree.binarytree.linkedstoreage;

public class BinaryTreeTest {

    public static void main(String[] args) {
        Object[] arr = {1,2,3,4,5,6,7,8,9};
        BinaryTree tree = new BinaryTree();
        TreeNode root = tree.buildTree(arr);
        System.out.print("先序遍历 ");
        tree.preOrderTree(root);
        System.out.println();

        System.out.print("中序遍历 ");
        tree.inOrderTree(root);
        System.out.println();

        System.out.print("后序遍历 ");
        tree.postOrderTree(root);
        System.out.println();

        System.out.print("层次遍历 ");
        tree.levelOrderTree(root);
        System.out.println();
    }

}

执行结果如下:

先序遍历 1 2 4 8 9 5 3 6 7 
中序遍历 2 4 8 9 5 1 3 6 7 
后序遍历 2 4 8 9 5 3 6 7 1 
层次遍历 1 2 3 4 5 6 7 8 9 

二叉搜索树

二叉搜索树:一颗二叉树,可以为空。如果不为空,满足如下性质:

  • 非空左子树所有结点的值都小于根结点的值
  • 非空右子树所有结点的值都大于根结点的值
  • 左子树和右子树都是二叉搜索树

二叉搜索树不会出现重复元素,所以遇到重复元素的时候就不插入。

二叉搜索树操作的函数有:

  • 插入新结点
  • 删除结点
  • 查找最大值和最小值
  • 查找指定值的结点

在上面的操作中,删除结点的操作比较复杂,所以介绍删除结点操作的实现思路,其他的就不介绍了,看代码实现就可以了。

二叉搜索树的删除操作分类讨论:

1、 要删除的结点没有子结点,即是叶节点。

这里我们将要删除结点的父结点的left或right设为NULL就可以了

这里的left、right取决于要删除的结点是父结点的左结点或右结点

65_9.png

像上图current是当前要删除的结点,我们只要把parent.left设为null即可

1、 删除的结点有一个孩子结点

将这个孩子结点赋值给要删除结点的父结点的left或right

65_10.png

1、 要删除的结点有两个孩子结点

这种情况比较复杂,我们引入**后继结点**的概念。

后继结点:如果将一棵二叉树按照中序遍历的方式输出,则任一结点的下一个结点就是该结点的后继结点。

 *  **后继节点为要删除结点的右结点**

    将要删除的结点用后继结点替换即可,注意处理好删除结点的left和后继结点的right

65_11.png

65_12.png

  • 后继节点为要删除结点的右孩子的左子

    这种情况比较复杂,看动图就明白了

65_13.png

65_14.png

二叉搜索树的代码实现

package com.xgc.tree.binarysearchtree;

import java.util.LinkedList;
import java.util.Queue;

/**
 * 二叉搜索树
 * @author xgc
 *
 */
public class BinarySearchTree {

    /**
     * 二叉搜索树的结点
     * @author xgc
     *
     */
    private class Node {
        int data; // 数据域
        Node right; // 右子树
        Node left; // 左子树

        public Node(int data) {
            this.data = data;
        }

        public Node() {
        }
    }

    //树的根节点
    private Node root;

    /**
     * 根据指定的值找出二叉搜索树中对应的结点
     * @param data
     * @return
     */
    public Node find(int data) {
        Node current = root;
        while(current.data != data) {
            if (data > current.data) {
                current = current.right;
            }
            if (data < current.data) {
                current = current.left;
            }
            if (current == null) {
                return null;
            }
        }
        return current;
    }

    /**
     * 二叉搜索树的插入
     * @param data
     */
    public void insert(int data) {
        //要插入的结点
        Node p = new Node(data);

        if (root == null) {
            root = p;
        } else {
            Node parent = new Node();
            Node current = root;
            while(true) {
                parent = current;

                if (data>current.data) {
                    current = current.right;
                    if (current==null) {
                        parent.right = p;
                        return;
                    }
                } else if(data == current.data) {
                    return;
                } else {
                    current = current.left;
                    if (current==null) {
                        parent.left = p;
                        return;
                    }
                }
            }
        }
    }

    /**
     * 删除指定的数据
     * @param data
     * @return 删除操作是否成功
     */
    public boolean delete(int data) {
        Node current = root;
        Node parent = new Node();

        boolean isRightChild = true;

        //查找要删除的结点
        while(current.data != data) {
            parent = current;
            if (data > current.data) {
                current = current.right;
                isRightChild = true;
            } else {
                current = current.left;
                isRightChild = false;
            }
        }

        //要删除的结点为叶结点
        if (current.right == null && current.left == null) {
            if (current == root) {
                root = null;
            } else {
                if (isRightChild) {
                    parent.right = null;
                } else {
                    parent.left = null;
                }
            }

            return true;
        }
        //要删除的结点只有一个子结点
        else if(current.left == null) {
            if (current == root) {
                root = current.right;
            }else if(isRightChild) {
                parent.right = current.right;
            }else {
                parent.left = current.right;
            }

            return true;
        }
        else if(current.right == null) {
            if (current == root) {
                root = current.left;
            }else if(isRightChild) {
                parent.right = current.left;
            }else {
                parent.left = current.left;
            }

            return true;
        }
        //要删除的结点有两个子结点
        else {
            Node successor = getSuccessor(current);

            if (current == root) {
                root = successor;
            } else if (isRightChild) {
                parent.right = successor;
            } else {
                parent.left  =successor;
            }

            successor.left = current.left;
            return true;
        }
    }

    /**
     * 获取给定结点的后继结点
     * 不仅获取后继结点,还对删除结点的右子树结构进行了调整
     * @param current
     * @return 后继结点
     */
    private Node getSuccessor(Node node) {
        Node successorParent = node;
        Node successor = node;
        Node current = node.right;

        while(current!=null) {
            successorParent = successor;
            successor = current;
            current = current.left;
        }
        //到这里后继结点已经找好了

        //如果后继结点为要删除结点的右结点的左子,调整一下要删除结点的右子树
        if (successor != node.right) {
            successorParent.left = successor.right;
            successor.right = node.right;
        }
        return successor;
    }

    public void levelOrderTree() {
        if (root == null) return;
        Queue<Node> nodes = new LinkedList<>();
        nodes.add(root);
        while (!nodes.isEmpty()) {
            Node node = nodes.poll();
            System.out.print(node.data + " ");
            if (node.left != null) {
                nodes.add(node.left);
            }
            if (node.right != null) {
                nodes.add(node.right);
            }
        }
    }

    public void show(Node node) {
        System.out.println(node.data);
    }
}

测试

package com.xgc.tree.binarysearchtree;

public class BinarySearchTreeTest {

    public static void main(String[] args) {
        BinarySearchTree tree = new BinarySearchTree();
        tree.insert(5);
        tree.insert(6);
        tree.insert(7);
        tree.insert(3);
        tree.insert(4);
        tree.insert(7);

        tree.levelOrderTree();

        tree.delete(3);

        System.out.println();
        tree.levelOrderTree();

        System.out.println();
        tree.show(tree.find(7));
    }

}

执行结果为:

5 3 6 4 7 
5 4 6 7 
7

文章永久链接:https://tech.souyunku.com/40723

未经允许不得转载:搜云库技术团队 » 数据结构 - 树以及Java代码实现

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们