数据结构~基础2~树【《二叉树、二叉搜索树、AVL树、B树、红黑树》的设计】~二叉树

简介: 数据结构~基础2~树【《二叉树、二叉搜索树、AVL树、B树、红黑树》的设计】~二叉树

数据结构~基础2~树【《二叉树、二叉搜索树、AVL树、B树、红黑树》的设计】~二叉树

 


●  树的形状【左子树(左区间)】 根(父结点)【右子树(右区间)】

为啥遍历是不断沿着左子树爬下下一层~~~为了实现拿到当前层的第一个结点。

对于树的遍历,到下一层,在形式上是先到了“根”(父结点)上。

 

1、二叉树之通用的接口【遍历】:

序遍历: 根(父)      左子树        右子树   |    根(父)      右子树         左子树     【前序只需满足 根最先访问】

序遍历:左子树        根(父)    右子树   |   右子树        根(父)      左子树   【中序只需满足 根中间访问】

序遍历: 左子树      右子树        根(父)|    右子树         左子树        根(父) 【后序只需满足 根最后访问】

序遍历: 一层一层的结点从左到右进行访问。

 

二叉树的通用接口遍历[前序、中序、后序、层序、允许外界遍历二叉树的元素(设计接口)+增强遍历] + 前驱、后驱结点

                + 树的深度 + 是否为完全二叉树

 

1,前序遍历:

■ 递归:根 左 右

■ 迭代:【使用栈】:不断地下一层【通过node.left方式】:不断地拿到下一层的根,将根数据进行添加

直到到达最后一层【node.left = null】,pop出当前的根结点,通过其切换到右子树。

□ 前序遍历的代码:


//递归实现// 前序遍历需要传入一个结点(根结点),然后才能开始遍历
    private void preorderTraversal(Node<E> node) {
        // 不断地遍历,终有一空便结束
        if (node == null)
            return;
        System.out.println(node.elmement);
        preorderTraversal(node.left);
        preorderTraversal(node.right);
    }
//迭代实现【并将结果存储到List】public List<Integer> preorderTraversal1(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        if (root == null) {
            return res;
        }
        //遍历过的点,不要了可以pop()掉,不断的pop(),然后才能拿到当前结点的右结点(先是不断的更新结点为左结点)
        //然后没有左结点了,开始pop() 一层(一个结点)
        Deque<TreeNode> stack = new LinkedList<TreeNode>();
        TreeNode node = root;
        while (!stack.isEmpty() || node != null) {
            while (node != null) {
                res.add(node.val);
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            node = node.right;
        }
        return res;
    }


2,中序遍历:

■ 递归:左 根 右

■ 迭代:【使用栈】:不断地下一层【通过node.left方式】:不断地拿到下一层的根,

直到到达最后一层【node.left = null】,pop出当前的根结点,将当前根的数据进行添加,通过其切换到右子树。

□ 中序遍历的代码:


// 递归:    private void inorderTraversal(Node<E> node) {
        if (node == null)
            return;
        inorderTraversal(node.left);
        System.out.println(node.elmement);
        inorderTraversal(node.right);
    }
// 迭代
    public List<Integer> inorderTraversal2(TreeNode root) {
        List<Integer> list2 = new ArrayList<>();
        if (root == null)
            return list2;
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode node = root;
        while(node != null || !stack.isEmpty() ) {
            while(node != null) {
                stack.push(node);
                node = node.left;
            }
            node = stack.pop();
            list2.add(node.val);    //已经拿到当前结点了    
            node = node.right;
        }
        return list2;
    }

3,后序遍历:

■ 递归:左 右 根

■ 迭代:【使用栈】:不断地下一层【通过node.left方式】:不断地拿到下一层的根,

直到到达最后一层【node.left = null】,pop出当前的根结点,考虑右结点:【情况一:右边结点存在,将根push回去,切换到右子树】

【情况二:右边结点不存在或者当前结点的右结点是上一个遍历过的结点,将当前结点(根)的数据进行添加,并记录前一个结点。

□ 后序遍历的代码:


// 递归:     private void postorderTraversal(Node<E> node) {
        if (node == null)
            return;
        postorderTraversal(node.left);
        postorderTraversal(node.right);
        System.out.println(node.elmement);
    }
// 迭代
    public List<Integer> postorderTraversal(TreeNode root) {     List<Integer> list = new ArrayList<>();
        if(root == null)    return list;
        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode prev = null;
        while(!stack.isEmpty() || root != null) {
            while(root != null) {
                stack.push(root);
                root = root.left;
            }
            root = stack.pop();       //考虑右结点 
            //右结点不存在,或者右结点是前一个遍历过的结点prev
            if (root.right == null || root.right == prev) {
                list.add(root.val);
                prev = root;
                root = null;    //不加:超出内存
            } else { //右结点存在
                stack.push(root);
                root = root.right;
            }
        }        
        return list;
    }

