【树】基本概念 图解 Java描述

一、基本概念

与顺序表、栈和队列这种一对一的线性结构不同,树是一种一对多的线性结构。这里的一对多是指一个元素最多有一个前驱节点,并且可以有多个后继节点。

树是n个节点的有穷集,n=0时称为空树,n>0称为非空树;

非空树中的每个元素称为节点(node);其中最顶端没有前驱节点的节点称为根节点或树根(root);

当n>1时,其余节点可分为m个互不相交的集合,每个集合本身也是一棵树,被称为子树(subtree);

接下来介绍树中的几个名词:

  • 节点拥有的子树的数量称之为(Degree)。度为0的节点称为终端节点或叶节点(Leaf),度不为零的节点称为分支节点。除了叶节点以外,根节点和分支节点统称为内部节点。节点的子树的根称为该节点的孩子(Child),该节点称为孩子的双亲或父节点。同一个双亲的孩子之间互相称为兄弟节点树的度是各个节点度的最大值。
  • 节点的层次(Level)从根开始定义,根为第一层根的孩子为第二层。双亲在同一层的结点互为堂兄弟。树中结点的最大层次称为树的深度(Depth)或高度。如果将树中结点的各个子树看成从左到右是有次序的,不能互换的,则称该树为有序树,否则称为无序树森林是 m(m>=0)棵互不相交的树的集合。

树

二、树的存储结构

由于树中每个结点的孩子可以有多个,所以简单的顺序存储结构无法满足树的实现要求。下面介绍两种常用的物理存储结构来表示树的结构:

  1. 链式存储结构;
  2. 数组;

1. 链式存储结构

链式存储结构最为直观,声明一个节点Node,然后节点内定义指针指向孩子节点,如下图:
链式存储

public class Tree<E> {

    private class Node {
        E data;

        Node left;
        Node right;

        Node(E data, Node left, Node right) {
            this.data = data;
            this.left = left;
            this.right = right;
        }
    }
    
    // ...
}

2. 数组存储

使用数组结构存储时,会将树中的节点按一定的层次结构存储到数组中。假如某个节点为空,那么相应数组中的位置也将空出来:
在这里插入图片描述
之所以这样在相应的位置空出来,是为了可以更快的查找到二叉树的孩子节点和双亲节点,假如一个双亲节点的下标为parent,那么它的两个孩子节点就是:

2 * parent + 1 和 2 * parent + 2。放过来也可以从孩子节点的下标推出双亲节点的下标。

三、二叉树

1. 基本概念

二叉树基本都不陌生,像上面存储结构中所展示的图就都是二叉树。二叉树(Binary Tree)是每个节点最多有两个子树,通常被称为“左子树(left subtree)”和“右子树(right subtree)”。除此之外,二叉树的子树是拥有左右子树次序之分的,像下面的两棵树,是同一棵树,但是又是不同的二叉树:
在这里插入图片描述

2. 几种常见的二叉树

接下来介绍二叉树中几种常见的树,它们在二叉树的应用中(比如说查找)都很有价值:

2.1 斜树

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

斜树每一层只有一个结点,结点个数与二叉树的深度相同。其实斜树就是线性表结构,基本上就退化到与链表相识的结构了。

2.2 满二叉树

一个二叉树的所有非叶子节点都存在左右孩子节点,并且所有的叶子节点都在同一层级,这样的二叉树称为满二叉树。

在这里插入图片描述

2.3 完全二叉树

若设二叉树的高度为 h,除第 h 层外,其他各层(1 到 h-1)的结点数都达到最大个数,第 h 层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
在这里插入图片描述
从上可以看出,完全二叉树具有以下特点:

  • 叶子只能出现在最下层;
  • 拥有同样节点数的二叉树,完全二叉树的深度最小。

2.4 平衡二叉树

平衡二叉树又称为 AVL树(区别于AVL算法),它是一颗二叉树,且具有以下性质:它是一颗空树或它的左右两个子树的高度差的绝对值不超过1,并且每个节点两个子树(如果有节点的话)都是一颗平衡二叉树。

四、二叉树的实现与遍历

1. 数组存储实现

public class ArrayBinaryTree<E> {

    private static final int DEFAULT_DEPTH = 5; // 定义二叉树的深度
    private int size = 0;
    private E[] data;

    ArrayBinaryTree() {
        this(DEFAULT_DEPTH);
    }

