• 二叉树认识与编程实现


    欢迎浏览我的个人博客
    转载请注明出处 https://pushy.site

    1. 树

    我们都知道,树是一种一对多的数据结构,它是由n个有限节点组成的一个具有层次关系的集合,它有如下的特点:

    • 根节点是唯一的(老大当然是一个~);
    • 每个节点都有零个或者必须多个子节点(丁克、独生子、双胞胎、三胞胎...);
    • 每一个非根节点有且只有一个父节点(只有一个老爹,没有老王)

    如下图,A即是根节点:

    510px-Treedatastructure.png

    2. 二叉树

    从名字和图中可以看出,二叉树应该是一种特殊形式的树:

    576px-Binary_tree.svg.png

    没错!它和树相比,有一下的自己的特点:

    • 每个节点最多有两颗树(可以丁克,可以独生子,可以双胞胎,但是不允许三胞胎、四胞胎...);

    • 左子树和右子树是有顺序的,次序不能任意颠倒(老大老二分明);

    • 即使树中的某节点只有一棵子树,也要区别它是左子树还是右子树(独生子也要称老大!);

    2.1 特殊二叉树

    另外,二叉树还有特殊的形式:

    满二叉树:所有分支节点都存在左子树和右子树,并且所有的叶子都在同一层上。

    full-tree.png

    完全二叉树:在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点。

    complete-tree.png

    2.2 遍历方式

    先序遍历

    TIM截图20181112191209.png

    先序遍历的流程是:若二叉树为空, 则空操作返回。否则先访问根节点,然后前序遍历左子树,再前序遍历右子树。遍历的顺序是:A-B-D-G-H-C-E-I-F

    中序遍历

    middle.png

    中序遍历的流程是:若树为空,则空操作返回,否则从根节点开始(注意并不是先访问根节点,而是一直找到左子树的叶子),然后中序遍历根节点的左子树,然后是访问根节点,最后中序遍历右子树。遍历的顺序为:G-D-H-B-A-E-I-C-F。

    后序遍历

    post.png

    后序遍历的流程是:若树为空, 则空操作为空,否则从左到右先叶子后节点的方式遍历访问左右子树,最后是访问根节点。遍历的顺序为:G-H-D-B-I-E-F-C-A。

    3. 编程实现

    3.1 C

    首先来看C语言的实现,定义结构体BiTNode,表示每个结点的结构体。并定义BiTNode类型的指针变量为BiTree

    typedef struct BiTNode {
        int data;  // 数据域
        struct BiTNode *lChild;  // 左子节点
        struct BiTNode *rChild;  // 右子节点
    } BiTNode;
    
    typedef BiTNode* BiTree;
    

    然后定义CreateBiTree,通过先序递归的方法来创建二叉树。当输入的值为-1时代表当前创建的节点无左子节点或者右子节点:

    void CreateBiTree(BiTree *T) {
        TElementType ch;
        scanf("%d", &ch);
        if (ch == -1) {
            *T = NULL;
            return;
        }
        else
        {
            *T = (BiTree) malloc(sizeof(BiTNode));  // 申请内存空间,为BiTNode大小
            (*T)->data = ch;  // 设置数据域的值为输入的值
            printf("输入%d的左子节点:",ch);
            CreateBiTree(&(*T)->lChild);  // 递归调用,构造左子树
            printf("输入%d的右子节点:",ch);
            CreateBiTree(&(*T)->rChild);  // 递归调用,构造右子树
        }
    }
    

    遍历的方式是从根节点开始遍历,先访问左子节点,该访问左子节点的左子节点,直到访问的左子节点的左子节点为空(T==NULL)。然后依次返回上一次递归调用的地方,开始访问右节点。直到返回到第一次递归调用的地方,开始访问根节点的右子节点,并开始递归调用访问。

    因此在遍历的方法中,我们可以反复地递归调用PreOrderTraverse来遍历二叉树的所有子节点:

    void PreOrderTraverse(BiTree T) {
        if (T == NULL) {
            return;
        }
        printf("%d", T->data);
        // 递归调用,从根节点的左节点开始遍历
        PreOrderTraverse(T->lChild);
        PreOrderTraverse(T->rChild);
    }
    

    同理,我们可以通过递归的方式来实现中序遍历二叉树:

    void InOrderTraverse(BiTree T) {
    
        if (T == NULL) {
            return;
        }
    
        InOrderTraverse(T->lChild);
        printf("%d", T->data);
        InOrderTraverse(T->rChild);
    }
    

    后序遍历也一样:

    void PostOrderTraverse(BiTree T) {
        if (T == NULL) {
            return;
        }
    
        PostOrderTraverse(T->lChild);
        PostOrderTraverse(T->rChild);
        printf("%d", T->data);
    }
    

    3.2 Java

    利用Java面向对象的特点,我们能更简单地实现二叉树的结构。首先定义节点类Nodeleftright是对左子节点和右子节点的引用:

    static class Node {
    
        public Integer data;   // 数据域,当前节点存储的数值
        public Node left;
        public Node right;
    
        public Node(Integer data) {
            this.data = data;
        }
    }
    

    定义BinaryTree类,提供createTree静态方法进行手动创建根节点,并创建该根节点的左子节点和右子节点,并添加到根节点的引用,最后返回该根节点。

    先序递归遍历的方法和C语言实现的差不多:

    public class BinaryTree {
        
        /**
         * 测试创建二叉树
         */
        public static Node createTree() {
            Node root = new Node(1);
            Node headLeft = new Node(2);  // 创建左节点
            Node headRight = new Node(3); // 创建右节点
    
            root.left = headLeft;  // 添加引用
            root.right = headRight;
    
            return root; 
        }
        
        /**
         * 递归实现先序遍历二叉树
         */
        public static void preOrderTraverse(Node node) {
            if (node == null) {
                return;
            }
            System.out.print(node.data);
            preOrderTraverse(node.left);
            preOrderTraverse(node.right);
        }
    }
    

    另外,我们还可以不使用递归,而是使用栈来实现先序遍历二叉树的所有节点。实现的原理是:首先将根节点丢入栈中,每次都取出栈顶节点,如果存在右子节点或者左子节点则放入栈中。需要注意的是,必须是先将右子节点放入栈中,因为先序遍历的话输出的时候左节点优先于右节点输出。

    这种遍历的方式称之为深度优先遍历,即从根节点出发,沿着左子树方向进行纵向遍历,直到找到叶子节点为止。然后回溯到前一个节点,进行右子树节点的遍历,直到遍历完所有可达节点为止。

    /**
     * 深度优先遍历,相当于先序遍历
     * 使用栈非递归实现二叉树的遍历
     */
    public static void DFS(Node root) {
        if (root == null) {
            return;
        }
        Stack<Node> nodes = new Stack<>();
        nodes.add(root);
    
        while (!nodes.isEmpty()) {
            // 取出栈顶元素,判断是否有子节点
            Node temp = nodes.pop();
            System.out.println("当前子节点的值: " + temp.data);
    
            if (temp.right != null) {
                nodes.push(temp.right);
            }
    
            if (temp.left != null) {
                nodes.push(temp.left);
            }
        }
    }
    

    如果你听说过深度优先遍历(DFS),那你肯定也知道广度优先遍历(BFS),它是从根节点出发,在横向遍历二叉树层段节点的基础上纵向遍历二叉树的层次。下边我们需要借助队列的数据结构来实现广度优先遍历:

    /**
     * 广度优先遍历
     * 使用队列非递归的方式实现二叉树的遍历
     */
    public static void BFS(Node root) {
        if (root == null) {
            return;
        }
        Queue<Node> nodes = new ArrayDeque<>();
        nodes.add(root);
    
        while (!nodes.isEmpty()) {
            Node temp = nodes.remove();
            System.out.println("当前的子节点为: " + temp.data);
            if (temp.left != null) {
                nodes.add(temp.left);
            }
            if (temp.right != null) {
                nodes.add(temp.right);
            }
        }
    }
    

    维基百科上有一张动图能很好地展示出广度优先遍历的过程:

    白色代表尚未加入队列且未遍历,灰色代表加入队列等待遍历,黑色则代表已经被遍历。

    最后,尽管代码在文中基本给出,但是还是准备了一个小demo。因为我个人看博文的话,如果没有给出一个完整的demo,感觉很难受QAQ...

  • 相关阅读:
    Get-CrmSetting返回Unable to connect to the remote server的解决办法
    Dynamics 365中的常用Associate和Disassociate消息汇总
    Dynamics 365 Customer Engagement V9 活动源功能报错的解决方法
    Dynamics Customer Engagement V9版本配置面向Internet的部署时候下一步按钮不可点击的解决办法
    Dynamics 365检查工作流、SDK插件步骤是否选中运行成功后自动删除系统作业记录
    注意,更改团队所属业务部门用Update消息无效!
    Dynamics 365的审核日志分区删除超时报错怎么办?
    Dynamics 365使用Execute Multiple Request删除系统作业实体记录
    Dynamics 365的系统作业实体记录增长太快怎么回事?
    Dynamics CRM日期字段查询使用时分秒的方法
  • 原文地址:https://www.cnblogs.com/Pushy/p/9955745.html
Copyright © 2020-2023  润新知