数据结构和算法之二叉树

一、为什么需要树这种数据结构

是因为之前的数组和链表两种存储结构对增删改查都各有利弊,而且差异明显。所以树这种结构就是增删改查效率差异不是很明显,增删改查所耗费时间都比较均衡的一种数据结构。

1、数组存储方式

1.1、优点

通过下标方式访问元素,速度快。对于有序数组,还可以使用二分查找来提高检索速度。

1.2、缺点

若检索具体某个值,或插入值(按一定顺序),会整体移动。效率较低。

2、链表存储方式

2.1、优点

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

2.2、缺点

在进行检索时,效率仍然很低,比如检索某个值,需要从头节点开始遍历进行判断。

3、树存储方式

3.1、说明

可以利用二叉排序树(Binary Sort Tree),既可以保证数据的检索速度,同时也可以保证数据的插入、删除、修改的速度。

二叉排序树就是要求任何左侧子节点的值都大于当前父节点,右侧子节点的值都小于当前父节点。

3.2、案例

3.2.1、假设有一颗二叉排序树【8 4 11 2 6 10 13】,如下图所示

在这里插入图片描述

二叉排序树,左侧子节点的值都大于当前父节点,右侧子节点的值都小于当前父节点。

3.2.2、如下需求

3.2.2.1、查找10

具体步骤:
总共需要两次查找
1、判断10与根节点大小,大于根节点,在右侧查找
2、判断10与11的大小,小于11,则在11的左侧查找,找到!

3.2.2.2、添加15

15大于8,在8的右侧找,15大于11,在11的右侧找,15大于13,所以放到13的右侧节点上,如下图所示:
在这里插入图片描述

3.2.2.3、删除2

2小于8,在8的左边找。找到4,2小于4,在4的左边找,找到了2,将这个节点置为null即可。如下图
在这里插入图片描述

3.2.3、总结

可以发现利用二叉排序树的数据结构,增删改查都很方便,效率相对均衡。

二、树的示意图

在这里插入图片描述

三、树的常用术语

1、节点

没得解释,上图中任何一个圆圈都代表一个节点。

2、根节点

最顶层节点,也就是上图中的A节点。

3、父节点

所有非叶子节点都是父节点。比如:ABCD,他们都有对应的子节点。

4、子节点

比如B和C是A的子节点,H是D的子节点。

5、叶子节点

没有子节点的节点

6、节点的权

节点的值。

7、路径

从root节点找到该节点的路线。比如要找到F,那就是A->C->F这条查找线就称为路径。

8、层

上图画出来了。就是树的每一层。

9、子树

D->H称为根的子树(注意这里是子树,是一棵树,不是单个节点),同理,C->F、G也称为根节点的子树

10、树的高度

最大层数。

11、森林

多颗子树构成森林,比如:整棵树就是一个森林。

四、二叉树的概念

1、基础二叉树

每个节点最多只能有两个子节点。

注意“最多”,也就是说可以有两个,可以只有左子节点,也可以只有右子节点。如下图
在这里插入图片描述

2、满二叉树

该二叉树的所有叶子节点都在最后一层,并且节点总数=2n - 1,n为层数。

如下图就是满二叉树
在这里插入图片描述
再如下图就不是满二叉树,因为不符合所有叶子节点都在最后一层的特点,节点H的问题。
在这里插入图片描述

3、完全二叉树

该二叉树的所有叶子节点都在最后一层或倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续,若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

1.根据定义可知,满二叉树都是完全二叉树。
如图所示,两颗都是完全二叉树。
在这里插入图片描述

五、二叉树的遍历

0、demo

在这里插入图片描述

1、前序遍历

1.1、概念

先输出父节点,再遍历左子树,最后遍历右子树。

1.2、demo前序遍历

A B D H E C F G

1.3、核心代码

/**
* 前序遍历
*/
public void preOrder () {
   // 先输出父节点
   System.out.println(this);
   // 遍历左侧节点
   if (this.left != null) {
       this.left.preOrder();
   }
   // 遍历右侧节点
   if (this.right != null) {
       this.right.preOrder();
   }
}

2、中序遍历

2.1、概念

先遍历左子树,再输出父节点,最后遍历右子树。

2.2、demo中序遍历

D H B E A F C G

2.3、核心代码

/**
 * 中序遍历
 */
public void middleOrder () {
    // 先遍历左侧节点
    if (this.left != null) {
        this.left.middleOrder();
    }
    // 输出父节点
    System.out.println(this);
    // 最后遍历右侧节点
    if (this.right != null) {
        this.right.middleOrder();
    }
}

3、后序遍历

3.1、概念

先遍历左子树,再遍历右子树,最后输出父节点。

