【数据结构】二叉树及前中后序遍历

简介: 【数据结构】二叉树及前中后序遍历

一、二叉树

1、为什么需要树

1)数组存储方式的分析

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

缺点:如果要检索具体某个值,或者插入值(按一定顺序)会整体移动,效率较低

2)链式存储方式的分析

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

缺点:在进行检索时,效率仍然较低,比如(检索某个值,需要从头节点开始遍历)

3)树存储方式的分析

可以提高数据存储,读取的效率

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

2、树的常用术语

  1. 节点
  2. 根节点
  3. 父节点
  4. 子节点
  5. 叶子节点:没有子节点的节点
  6. 节点的权:节点值
  7. 路径:从root 节点找到该节点的路线
  8. 子树
  9. 树的高度(最大层数)
  10. 森林 :多颗子树构成森林

3、二叉树概述

  1. 二叉树:每个节点最多只能有两个子节点(左节点和右节点)
  2. 满二叉树:二叉树的所有叶子节点都在最后一层,并且结点总数=2^n-1(n 为层数)
  3. 完全二叉树:二叉树的所有叶子节点都在最后一层或者倒数第二层,而且最后一层的叶子节点在左边连续,倒数第二层的叶子节点在右边连续

4、二叉树遍历概述

  1. 前序遍历:先输出父节点,再遍历左子树和右子树
  2. 中序遍历:先遍历左子树,再输出父节点,再遍历右子树
  3. 后序遍历:先遍历左子树,再遍历右子树,最后输出父节点

小结:看输出父节点的顺序,就确定是前序,中序还是后序

5、二叉树代码实现

package work.rexhao.tree;

/**
 * 二叉树的遍历和查找
 *
 * @author 王铭颢
 * @Date 2022/7/5 10:23
 */
public class BinaryTreeDemo {
   
    public static void main(String[] args) {
   
        BinaryTree bt = new BinaryTree();
        System.out.print("先序遍历: ");
        BinaryTree.preOrder(bt.head);//先序遍历:12453
        System.out.println();
        System.out.print("中序遍历: ");
        BinaryTree.infixOrder(bt.head);//中序遍历:42513
        System.out.println();
        System.out.print("后序遍历: ");
        BinaryTree.postOrder(bt.head);//后续遍历:45231
        System.out.println();
        System.out.println("------------");
        System.out.println("先序查找3: " + BinaryTree.preOrderSearch(bt.head, 3));
        System.out.println("先序查找6: " + BinaryTree.preOrderSearch(bt.head, 6));
        System.out.println("中序查找3: " + BinaryTree.infixOrderSearch(bt.head, 3));
        System.out.println("中序查找6: " + BinaryTree.infixOrderSearch(bt.head, 6));
        System.out.println("后序查找3: " + BinaryTree.postOrderSearch(bt.head, 3));
        System.out.println("后序查找6: " + BinaryTree.postOrderSearch(bt.head, 6));
        System.out.println("------------");
        BinaryTree.delTree(bt.head, 2);
        BinaryTree.infixOrder(bt.head);//中序遍历:13
        System.out.println();
        BinaryTree.delTree(bt.head, 6);
        BinaryTree.infixOrder(bt.head);//中序遍历:13
        System.out.println();
        System.out.println("------------");
        bt = new BinaryTree();
        BinaryTree.delNode(bt.head, 2);
        BinaryTree.infixOrder(bt.head);//中序遍历:453
        System.out.println();
    }
}

/**
 * 二叉树
 */
class BinaryTree {
   
    TreeNode head;

    public BinaryTree() {
   
        head = new TreeNode(1);
        head.leftNode = new TreeNode(2);
        head.rightNode = new TreeNode(3);
        head.leftNode.leftNode = new TreeNode(4);
        head.leftNode.rightNode = new TreeNode(5);
        /*
        初始化树结构示意图
                 1
               /  \
              2    3
            /  \
           4    5

        先序遍历:12453
        中序遍历:42513
        后续遍历:45231
         */
    }

    /**
     * 先序遍历
     */
    public static void preOrder(TreeNode tn) {
   
        if (tn != null) {
   
            System.out.print(tn.getData());
            preOrder(tn.leftNode);
            preOrder(tn.rightNode);
        }
    }

    /**
     * 中序遍历
     */
    public static void infixOrder(TreeNode tn) {
   
        if (tn != null) {
   
            infixOrder(tn.leftNode);
            System.out.print(tn.getData());
            infixOrder(tn.rightNode);
        }
    }

    /**
     * 后序遍历
     */
    public static void postOrder(TreeNode tn) {
   
        if (tn != null) {
   
            postOrder(tn.leftNode);
            postOrder(tn.rightNode);
            System.out.print(tn.getData());
        }
    }

    /**
     * 先序查找
     */
    public static boolean preOrderSearch(TreeNode tn, int target) {
   
        if (tn == null) return false;
        if (target == tn.getData()) {
   
            return true;
        } else if (preOrderSearch(tn.leftNode, target)) {
   
            return preOrderSearch(tn.leftNode, target);
        } else {
   
            return preOrderSearch(tn.rightNode, target);
        }
    }

