二叉树总结

本文详细介绍了树的定义,特别是二叉树的概念,包括满二叉树和完全二叉树。同时,讲解了二叉搜索树的性质,并深入探讨了堆的数据结构,包括向下和向上重排操作。接着,文章详述了二叉树的前序、中序、后序遍历方法,包括递归和迭代实现。最后,提到了二叉树遍历在构建排序双向链表问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

树的定义

树状图是一种数据结构,它是由 n ( n ≥ 1 ) n(n\ge1) n(n1) 个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

  1. 每个结点有零个或多个子结点;
  2. 没有父结点的结点称为根结点;
  3. 每一个非根结点有且只有一个父结点;
  4. 除了根结点外,每个子结点可以分为多个不相交的子树。

引自百度百科

二叉树

在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树” ( l e f t    s u b t r e e ) (left \;subtree) (leftsubtree)和“右子树” ( r i g h t    s u b t r e e ) (right \;subtree) (rightsubtree)。二叉树常被用于实现二叉查找树和二叉堆。
一棵深度为k,且有 2 k − 1 2^k-1 2k1个结点的二叉树,称为满二叉树。这种树的特点是每一层上的结点数都是最大结点数。而在一棵二叉树中,除最后一层外,若其余层都是满的,并且或者最后一层是满的,或者是在右边缺少连续若干结点,则此二叉树为完全二叉树。具有 n n n 个结点的完全二叉树的深度为 f l o o r ( l o g 2 n ) + 1 floor(log_2 n)+1 floor(log2n)+1。深度为 k k k 的完全二叉树,至少有 2 k − 1 {2^{k-1}} 2k1 个叶子结点,至多有 2 k − 1 {2^k-1} 2k1 个结点。
引自百度百科

简单的二叉树定义

 public class TreeNode {
 	int val;
 	TreeNode left;
	TreeNode right;
 	TreeNode(int x) { 
 		val = x; 
 	}
}

二叉搜索树

二叉查找树 ( B i n a r y    S e a r c h    T r e e ) (Binary \;Search\; Tree) (BinarySearchTree) ( ( (又:二叉搜索树,二叉排序树 ) ) )它或者是一棵空树,或者是具有下列性质的二叉树:

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

引自百度百科

( h e a p ) (heap) (heap)通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆是非线性数据结构,相当于一维数组,有两个直接后继。
堆的定义如下: n n n 个元素的序列 k 1 , k 2 , k i , … , k n {k_1,k_2,k_i,…,k_n} k1,k2,ki,,kn当且仅当满足下关系时,称之为堆。
( k i ≤ k 2 i , k i ≤ k 2 i + 1 ) (k_i \le k_{2i},k_i \le k_{2i+1}) (kik2i,kik2i+1)或者 ( k i ≥ k 2 i , k i ≥ k 2 i + 1 ) (k_i \ge k_{2i},k_i \ge k_{2i+1}) (kik2i,kik2i+1), ( i = 1 ,   2 ,   3 ,   4...   n 2 ) (i = 1, \,2,\, 3, \,4...\,\frac{n}{2}) (i=1,2,3,4...2n)
若将和此次序列对应的一维数组 ( ( (即以一维数组作此序列的存储结构 ) ) )看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于 ( ( (或不小于 ) ) )其左、右孩子结点的值。由此,若序列 [ k 1 ,   k 2 ,   … ,   k n ] [k_1,\,k_2,\,…,\,k_n] [k1,k2,,kn]是堆,则堆顶元素 ( ( (或完全二叉树的根 ) ) )必为序列中n个元素的最小值 ( ( (或最大值 ) ) )

向下重排

从堆中删除一个数

向上重排

在堆中加入一个数

二叉树的遍历

前序遍历

前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。
若二叉树为空则结束返回,否则:

  1. 访问根结点。
  2. 前序遍历左子树。
  3. 前序遍历右子树 。

中序遍历、后序遍历

中序遍历首先遍历左子树,再访问根节点,最后遍历右子树。在遍历左、右子树时,仍然先遍历左子树,再访问根节点,最后遍历右子树。
后序遍历首先遍历左子树,再遍历右子树,最后访问根节点。在遍历左、右子树时,仍然先遍历左子树,再遍历右子树,最后访问根节点。

遍历方法

递归

