树结构

本文介绍了树的基本特性,包括节点、子树、度、层次和深度等概念,并重点讲解了二叉树的定义,特殊类型的二叉树,以及二叉树的遍历方法如前序、中序、后序和层序遍历。此外,讨论了树的存储结构,如数组和链表,并提及了非递归遍历的实现思路。

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

关于树的一些特性:

  • 每个树都是由根节点及一些“子树”构成。非空树至少有一个节点,根节点。否则,没有任何节点,为空树。

  • 子树之间是互不相交的

  • 除了根节点外,每个节点有且仅有一个父节点

  • 一颗N个节点的树有N-1条边

    关于树的一些概念:

  • 节点的度:节点的子树个数

  • 树的度:树中所有节点的度的最大值

  • 叶子节点的度为0

  • 节点的层次:规定根节点所在的层次为1,其他任意节点是其父节点的层次加1

  • 树的深度:树中所有节点的最大层次就是这棵树的深度

树的表示

儿子-兄弟表示法
这里写图片描述
将表示的节点树右转45°,可以看到如下:
这里写图片描述
由此我们可以知道,一般的树都可以由左右两个指针的节点所表示,也就是我们常说的二叉树

二叉树:若为非空树,则由根节点及左子树、右子树两个不相交的二叉树组成
子树有左右之分

一些特殊的二叉树:
这里写图片描述
一个二叉树第i层的最大节点数是:2^(i-1),i>=1
深度为K的二叉树的最大节点数是2^K-1
2^0+2^1+2^3+….+2^(K-1) = 2^K -1

对二叉树的操作:
1.创建一个二叉树
2.遍历:按某个顺序访问每个节点
——遍历方法:
先序遍历:根,左子树,右子树
中序遍历:左子树,根,右子树
后序遍历:左子树,右子树,根
层序遍历:从上到下,从左到右


树的存储结构

  • 数组

完全二叉树可以用数组存储,如果是一般二叉树,用数组存储就会造成空间的浪费。
比如上图中的完美二叉树
如果用数组存储的话,如下表:

节点ABCDEFGHIJKLMNO
序号123456789101112131415

其中非根节点i的父节点为:i/2取整
节点i的左孩子为2I(如果2i>n,则该节点没有左孩子)
节点i的右孩子为2i+1(如果2i+1>n,则该节点没有右孩子)
这里写图片描述

  • 链表
//节点结构
class TreeNode{
    private Object data;
    TreeNode leftChild;
    TreeNode rightChild;
}

这里写图片描述

前面提到二叉树的遍历有四种遍历方式,下面我们来说一下其中的三种遍历方式,前序中序及后序遍历
由于树就是一种递归的结构,所以我们可以利用递归实现二叉树的遍历

  • 前序遍历
public void PreTraverse(TreeNode root){
    if(root != null){
        System.out.println(root.data);
        PreTraverse(root.leftChild);
        PreTraverse(root.rightChild);
    }
}
  • 中序遍历
public void MidTraverse(TreeNode root){
    if(root != null){
        MidTraverse(root.leftChild);
        System.out.println(root.data);
        MidTraverse(root.rightChild);
    }
}
  • 后序遍历
public void PostOrderTraverse(TreeNode root){
    if(root != null){
        PostOrderTraverse(root.leftChild);
        PostOrderTraverse(root.rightChild);
        System.out.println(root.data);
    }
}

利用递归我们可以很简单的实现二叉树的遍历,利用非递归的方式如何实现二叉树的遍历呢?
思路:使用堆栈

  • 中序遍历的非递归实现:

思路:
1.遇到一个节点,就将其压栈,并去遍历它的左子树
2.当左子树遍历完毕后,就从栈顶弹出一个节点并去访问它
3.然后再去按其右指针再去中序遍历该节点的右子树

public void InorderTraverse(TreeNode root){
    TreeNode r = root;
    Stack s = new Stack();
    while(r != null || !s.isEmpty()){    //树不空或者栈不空
        while(r != null){   //一直向左并将遍历的节点入栈
            s.push(r);
            r = r.leftChild;
        }
        if(!s.isEmpty){   //左孩子遍历完,弹出栈顶节点,并访问,然后再遍历该节点的右子树
            TreeNode node = s.pop();
            System.out.println(node.data);
            r = node.rightChild;
        }
    }
}
  • 先序遍历的非递归算法:

    思路:
    无论是哪种遍历方法,它们最终所走的路线都是一样的,之所以输出的节点的顺序不同,是因为不同的遍历决定了是在第一次碰到该节点的时候访问还是第二次碰到该节点的时候进行访问。中序遍历是在第二次走到该节点的时候去访问,而先序遍历呢?自然就是第一次遇到该节点的时候就访问,所以上述的中序遍历的非递归算法我们修改一条语句的位置就是先序遍历的非递归算法了

public void InorderTraverse(TreeNode root){
    TreeNode r = root;
    Stack s = new Stack();
    while(r != null || !s.isEmpty()){    //树不空或者栈不空
        while(r != null){   //一直向左并将遍历的节点入栈
            System.out.println(node.data);  //!!!!!!!压栈之前先访问
            s.push(r);  //第一次遇到节点,入栈
            r = r.leftChild;
        }
        if(!s.isEmpty){   //左孩子遍历完,弹出栈顶节点,并访问,然后再遍历该节点的右子树
            TreeNode node = s.pop();  //出栈,第二次遇到节点
            //System.out.println(node.data);
            r = node.rightChild;
        }
    }
}

未完待续……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值