    /**
     * 中序查找
     */
    public static boolean infixOrderSearch(TreeNode tn, int target) {
   
        if (tn == null) return false;
        if (infixOrderSearch(tn.leftNode, target)) {
   
            return infixOrderSearch(tn.leftNode, target);
        } else if (target == tn.getData()) {
   
            return true;
        } else {
   
            return infixOrderSearch(tn.rightNode, target);
        }
    }

    /**
     * 后序查找
     */
    public static boolean postOrderSearch(TreeNode tn, int target) {
   
        if (tn == null) return false;
        if (postOrderSearch(tn.leftNode, target)) {
   
            return postOrderSearch(tn.leftNode, target);
        } else if (postOrderSearch(tn.rightNode, target)) {
   
            return postOrderSearch(tn.rightNode, target);
        } else {
   
            return target == tn.getData();
        }
    }

    /**
     * 删除子树
     * 叶子结点:删除节点
     * 非叶子节点:删除子树
     * 注:不删除根节点
     */
    public static void delTree(TreeNode tn, int target) {
   
        if (tn == null) {
   
            return;
        }
        if (tn.leftNode != null) {
   
            if (tn.leftNode.getData() == target) {
   
                tn.leftNode = null;
                return;
            } else {
   
                delTree(tn.leftNode, target);
            }
        }
        if (tn.rightNode != null) {
   
            if (tn.rightNode.getData() == target) {
   
                tn.rightNode = null;
                return;
            } else {
   
                delTree(tn.rightNode, target);
            }
        }
    }

    /**
     * 删除节点
     * 叶子节点:删除
     * 非叶子节点(一个子节点):替代原节点
     * 非叶子节点(两个子节点):左节点代替原节点
     */
    public static void delNode(TreeNode tn, int target) {
   
        if (tn == null) return;
        if (tn.leftNode != null) {
   
            if (tn.leftNode.getData() == target) {
   
                if (tn.leftNode.rightNode == null && tn.leftNode.leftNode == null) {
   
                    // 叶子节点
                    tn.leftNode = null;
                } else if (tn.leftNode.rightNode != null && tn.leftNode.leftNode != null) {
   
                    // 两个节点
                    tn.leftNode = tn.leftNode.leftNode;
                } else {
   
                    // 一个节点
                    if (tn.leftNode.leftNode != null) {
   
                        // 左节点
                        tn.leftNode = tn.leftNode.leftNode;
                    } else {
   
                        // 右节点
                        tn.leftNode = tn.leftNode.rightNode;
                    }
                }
                return;
            } else {
   
                delNode(tn.leftNode, target);
            }
        }
        if (tn.rightNode != null) {
   
            if (tn.rightNode.getData() == target) {
   
                if (tn.rightNode.rightNode == null && tn.rightNode.leftNode == null) {
   
                    // 叶子节点
                    tn.rightNode = null;
                } else if (tn.rightNode.rightNode != null && tn.rightNode.leftNode != null) {
   
                    // 两个节点
                    tn.rightNode = tn.rightNode.leftNode;
                } else {
   
                    // 一个节点
                    if (tn.rightNode.leftNode != null) {
   
                        // 左节点
                        tn.rightNode = tn.rightNode.leftNode;
                    } else {
   
                        // 右节点
                        tn.rightNode = tn.rightNode.rightNode;
                    }
                }
                return;
            } else {
   
                delNode(tn.rightNode, target);
            }
        }
    }

}

/**
 * 二叉树的节点
 */
class TreeNode {
   
    private int data;
    TreeNode leftNode;
    TreeNode rightNode;

    TreeNode(int data) {
   
        this.data = data;
    }

    public int getData() {
   
        return data;
    }

    public void setData(int data) {
   
        this.data = data;
    }


}

二、顺序存储二叉树

1、顺序存储二叉树的说明

从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组

2、顺序存储二叉树的特点

  1. 顺序二叉树通常只考虑完全二叉树
  2. 第n个元素的左子节点为 2*n+1
  3. 第n个元素的右子节点为 2*n+2
  4. 第n个元素的父节点为 (n-1)/2
  5. n:表示二叉树中的第几个元素(按0开始编号)

3、顺序存储二叉树代码实现

package work.rexhao.tree;

/**
 * 顺序存储二叉树
 *
 * @author 王铭颢
 * @Date 2022/7/5 14:56
 */
public class ArrBinaryTreeDemo {
   
    public static void main(String[] args) {
   
        ArrBinaryTree abt = new ArrBinaryTree(7);
        for (int i = 0; i < abt.data.length; i++) {
   
            abt.data[i] = i + 1;
        }
        System.out.print("前序:");
        ArrBinaryTree.preOrder(abt, 0);
        System.out.println();
        System.out.print("中序:");
        ArrBinaryTree.infixOrder(abt, 0);
        System.out.println();
        System.out.print("后序:");
        ArrBinaryTree.postOrder(abt, 0);
        System.out.println();

    }
}

class ArrBinaryTree {
   