递归的方法很简单,下面是前序遍历的递归算法:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans;

        ans = new ArrayList<>();
        if(root == null)
            return ans;
        ans.add(root.val);
        ans.addAll(preorderTraversal(root.left));
        ans.addAll(preorderTraversal(root.right));
        return ans;
    }

递归算法很简单,中序和后序遍历只需要改变 a n s . a d d ( r o o t . v a l ) ; ans.add(root.val); ans.add(root.val); 的位置即可。但是由于递归的时候比较耗资源,下面介绍下迭代算法:

迭代

前序遍历
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans;
        Stack<TreeNode> stack;
        TreeNode cursor;

        ans = new ArrayList<>();
        stack = new Stack<>();
        if(root == null)
            return ans;
        stack.push(root);
        while(!stack.isEmpty()){
            cursor = stack.pop();
            ans.add(cursor.val);
            if(cursor.right != null){
                stack.push(cursor.right);
            }
            if(cursor.left != null){
                stack.push(cursor.left);
            }
        }
        return ans;
    }
}

用一个辅助栈来实现前序遍历。需要主要的是右子树和左子树的插入顺序。

中序遍历
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        Stack<TreeNode> stack;
        List<Integer> ans;
        TreeNode cursor;

        stack = new Stack<>();
        ans = new ArrayList<>();
        cursor = root;
        while(cursor != null || !stack.isEmpty()){
            while(cursor != null){
                stack.push(cursor);
                cursor = cursor.left;
            }
            cursor = stack.pop();
            ans.add(cursor.val);
            cursor = cursor.right;
        }
        return ans;
    }
}

同样是利用辅助栈实现。

后序遍历
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans;
        Stack<TreeNode> stack;
        TreeNode cursor;

        ans = new LinkedList<>();
        stack = new Stack<>();
        if(root == null)
            return ans;
        stack.push(root);
        while(!stack.isEmpty()){
            cursor = stack.pop();
            ans.add(0, cursor.val);
            if(cursor.left != null)
                stack.push(cursor.left);
            if(cursor.right != null)
                stack.push(cursor.right);
        }
        return ans;
    }
}

后序遍历和前序遍历有点像,如果把前序遍历倒过来就是后序遍历吗?前序遍历是:根节点 → \rightarrow 左子树 → \rightarrow 右子树;而后序遍历是:左子树 → \rightarrow 右子树 → \rightarrow 根节点。有点差别,但是如果把前序遍历做个改变(就是上面前序遍历说到的右子树和左子树的插入顺序),那么就变成了:根节点 → \rightarrow 右子树 → \rightarrow 左子树,再反向可得后序遍历。 L i n k e d L i s t LinkedList LinkedList 保证了插入新元素的时间复杂度是 O ( 1 ) O(1) O(1)

二叉树遍历的应用

二叉搜索数与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
这是牛客上的一道题目。
根据二叉搜索树和中序遍历的定义可以知道,一颗二叉搜索树的中序遍历就是一个递增的数列,因此用中序遍历的思路解决这道题。

递归解法:

public class Solution {
    public TreeNode Convert(TreeNode root) {
        return ConvertHelper(root)[0];
    }
    
    private TreeNode[] ConvertHelper(TreeNode root){
        TreeNode[] ans, left, right;
        
        ans = new TreeNode[2];
        if(root == null){
            return ans;
        }
        left = ConvertHelper(root.left);
        right = ConvertHelper(root.right);
        if(left[0] != null){
            root.left = left[1];
            left[1].right = root;
            ans[0] = left[0];
        }else{
            ans[0] = root;
        }
        if(right[0] != null){
            root.right = right[0];
            right[0].left = root;
            ans[1] = right[1];
        }else{
            ans[1] = root;
        }
        return ans;
    }
}

a n s [ 0 ] 、 a n s [ 1 ] ans[0]、ans[1] ans[0]ans[1]分别保存双向链表的头和尾。

迭代解法:

public class Solution {
    public TreeNode Convert(TreeNode root) {
        TreeNode cursor, head, tail;
        Stack<TreeNode> stack;
        
        if(root == null)
            return null;
        stack = new Stack<>();
        head = new TreeNode(0);
        tail = head;
        cursor = root;
        while(cursor != null || !stack.isEmpty()){
            while(cursor != null){
                stack.push(cursor);
                cursor = cursor.left;
            }
            cursor = stack.pop();
            tail.right = cursor;
            cursor.left = tail;
            tail = tail.right;
            cursor = cursor.right;
        }
        head = head.right;
        head.left = null;
        return head;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值