二叉树

作者: 狠狠狠努力的疯子 | 来源:发表于2020-10-10 17:55 被阅读0次

    1.二叉树特点

    由二叉树定义以及图示分析得出二叉树有以下特点:

    1)每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点。
    2)左子树和右子树是有顺序的,次序不能任意颠倒。
    3)即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。
    4)无子树的节点也称为叶子

    2.二叉树性质

    1)在二叉树的第i层上最多有2i-1 个节点 。(i>=1)
    2)二叉树中如果深度为k,那么最多有2k-1个节点。(k>=1)
    3)n0=n2+1 n0表示度数为0的节点数,n2表示度数为2的节点数。
    4)在完全二叉树中,具有n个节点的完全二叉树的深度为[log2n]+1,其中[log2n]是向下取整。
    5)若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点有如
    (1) 若 i=1,则该结点是二叉树的根,无双亲, 否则,编号为 [i/2] 的结点为其双亲结点;
    (2) 若 2i>n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子结点;
    (3) 若 2i+1>n,则该结点无右孩子结点, 否则,编号为2i+1 的结点为其右孩子结点。

    3.斜树

    往一边倾斜的数,就叫斜树,例如:所有的节点都是只有左子女节点的树就叫左斜树;反过来只有右子女节点的树就叫右斜树。

    4.满二叉树

    满二叉树:在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
    特点

    1)叶子只能出现在最下一层。出现在其它层就不可能达成平衡。
    2)非叶子结点的度一定是2。
    3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。

    5.完全二叉树

    完全二叉树:对一颗具有n个结点的二叉树按层编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
    特点

    1)叶子结点只能出现在最下层和次下层。
    2)最下层的叶子结点集中在树的左部。
    3)倒数第二层若存在叶子结点,一定在右部连续位置。
    4)如果结点度为1,则该结点只有左孩子,即没有右子树。
    5)同样结点数目的二叉树,完全二叉树深度最小。
    满二叉树一定是完全二叉树,但反过来不一定成立。

    6.二叉树遍历

    遍历方式:

    1)前序遍历
    2)中序遍历
    3)后序遍历
    4)层序遍历

    图1.png
    前序遍历:进入当前节点,马上执行当前节点的主要逻辑
    进入A,执行A的逻辑,然后到B,执行B的逻辑,到D,执行D的逻辑,到H执行H的逻辑,因为H没有子节点,所以返回到D,然后进入D的右节点I,执行I的逻辑,而I也没有子节点,返回到D,因为D的逻辑和左右节点也执行完了,所以这时候回到B,执行B节点的右节点E,执行E的逻辑,然后进入J,执行J的逻辑,J没有节点返回到节点E,然后进入K节点,执行K的逻辑,至此A节点的左子树的逻辑已经跑完,后面到A节点的右子树,规律也一样。
    最终输出的结果是:
    A -> B -> D -> H -> I -> E -> J -> K -> C -> F -> L -> G
    
    // 前序遍历: 根->左->右
    fun preTraverse(root: TreeNode?) {
        if (root != null) {
            println("preTraverse:${root.value}")
            preTraverse(root.left,stringBuffer)
            preTraverse(root.right,stringBuffer)
        }
    }
    

    中序遍历:第一次进入当前节点不执行当前节点的主要逻辑,当第二次进入这个节点的时候才执行当前节点主要逻辑。
    进A节点,先不执行A节点的逻辑,直接进入B节点,先不执行B节点的逻辑,直接进入D节点,先不执行D节点的逻辑,直接进入H节点,由于H节点没有子节点,则执行H节点的逻辑,执行完H节点的逻辑后返回D节点,开始执行D节点的逻辑,执行完D节点的逻辑后进入I节点,由于I节点没有子节点,则直接执行I节点的逻辑,执行完I节点后返回D节点,此时的D节点已经执行完,所以直接返回到B节点,开始执行B节点的逻辑,执行完B节点的逻辑后进入E节点,先不执行E节点的逻辑,直接进入J节点,由于J节点没有子节点,直接执行J节点的逻辑,执行完J节点的逻辑后返回E节点,开始执行E节点的逻辑,执行完E节点的逻辑后进入K节点,由于K节点没有子节点,直接执行K节点的逻辑,执行完K节点逻辑后返回E节点,此时的E节点已完成所有逻辑,所以直接返回B节点,而B节点也已经完成所有逻辑,所以也直接返回到A节点,开始执行A节点的逻辑,至此A节点的左子树已经完成了,而后面到A节点的右子树,规律也一样。
    最终输出的结果是:

    H -> D -> I -> B -> J -> E -> K -> A -> L -> F -> C -> G
    
    // 中序遍历: 左->根->右
    fun inTraverse(root: TreeNode?) {
        if (root != null) {
            inTraverse(root.left,stringBuffer)
            println("preTraverse:${root.value}")
            inTraverse(root.right,stringBuffer)
        }
    }
    

    后续遍历:第一次进入当前节点的时候不执行当前节点的主要逻辑,当第二次进入这个节点的时候还是不执行主要逻辑,只有第三次进入当前节点的时候才执行当前的逻辑。
    进A节点,先不执行A节点的逻辑,直接进入B节点,先不执行B节点的逻辑,直接进入D节点,先不执行D节点的逻辑,直接进入H节点,由于H节点没有子节点,则执行H节点的逻辑,执行完H节点的逻辑后返回D节点,还是不执行D节点的逻辑,直接进入I节点,由于I节点没有子节点,则执行I节点的逻辑,执行完I节点后再次回到D节点,此时开始执行D节点的逻辑,执行完D节点逻辑后返回到B节点,先不执行B节点的逻辑直接进入E节点,先不执行E节点的逻辑,直接进入J节点,由于J节点没有子节点,则执行J节点的逻辑,执行完J节点的逻辑后返回E节点,还是不执行E节点的逻辑,直接进入K节点,由于K节点没有子节点,则执行K节点的逻辑,执行完K节点后再次回到E节点,此时开始执行E节点的逻辑,执行完E节点的逻辑后返回到B节点,此时开始执行B节点的逻辑,执行完B节点的逻辑后,返回到A节点,此时A节点的左子树已经执行完毕,但是与前序、后序不同的是此时还没有执行A节点的逻辑,而右子树的执行规律和左子树一样。
    最终输出的结果是:

    H -> I -> D -> J -> K -> E -> B -> L -> F -> G -> C -> A
    
    // 后序遍历: 左->右->根
    fun postTraverse(root: TreeNode?) {
        if (root != null) {
            postTraverse(root.left,stringBuffer)
            postTraverse(root.right,stringBuffer)
            println("preTraverse:${root.value}")
        }
    }
    

    层序遍历:顾名思义就是从上到下,从左往右,一层一层的执行。
    先执行第一层A节点的逻辑;然后到第二层B节点的逻辑,C节点的逻辑;再到第三层D节点的逻辑,E节点的逻辑,F节点的逻辑,G节点的逻辑;最后到第四层H节点的逻辑,I节点的逻辑,J节点的逻辑,K节点的逻辑,L节点的逻辑。
    最终输出的结果是:

    A -> B -> C -> D -> E -> F -> G -> H -> I -> J -> K -> L
    
    // 层次遍历(BFS)
    fun levelOrder(root: TreeNode?): MutableList<MutableList<Int>> {
        val result = mutableListOf<MutableList<Int>>()
        if (root == null) {
            return result
        }
        val queue = LinkedList<TreeNode>()
        queue.offer(root)
        while (!queue.isEmpty()) {
            val level = mutableListOf<Int>()
            val size = queue.size
            for (i in 0 until size) {
                val head = queue.poll()
                level.add(head.value)
                if (head.left != null) {
                    queue.offer(head.left)
                }
                if (head.right != null) {
                    queue.offer(head.right)
                }
            }
            result.add(level)
        }
        return result
    }
    

    7.线索二叉树

    线索二叉树:二叉树末端的节点由于没有子节点,那么实际是浪费了空间,为了利用好这些空间,利用某种遍历将末端节点的子节进行线索化,所谓的线索化就是将空的左子节点指向它的前驱节点,空的右子节点指向后继节点。所谓的前驱节点就是当前节点的前一个输出节点,而后继节点就是指当前节点的后一个输出节点。
    例如:图2的中序遍历的E节点,它的前驱节点就是B节点,而后继节点就是A节点。

    图2.png

    二叉树线索化带来的好处是,不管给到哪个节点都能快速找到该节点前驱和后继节点。

    /**---------------------中序遍历线索化------------------------------*/
    private var preTreeNode: TreeNode? = null
    fun inTraverseThreading(current: TreeNode?) {
        if (current != null) {
            inTraverseThreading(current.left)
            println("current:${current.value},preTreeNode:${preTreeNode?.value}")
            if (current.left == null) {
                current.leftTag = TreeNodeTag.Thread
                current.left = preTreeNode
            }
            if (preTreeNode?.right == null) {
                preTreeNode?.rightTag = TreeNodeTag.Thread
                preTreeNode?.right = current
            }
            preTreeNode = current
            inTraverseThreading(current.right)
        }
    }
    

    8.二叉排序树

    二叉排序树的特点

    1)若左子树不空,则左子树上所有结点的值均小于或等于它的根结点的值;
    2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
    3)左、右子树也分别为二叉排序树;

    二叉排序树的创建
    取一个数据和根节点对比,如果比根节点小放在左边,否则放在右边,如果根节点已经有子节点,则和子节点再做对比。
    例如现在有数据:55,10,15,88,60,50,100,80,52,46,35,20,45,87,76,58

    1)取第一个数据55作为根节点;
    2)取数据10和根节点55做对比,因为比根节点55小,所以放在根节点55的左边,此时的根节点55的左子节点为空,所以直接插在根节点55的左子节点位置;
    3)取第三个数据15和根节点55对比,因为比根节点55小,所以放在根节点55的左边,此时的根节点55的左子节点不为空,所以还需要和左子节点10作对比,因为比左子节点10大,所以放在左子节点10的右子节点上;
    4))以此内推,将数据插入,最终输出结果如图3。

    图3.png
    data class TreeNode(
        val value: Int, 
        var left: TreeNode? = null,
        var right: TreeNode? = null
    ) 
    /**
     * 查找
     * @param target 目标
     * @param current 当前的节点
     */
    fun searchBST(target: TreeNode, current: TreeNode): TreeNode {
        if (target.value == current.value) {
            return current
        }
        return if (target.value > current.value) {
            if (current.right != null) {
                searchBST(target, current.right!!)
            } else {
                current
            }
        } else {
            if (current.left != null) {
                searchBST(target, current.left!!)
            } else {
                current
            }
        }
    }
    

    参考链接

    相关文章

      网友评论

          本文标题:二叉树

          本文链接:https://www.haomeiwen.com/subject/afneuktx.html