数据结构-二叉树

简介: 数据结构-二叉树

什么是树?

1、基本概念

二叉树是我们生活中最常见的非线性结构。最常见的应用就是我们经常使用的文件分类。如图:


在c盘中存在着很多文件和文件夹,文件夹又可以细分为其他的文件夹和文件。对于图中的c盘,就相当于树的跟,而其他的文件夹就相当于树枝,然后树枝上有分出许多其他的树枝,以此组成一个树形结构。相比于顺序表,链表这样的结构,树形结构能够更好的分类,一般拥有更高的访问效率。


存在树,那么也同样存在非树。如果在存在一个节点指向同一层的结点,就称为非树。如图:


2、结构特征

类似于如链表的头结点,树形结构的根节点是读取树,操作树的最基本入口。

树中的A结点和链表的head结点都起着不可替代的作用。

3、基本特性

以下图为例子:


  1. 结点的度:每一个节点含有子树的个数,例如图中的A结点,存在BCDE这四棵子树,其中B存在FGH这三棵子树。所以A节点的度为4,B结点的度为3。
  2. 树的度:一棵树的所有的结点的度的集合中,最大值为这整棵树的度(树的结点中的最大的度)
  3. 叶子结点(终端结点):度为0的结点。例如图中的M结点、结点等。
  4. 父结点(双亲结点):一个结点的上一级结点,成为这个结点的父结点,例如上图中,A是B的父结点。
  5. 孩子结点(子结点):和父亲结点类似,一个结点如果含有其下一级,那么这个结点的下一级就称为这个结点的孩子结点。
  6. 根节点:也就是整棵树的起点,如上图中的A结点。
  7. 结点的层次:如果根节点的层次为1,那么根节点的子节点BCDE的层次就为2,以此类推。
  8. 树的深度(高度):树的结点的最大层次。
  9. 非终端结点(分支结点):度不为0的结点。
  10. 祖先:从根到该结点所经分支上的所有结点。例如,A结点是所有结点的祖先。
  11. 子孙:以某结点为根的子树中任一结点都称为该结点的子孙。
  12. 森林:由多棵不相交的树组成的系统,称为森林。


4、代码结构

class TreeNode {
    int val;
    TreeNode firstChild;
    TreeNode secondChild;
// .......TreeNode fifthchild;
// .......
}

在树的三链表示中还存在一个指向父亲结点的指针变量。


特殊的树-二叉树

从上面的介绍不难看出, 二叉树是结点的度的最大值为2的树。代码结构为:

1. class TreeNode {
2.     int val;
3.     TreeNode leftChild;
4.     TreeNode rightChild;
5. }

如果leftChild和rightChild有一个不为空,则这个结点为分支节点。如果leftChild和rightChild都为空,则这个结点为叶结点。val用来表示存放的数据。如图:

82f66017e2f4d79e2298f16ec6db7d4c_d3432d071ae24e5e86d436ae0595e77b.png

  • 满二叉树

定义:一棵二叉树,它的每一层的节点数都达到最大值,则称这个二叉树为满二叉树,如果一棵满二叉树层数为k(根节点的层数为1),那么它一共拥有 个结点,例如:

满二叉树


图中的层数3,节点数为


  • 完全二叉树

定义:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。

例如:

完全二叉树


3、二叉树的性质


  • 除了根节点外,每一个节点都有都有一条边和其父结点相连,若一个有n个结点,那么就一共有n-1条边
  • 若规定根节点的层数为1,则一棵二叉树的第n层最多含有 (n≥1)个结点。
  • 假设一棵树有n个结点,设叶结点(度为1的结点)个数为 n0, 度为2的非叶结点个数为 n2,由第一个性质可知,总的节点数为 == 边的个数+ 1。度为2的结点可以衍生出两条边,度为1 的结点可以衍生出1条边,所以:


n0 + n1 + n2 = 2*n2 + 1*n1 + 0*n0 + 1

n0 = n2 + 1

即: 任何一棵二叉树, 如果其叶结点个数为 n0, 度为2的非叶结点个数为 n2,则有n0=n2+1

  • 具有n个结点的完全二叉树的深度k为 上取整