4,层序遍历:

■ 迭代:【使用队列】(使用队列理由:一层一层从左边到右边【父节点A 先于 父结点B,则父节点A 的孩子 会先于 父结点B 的孩子】):

首先先根入队,然后开始不断地取出当前结点,判断当前结点是否有左结点,有入队,是否有右结点,有入队。



// 层序遍历【迭代】
    public void levelOrderTraversal() {
        if (root == null)
            return;
        Queue<Node<E>> queue = new LinkedList<>();
        queue.offer(root);
        // 只要队列不为空:就不断的出队,然后入队左右子节点
        while (!queue.isEmpty()) {
            Node<E> node = queue.poll();
            System.out.println(node.elmement);
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }
        }
    }


5允许外界遍历二叉树的元素(设计接口)+ 增强遍历【这里以层序遍历 后序遍历(前序、中序跟后序一样)】

(1)层序遍历:

层序遍历【允许外界遍历二叉树的元素的情况的接口设计】


0.png


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------



1.png

 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

层序遍历【允许外界遍历二叉树的元素的情况的接口设计】+ 增强遍历:

增强遍历:通过接口方法的返回值,控制遍历的停止(so 接口方法需要写成可以被控制的,例如布尔类型

 

2.png


 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------


3.png

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

(2)后序遍历:

① 后序遍历【允许外界遍历二叉树的元素的情况的接口设计】跟 层序一致(略)

② 后序遍历【允许外界遍历二叉树的元素的情况的接口设计】+ 增强遍历:


4.png

 -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------


5.png


 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

6,前驱、后驱结点:

 

■ 前提是:中序遍历才有所谓的前驱和后驱结点。

(1) 前驱结点:中序遍历时的前一个结点。

即:前驱结点(就是比当前结点小的前一个结点)。

● 哪个位置的结点有机会有前驱(和 右):


6.png


● “前一个结点”:需要离得最近。

根(看左,找左区间最大的,离得近)

右(看根,找根)

 

●方法(接口):

public Node<E> predecessor(Node<E> node){    

 //当前传入结点(可,需要判断一下:是否有左(有左即可,不需要有右))

   node = node.left;

 //① 若node 非空:则作为“根”,去左区间找最大的

  //② 若node 空:找根(“父结点”),通过判断是否构成“根-右”的关系。【找的过程是逆思维:因为需要遍历 while(“根-左”), 跳出循环则是找到了“根-右”的关系】

}

 

● 具体代码:


// 前驱结点
    public Node<E> predecessor(Node<E> node) {
        // 有左节点(left.right.right.right...)
        Node<E> p = node.left;
        if (p != null) {
            while (p.right != null) {
                p = p.right;
            }
            return p;
        }
        // 没有左节点(视图找到第一个父结点的右结点等于当前结点)
        // 若一直是父节点左,就继续上一层的父节点
        while (node.parent != null &&  node.parent.left == node) {
            node = node.parent;
        }
        // 来到这里:要么父节点是null,要么是 当前结点是父节点的右结点
        return node.parent;
    }


 

(2) 后驱结点:分析同理

 

7,树的深度:

层序遍历+一个辅助变量(用来判断当前层的结点已经遍历到最后一个结点了)实现

 

8,是否是一棵完全二叉树:

一棵完全二叉树的结点情况分析:

(1)遍历当前结点:有左结点,有右边结点 --正确

(2)有左结点,没有右结点   --正常

(3)没有左结点,有右结点  --不正常

(4)没有左结点,也没有右结点  -- 正常

分析:一旦出现第一个结点没有右结点,则接下来的结点都是叶子结点!【且该过程,一旦出现,有右结点没有左结点的“越界”行为,就false】

■ 完全二叉树的代码:


// 判断是否为完全二叉树(方法二:是构建在只要是二叉树就进行层序遍历基础上,加上特殊情况的判断)
    public boolean isComplete2() {
        if (root == null)
            return false;
        Queue<Node<E>> queue = new LinkedList<>();// 犯了个错:Queue 是接口,不可直接new
        // 叶子状态
        boolean leaf = false;
        queue.offer(root);
        while (!queue.isEmpty()) {
            // 出队,后入队左右子树结点
            Node<E> node = queue.poll();
            if (leaf && node.isLeaf()) {
                return false;
            }
            if (node.left != null) {
                queue.offer(node.left);
            } else if (node.right != null) { // 否则左边为空,右边不为空
                return false;
            }
            if (node.right != null) {
                queue.offer(node.right);
            } else { // 否则右边为空,左边可能为空,可能不为空【找到第一个空的右结点,接下来的结点都是叶子结点】
                // 则开始叶子结点的状态
                leaf = true;
            }
        }
        return true;
    }



目录
相关文章
|
15天前
|
存储 Java Serverless
【数据结构】哈希表&二叉搜索树详解
本文详细介绍了二叉搜索树和哈希表这两种数据结构。二叉搜索树是一种特殊二叉树,具有左子树节点值小于根节点、右子树节点值大于根节点的特点,并且不允许键值重复。文章给出了插入、删除和搜索等方法的具体实现。哈希表则通过哈希函数将键名映射为数组下标,实现快速查找,其插入、删除和查找操作时间复杂度理想情况下为O(1)。文中还讨论了哈希函数的设计原则、哈希冲突的解决方法及哈希表的实现细节。
23 8
【数据结构】哈希表&二叉搜索树详解
|
27天前
|
存储 机器学习/深度学习
【数据结构】二叉树全攻略,从实现到应用详解
本文介绍了树形结构及其重要类型——二叉树。树由若干节点组成,具有层次关系。二叉树每个节点最多有两个子树,分为左子树和右子树。文中详细描述了二叉树的不同类型,如完全二叉树、满二叉树、平衡二叉树及搜索二叉树,并阐述了二叉树的基本性质与存储方式。此外,还介绍了二叉树的实现方法,包括节点定义、遍历方式(前序、中序、后序、层序遍历),并提供了多个示例代码,帮助理解二叉树的基本操作。
43 13
【数据结构】二叉树全攻略,从实现到应用详解
|
6天前
|
JSON 前端开发 JavaScript
一文了解树在前端中的应用,掌握数据结构中树的生命线
该文章详细介绍了树这一数据结构在前端开发中的应用,包括树的基本概念、遍历方法(如深度优先遍历、广度优先遍历)以及二叉树的先序、中序、后序遍历,并通过实例代码展示了如何在JavaScript中实现这些遍历算法。此外,文章还探讨了树结构在处理JSON数据时的应用场景。
一文了解树在前端中的应用,掌握数据结构中树的生命线
|
22天前
|
C语言
数据结构基础详解(C语言):图的基本概念_无向图_有向图_子图_生成树_生成森林_完全图
本文介绍了图的基本概念,包括图的定义、无向图与有向图、简单图与多重图等,并解释了顶点度、路径、连通性等相关术语。此外还讨论了子图、生成树、带权图及几种特殊形态的图,如完全图和树等。通过这些概念,读者可以更好地理解图论的基础知识。
|
24天前
|
存储 算法 C语言
数据结构基础详解(C语言): 二叉树的遍历_线索二叉树_树的存储结构_树与森林详解
本文从二叉树遍历入手,详细介绍了先序、中序和后序遍历方法,并探讨了如何构建二叉树及线索二叉树的概念。接着,文章讲解了树和森林的存储结构,特别是如何将树与森林转换为二叉树形式,以便利用二叉树的遍历方法。最后,讨论了树和森林的遍历算法,包括先根、后根和层次遍历。通过这些内容,读者可以全面了解二叉树及其相关概念。
|
24天前
|
存储 机器学习/深度学习 C语言
数据结构基础详解(C语言): 树与二叉树的基本类型与存储结构详解
本文介绍了树和二叉树的基本概念及性质。树是由节点组成的层次结构,其中节点的度为其分支数量,树的度为树中最大节点度数。二叉树是一种特殊的树,其节点最多有两个子节点,具有多种性质,如叶子节点数与度为2的节点数之间的关系。此外,还介绍了二叉树的不同形态,包括满二叉树、完全二叉树、二叉排序树和平衡二叉树,并探讨了二叉树的顺序存储和链式存储结构。
|
24天前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
2月前
|
存储 Java 程序员
"揭秘HashMap底层实现:从数组到链表,再到红黑树,掌握高效数据结构的秘密武器!"
【8月更文挑战第21天】HashMap是Java中重要的数据结构,采用数组+链表/红黑树实现,确保高效查询与更新。构造方法初始化数组,默认容量16,负载因子0.75触发扩容。`put`操作通过计算`hashCode`定位元素,利用链表或红黑树处理冲突。`get`和`remove`操作类似地定位并返回或移除元素。JDK 1.8优化了链表转红黑树机制,提升性能。理解这些原理能帮助我们更高效地应用HashMap。
34 0
|
4天前
|
算法 安全 测试技术
golang 栈数据结构的实现和应用
本文详细介绍了“栈”这一数据结构的特点,并用Golang实现栈。栈是一种FILO(First In Last Out,即先进后出或后进先出)的数据结构。文章展示了如何用slice和链表来实现栈,并通过golang benchmark测试了二者的性能差异。此外,还提供了几个使用栈结构解决的实际算法问题示例,如有效的括号匹配等。
golang 栈数据结构的实现和应用
01_设计一个有getMin功能的栈
01_设计一个有getMin功能的栈