    int[] data;

    ArrBinaryTree(int maxSize) {
   
        data = new int[maxSize];
    }

    /**
     * 先序遍历
     */
    public static void preOrder(ArrBinaryTree abt, int index) {
   
        // 数组越界
        if (index >= abt.data.length) {
   
            return;
        }
        System.out.print(abt.data[index]);
        preOrder(abt, index * 2 + 1);
        preOrder(abt, index * 2 + 2);
    }

    /**
     * 中序遍历
     */
    public static void infixOrder(ArrBinaryTree abt, int index) {
   
        // 数组越界
        if (index >= abt.data.length) {
   
            return;
        }
        infixOrder(abt, index * 2 + 1);
        System.out.print(abt.data[index]);
        infixOrder(abt, index * 2 + 2);
    }

    /**
     * 后序遍历
     */
    public static void postOrder(ArrBinaryTree abt, int index) {
   
        // 数组越界
        if (index >= abt.data.length) {
   
            return;
        }
        postOrder(abt, index * 2 + 1);
        postOrder(abt, index * 2 + 2);
        System.out.print(abt.data[index]);
    }
}

3、后序线索二叉树

...

目录
相关文章
|
7月前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
196 10
 算法系列之数据结构-二叉树
|
9月前
|
Java C++
【C++数据结构——树】二叉树的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现二叉树的基本运算。​ 相关知识 创建二叉树 销毁二叉树 查找结点 求二叉树的高度 输出二叉树 //二叉树节点结构体定义 structTreeNode{ intval; TreeNode*left; TreeNode*right; TreeNode(intx):val(x),left(NULL),right(NULL){} }; 创建二叉树 //创建二叉树函数(简单示例,手动构建) TreeNode*create
187 12
|
9月前
|
C++
【C++数据结构——树】二叉树的性质(头歌实践教学平台习题)【合集】
本文档介绍了如何根据二叉树的括号表示串创建二叉树,并计算其结点个数、叶子结点个数、某结点的层次和二叉树的宽度。主要内容包括: 1. **定义二叉树节点结构体**:定义了包含节点值、左子节点指针和右子节点指针的结构体。 2. **实现构建二叉树的函数**:通过解析括号表示串,递归地构建二叉树的各个节点及其子树。 3. **使用示例**:展示了如何调用 `buildTree` 函数构建二叉树并进行简单验证。 4. **计算二叉树属性**: - 计算二叉树节点个数。 - 计算二叉树叶子节点个数。 - 计算某节点的层次。 - 计算二叉树的宽度。 最后,提供了测试说明及通关代
167 10
|
9月前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
263 3
|
10月前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
9月前
|
数据采集 存储 算法
【C++数据结构——图】图的遍历(头歌教学实验平台习题) 【合集】
本文介绍了图的遍历算法,包括深度优先遍历(DFS)和广度优先遍历(BFS)。深度优先遍历通过递归方式从起始节点深入探索图,适用于寻找路径、拓扑排序等场景;广度优先遍历则按层次逐层访问节点,适合无权图最短路径和网络爬虫等应用。文中提供了C++代码示例,演示了如何实现这两种遍历方法,并附有测试用例及结果,帮助读者理解和实践图的遍历算法。
330 0
|
11月前
|
机器学习/深度学习 存储 算法
数据结构实验之二叉树实验基础
本实验旨在掌握二叉树的基本特性和遍历算法,包括先序、中序、后序的递归与非递归遍历方法。通过编程实践,加深对二叉树结构的理解,学习如何计算二叉树的深度、叶子节点数等属性。实验内容涉及创建二叉树、实现各种遍历算法及求解特定节点数量。
261 4
|
11月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
227 59
|
4月前
|
编译器 C语言 C++
栈区的非法访问导致的死循环(x64)
这段内容主要分析了一段C语言代码在VS2022中形成死循环的原因,涉及栈区内存布局和数组越界问题。代码中`arr[15]`越界访问,修改了变量`i`的值,导致`for`循环条件始终为真,形成死循环。原因是VS2022栈区从低地址到高地址分配内存,`arr`数组与`i`相邻,`arr[15]`恰好覆盖`i`的地址。而在VS2019中,栈区先分配高地址再分配低地址,因此相同代码表现不同。这说明编译器对栈区内存分配顺序的实现差异会导致程序行为不一致,需避免数组越界以确保代码健壮性。
61 0
栈区的非法访问导致的死循环(x64)
232.用栈实现队列,225. 用队列实现栈
在232题中,通过两个栈(`stIn`和`stOut`)模拟队列的先入先出(FIFO)行为。`push`操作将元素压入`stIn`,`pop`和`peek`操作则通过将`stIn`的元素转移到`stOut`来实现队列的顺序访问。 225题则是利用单个队列(`que`)模拟栈的后入先出(LIFO)特性。通过多次调整队列头部元素的位置,确保弹出顺序符合栈的要求。`top`操作直接返回队列尾部元素,`empty`判断队列是否为空。 两题均仅使用基础数据结构操作,展示了栈与队列之间的转换逻辑。