    @SuppressWarnings("unchecked")
    ArrayBinaryTree(int depth) {
        data = (E[]) new Object[(int) Math.pow(2, depth)];
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public int getSize() {
        return size;
    }

    // 获取指定结点的父结点
    public E getParent(int index) {
        checkIndex(index);
        if (index == 1) {
            throw new RuntimeException("根节点不存在父节点!");
        }
        // 判断节点是左孩子节点还是右孩子节点
        if (index % 2 == 1) { // 左孩子节点
            return data[(index - 1) / 2];
        } else { // 右孩子节点
            return data[(index - 2) / 2];
        }
    }

    // 获取左子结点
    public E getLeft(int index) {
        checkIndex(index * 2 + 1);
        return data[index * 2 + 1];
    }

    // 获取右子结点
    public E getRight(int index) {
        checkIndex(index * 2 + 2);
        return data[index * 2 + 2];
    }

    // 返回指定数据的位置
    public int indexOf(E value) {
        if (value == null) {
            throw new NullPointerException();
        } else {
            for (int i = 0; i < data.length; i++) {
                if (value.equals(data[i])) {
                    return i;
                }
            }
        }
        return -1;
    }

    // 顺序添加元素
    public void add(E element) {
        checkIndex(size);
        data[size] = element;
        size++;
    }

    // 在指定位置添加元素
    public void add(E element, int parent, boolean isLeft) {
        if (data[parent] == null) {
            throw new RuntimeException("index[" + parent + "] is not Exist!");
        }
        if (element == null) {
            throw new NullPointerException();
        }

        if (isLeft) { // 判断是否为左孩子节点
            checkIndex(2 * parent + 1);
            if (data[parent * 2 + 1] != null) {
                throw new RuntimeException("index[" + parent * 2 + 1 + "] is Exist!");
            }
            data[2 * parent + 1] = element;
        } else {
            checkIndex(2 * parent + 2);
            if (data[(parent + 1) * 2] != null) {
                throw new RuntimeException("index[" + parent * 2 + 2 + "] is  Exist!");
            }
            data[2 * parent + 2] = element;
        }
        size++;
    }

    // 检查下标是否越界
    private void checkIndex(int index) {
        if (index < 0 || index >= data.length) {
            throw new IndexOutOfBoundsException();
        }
    }

    public static void main(String[] args) {
        char[] data = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'};
        ArrayBinaryTree<Character> abt = new ArrayBinaryTree();
        for (int i = 0; i < data.length; i++) {
            abt.add(data[i]);
        }
        System.out.print(abt.getParent(abt.indexOf('J')));
    }
}

2. 链式存储实现

public class LinkedBinaryTree<E> {

    private List<Node> nodes = null;

    private class Node {
        Node leftChild;
        Node rightChild;
        E data;

        Node(E data) {
            this.data = data;
        }
    }

    // 获取根节点
    public Node getRoot() {
        return nodes.get(0);
    }

    public void createBinTree(E[] array) {
        nodes = new LinkedList<Node>();

        for (int i = 0; i < array.length; i++) {
            nodes.add(new Node(array[i]));
        }

        // 对前 last-1 个父节点按照父节点与孩子结点的数字关系建立二叉树
        for (int i = 0; i < array.length / 2 - 1; i++) {
            nodes.get(i).leftChild = nodes.get(i * 2 + 1);
            nodes.get(i).rightChild = nodes.get(i * 2 + 2);
        }

        // 最后一个父节点:因为最后一个父节点可能没有右孩子,所以单独拿出来处理
        int lastParent = array.length / 2 - 1;
        nodes.get(lastParent).leftChild = nodes.get(lastParent * 2 + 1);

        // 右孩子,如果数组的长度为奇数才建立右孩子
        if(array.length % 2 == 1) {
            nodes.get(lastParent).rightChild = nodes.get(lastParent * 2 + 2);
        }
    }

    public static void main(String[] args) {
        Character[] data = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'};
        LinkedBinaryTree<Character> ldt = new LinkedBinaryTree();
        ldt.createBinTree(data);
    }
}

3. 二叉树的遍历

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

二叉树的遍历主要有四种:前序遍历、中序遍历、后序遍历和层序遍历。

3.1 前序遍历

// 数组存储
public void preOrderTraverse(int index) {
    if(datas[index] == null)
        return;
    System.out.print(datas[index] + " ");
    preOrderTraverse(index * 2);
    preOrderTraverse(index * 2 + 1);
}

// 链式存储
public void preOrderTraverse(Node node) {
    if(node == null)
        return;
    System.out.print(node.data + " ");
    preOrderTraverse(node.leftChild);
    preOrderTraverse(node.RightChild);
}

3.2 中序遍历

// 链式存储
public void inOrderTraverse(Node node) {
    if(node == null)
        return;
    inOrderTraverse(node.leftChild);
    System.out.print(node.data + " ");
    inOrderTraverse(node.rightChild);
}

3.2 后序遍历

// 链式存储
public void postOrderTraverse(Node node) {
    if(node == null)
        return;
    postOrderTraverse(node.leftChild);
    postOrderTraverse(node.rightChild);
    System.out.print(node.data + " ");
}

五、其他树

猜你喜欢

转载自blog.csdn.net/Allen_Adolph/article/details/106898546
今日推荐