二叉树的基本操作

1、二叉树结构实现

public class BinaryTree {0
    // 二叉树结点的定义
    public static class BTNode {
        int val;
        BTNode leftChild;
        BTNode rightChild;
        public BTNode(int val) {
            this.val = val;
        }
    }
    // 简单创建一棵树,方便后面遍历
    public void test() {
        BTNode A = new BTNode(1);
        BTNode B = new BTNode(2);
        BTNode C = new BTNode(4);
        BTNode D = new BTNode(3);
        BTNode E = new BTNode(5);
        BTNode F = new BTNode(6);
        A.leftChild = B;
        A.rightChild = C;
        B.leftChild = D;
        C.leftChild = E;
        C.rightChild = F;
    }
}


实现并创建一棵简单的二叉树

2、前中后序遍历

所谓二叉树遍历,就是顺着某一种固定的路线,来依次对二叉树的每一个节点的数据进行访问,以此打印或者获取二叉树的存储内容。


如何访问? 对于每一棵树,都有一个根节点作为入口点,然后每一个结点,都存放有对应的左孩子和有孩子的引用变量,以此来访问他们,然后左孩子和有孩子中又存放着其对应的左孩子和右孩子,依次形成递归。结束访问,只需要访问到其孩子的引用为空(null)即可


根据根节点 的访问先后次序,可以分为以下遍历方式:


NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点--->根的左子树--->根的右子树。

LNR:中序遍历(Inorder Traversal)——根的左子树--->根节点--->根的右子树。

LRN:后序遍历(Postorder Traversal)——根的左子树--->根的右子树--->根节点。


前序遍历:1 2 3 4 5 6

中序遍历:3 2 1 5 4 6

后序遍历:3 2 5 6 4 1


3、层序遍历

二叉树的层序遍历

层序遍历:按照每一层的层次关系进行遍历,而不是通过递归的父子关系。如上图,

层次遍历的结果为:1 2 4 3 5 6

遍历思路:创建一个队列,首先遍历根节点,将根节点放入队列中,然后出列并输出打印出列结点,随后将出列结点的左孩子(leftChild)和右孩子(rightChild)放入队列中,随后的每一次遍历,都将当前打印的结点的左孩子和右孩子放入队列中,直到队列为空。



因为356结点的左孩子右孩子都为null,也就是没有左孩子和右孩子,那么就直接出队列然后输出打印

public void levelOrder(BTNode root) {
        Queue<BTNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()) {
            BTNode out = queue.poll();
            if (out.leftChild != null) {
                queue.offer(out.leftChild);
            }
            if (out.rightChild != null) {
                queue.offer(out.rightChild);
            }
            System.out.print(out.val + " ");
        }
    }

注意此处,传入的为二叉树的根节点。

输出结果为:

4、获取树中结点个数size()

    public int size(BTNode root) {
        if (root == null) {
            return 0;
        }
        return size(root.leftChild) + size(root.rightChild) + 1;
    }

return 中的1代表当前的结点,然后去访问左子树,和右子树的结点个数。如果某个结点为null,则直接返回0.


5、获取叶子节点的个数

叶子结点及度为0的结点,也就是leftChild和rightChild引用为null的结点。


计数法,可以使用两个函数来实现:

    private int count = 0;
    public int leafCount1(BTNode root) {
        count = 0;
        functionLeafCount(root);
        return count;
    }
    public void functionLeafCount(BTNode root) {
        if (root == null) {
            return;
        }
        if (root.leftChild == null && root.rightChild == null) {
            count++;
            return;
        }
        functionLeafCount(root.leftChild);
        functionLeafCount(root.rightChild);
    }

函数递归:

    public int leafCount2(BTNode root) {
        if (root == null) {
            return 0;
        }
        if (root.leftChild == null && root.rightChild == null) {
            return 1;
        }
        return leafCount1(root.leftChild) + leafCount2(root.rightChild);
    }

6、获取第k层的结点个数