3.2、demo后序遍历

H D E B F G C A

3.3、核心代码

/**
 * 后序遍历
 */
public void postOrder () {
    // 先遍历左侧节点
    if (this.left != null) {
        this.left.postOrder();
    }
    // 再遍历右侧节点
    if (this.right != null) {
        this.right.postOrder();
    }
    // 最后输出父节点
    System.out.println(this);
}

4、小结

看父节点的顺序,就确定是前序、中序还是后序。

5、完整代码

package com.chentongwei.tree;

/**
 * Description:
 *
 * @author TongWei.Chen 2019-12-24 15:56:45
 */
public class BinaryTreeDemo {
    public static void main(String[] args) {
        Node root = new Node(1, "A");
        Node node2 = new Node(2, "B");
        root.setLeft(node2);
        Node node3 = new Node(3, "C");
        root.setRight(node3);
        Node node4 = new Node(4, "D");
        node2.setLeft(node4);
        Node node5 = new Node(5, "E");
        node2.setRight(node5);
        Node node6 = new Node(6, "F");
        node3.setLeft(node6);
        Node node7 = new Node(7, "G");
        node3.setRight(node7);
        Node node8 = new Node(8, "H");
        node4.setRight(node8);
        BinaryTree binaryTree = new BinaryTree();
        binaryTree.setRoot(root);
        System.out.println("------------------前序遍历------------------");
        binaryTree.preNode(); // A B D H E C F G
        System.out.println("------------------中序遍历------------------");
        binaryTree.middleNode(); // D H B E A F C G
        System.out.println("------------------后序遍历------------------");
        binaryTree.postNode(); // H D E B F G C A
    }
}

class BinaryTree {
    private Node root;

    public void setRoot(Node root) {
        this.root = root;
    }

    public void preNode () {
        if (this.root == null) {
            System.out.println("根节点为空");
            return;
        }
        this.root.preOrder();
    }

    public void middleNode () {
        if (this.root == null) {
            System.out.println("根节点为空");
            return;
        }
        this.root.middleOrder();
    }

    public void postNode () {
        if (this.root == null) {
            System.out.println("根节点为空");
            return;
        }
        this.root.postOrder();
    }
}

class Node {
    private int id;
    private String value;
    // 左侧节点
    private Node left;
    // 右侧节点
    private Node right;

    public Node(int id, String value) {
        this.id = id;
        this.value = value;
    }

    /**
     * 前序遍历
     */
    public void preOrder () {
        // 先输出父节点
        System.out.println(this);
        // 遍历左侧节点
        if (this.left != null) {
            this.left.preOrder();
        }
        // 遍历右侧节点
        if (this.right != null) {
            this.right.preOrder();
        }
    }

    /**
     * 中序遍历
     */
    public void middleOrder () {
        // 先遍历左侧节点
        if (this.left != null) {
            this.left.middleOrder();
        }
        // 输出父节点
        System.out.println(this);
        // 最后遍历右侧节点
        if (this.right != null) {
            this.right.middleOrder();
        }
    }

    /**
     * 后序遍历
     */
    public void postOrder () {
        // 先遍历左侧节点
        if (this.left != null) {
            this.left.postOrder();
        }
        // 再遍历右侧节点
        if (this.right != null) {
            this.right.postOrder();
        }
        // 最后输出父节点
        System.out.println(this);
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public Node getLeft() {
        return left;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public Node getRight() {
        return right;
    }

    public void setRight(Node right) {
        this.right = right;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Node{");
        sb.append("id=").append(id);
        sb.append(", value='").append(value).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

------------------前序遍历------------------
Node{id=1, value=‘A’}
Node{id=2, value=‘B’}
Node{id=4, value=‘D’}
Node{id=8, value=‘H’}
Node{id=5, value=‘E’}
Node{id=3, value=‘C’}
Node{id=6, value=‘F’}
Node{id=7, value=‘G’}
------------------中序遍历------------------
Node{id=4, value=‘D’}
Node{id=8, value=‘H’}
Node{id=2, value=‘B’}
Node{id=5, value=‘E’}
Node{id=1, value=‘A’}
Node{id=6, value=‘F’}
Node{id=3, value=‘C’}
Node{id=7, value=‘G’}
------------------后序遍历------------------
Node{id=8, value=‘H’}
Node{id=4, value=‘D’}
Node{id=5, value=‘E’}
Node{id=2, value=‘B’}
Node{id=6, value=‘F’}
Node{id=7, value=‘G’}
Node{id=3, value=‘C’}
Node{id=1, value=‘A’}

发布了28 篇原创文章 · 获赞 33 · 访问量 8300

猜你喜欢

转载自blog.csdn.net/ctwctw/article/details/103667308