设根节点的层数为1

    public int getLevelCount(BTNode root,int k) {
        if (root == null) {
            return 0;
        }
        if (k == 1) {
            return 1;
        }
        return getLevelCount(root.leftChild, k - 1) + getLevelCount(root.rightChild, k - 1);
    }

传入参数k,然后递归每一层将 k - 1,当k == 1 的时候,就是我们所需要的层数


7、获取二叉树高度

二叉树的高度可以简单化为 :

左子树的高度 + 右子树的高度 + 1
    public int getheight(BTNode root) {
        if (root == null) {
            return 0;
        }
        int leftHeight = getheight(root.leftChild);
        int rightHeight = getheight(root.rightChild);
        return Math.max(leftHeight, rightHeight) + 1;
    }

8、检测值为value的元素是否存在

    public boolean exitValueOf(BTNode root, int val) {
        if (root == null) {
            return false;
        }
        if (root.val == val) {
            return true;
        }
        return exitValueOf(root.leftChild, val) || exitValueOf(root.rightChild, val);
    }


递归思路,首先判断入口点的值是否等于val,然后再去判断左子树,和右子树是否存在val。


如果root为空,则说明不含val的值的结点,返回false,如果找到了就返回true。


9、判断一棵树是不是完全二叉树

完全二叉树定义:一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。

    public boolean isCompleteTree(BTNode root) {
        if (root == null ) {
            return true;
        }
        Queue<BTNode> queue = new LinkedList<>();
        boolean startLeaf = false;
        queue.offer(root);
        while(!queue.isEmpty()) {
            BTNode cur = queue.poll();
            BTNode left = cur.leftChild;
            BTNode right = cur.rightChild;
 
            if (left == null && right != null) {
                return false;
            }
            if (left == null || right == null)  {
                startLeaf = true;
            }
            if (startLeaf && (left != null || right != null)) {
                return false;
            }
            if (left != null) {
                queue.offer(left);
            }
            if (right != null) {
                queue.offer(right);
            }
        }
        return true;
    }


目录
相关文章
|
13天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
62 8
|
1月前
|
存储 算法 关系型数据库
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
这篇文章主要介绍了多路查找树的基本概念,包括二叉树的局限性、多叉树的优化、B树及其变体(如2-3树、B+树、B*树)的特点和应用,旨在帮助读者理解这些数据结构在文件系统和数据库系统中的重要性和效率。
18 0
数据结构与算法学习二一:多路查找树、二叉树与B树、2-3树、B+树、B*树。(本章为了解基本知识即可,不做代码学习)
|
1月前
|
存储 算法 搜索推荐
数据结构与算法学习十七:顺序储存二叉树、线索化二叉树
这篇文章主要介绍了顺序存储二叉树和线索化二叉树的概念、特点、实现方式以及应用场景。
20 0
数据结构与算法学习十七:顺序储存二叉树、线索化二叉树
|
1月前
|
Java
【用Java学习数据结构系列】震惊,二叉树原来是要这么学习的(二)
【用Java学习数据结构系列】震惊,二叉树原来是要这么学习的(二)
27 1
|
1月前
|
算法 Java C语言
【用Java学习数据结构系列】震惊,二叉树原来是要这么学习的(一)
【用Java学习数据结构系列】震惊,二叉树原来是要这么学习的(一)
24 1
|
1月前
|
存储
【数据结构】二叉树链式结构——感受递归的暴力美学
【数据结构】二叉树链式结构——感受递归的暴力美学
|
1月前
|
存储 编译器 C++
【初阶数据结构】掌握二叉树遍历技巧与信息求解:深入解析四种遍历方法及树的结构与统计分析
【初阶数据结构】掌握二叉树遍历技巧与信息求解:深入解析四种遍历方法及树的结构与统计分析
|
1月前
|
存储 算法 调度
数据结构--二叉树的顺序实现(堆实现)
数据结构--二叉树的顺序实现(堆实现)
|
1月前
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解(三)
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解
|
1月前
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解(二)
【高阶数据结构】二叉树进阶探秘:AVL树的平衡机制与实现详解

热门文章

最新文章