树与二叉树(排序二叉树,平衡二叉树,B树)

本文主要介绍了Java数据结构中树和二叉树的相关知识。包括树的定义、相关概念,二叉树的定义、性质、遍历方式,还阐述了排序二叉树的查找、插入、删除操作,以及平衡二叉树、B树和红黑树的特点。强调了二叉排序树查找性能与树形状的关系。

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

1.树

1.1 树的定义

树(Tree)是n(n≥0)个结点的有限集。n=0 时称为空树。在任意一棵非空树中:

  1. 有且仅有一个特定的称为根(Root)的结点;
  2. 当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、… 、Tm其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)

如下图所示:

在这里插入图片描述

树的相关概念:

  1. 节点:树中的一个独立单元。如图中的A、B、C等。
  2. 节点的度:节点拥有子树的个数。如A有2个度,D有3个度。
  3. 树的度:树中最大的节点的度即为树的度。如图中树的度为D节点的度为3
  4. 叶子:度为0的节点称为叶子或终端节点。如图中的G,H,I,J,F。
  5. 非终端节点:度不为0的节点称为非终端节点或分支节点。除根节点外,非终端节点也称为内部节点
  6. 双亲和孩子:某节点的子结点称为该节点的孩子;该节点称为孩子的双亲。
  7. 兄弟:同一个双亲的孩子节点称为兄弟。如E和F是兄弟。
  8. 祖先:从根到该节点的双亲所经过的所有节点称为该节点的祖先。如J的祖先为A,C,E。
  9. 子孙:某节点的子树上任意一个节点都为该节点的子孙。如C的子孙E,F,J。
  10. 层次:从根节点开始定义,根节点为第一层,树中任一节点层次为其双亲层次加1。
  11. 树的深度:树的深度为树中节点的最大层次
  12. 有序树和无序树:如果树中节点的各子树看成从左至右是有次序的不能互换,则称该树为有序树,否则称为无序树。

对于树的定义还需要强调两点:

  1. n>0时根结点是唯一的,不可能存在多个根结点,别和现实中的大树混在一起,现实中的树有很多根须,那是真实的树,数据结构中的树是只能有一个根结点。
  2. m>0时,子树的个数没有限制,但它们一定是互不相交的。像下图中的两个结构就不符合树的定义,因为它们都有相交的子树。

2.二叉树

2.1 二叉树的定义与性质

二叉树的定义:

二叉树是有n(n>=0)个节点的有限集,有空树(n=0)和非空树。
对于非空树来说,它有且仅有一个称之为根的节点;除了根节点外的其余节点可分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身也是二叉树

二叉树与树的区别:

1.二叉树的每个节点至多只有两颗子树
2.二叉树是有序树,有左右子树之分

五中不同形态的二叉树:

在这里插入图片描述

二叉树的性质:

在这里插入图片描述

  1. 在二叉树的第i层上至多有2^(i-1)个节点
  2. 深度为k的二叉树至多有2^k-1个节点,最少有k个节点
  3. 对任意一颗二叉树T,如果其终端节点树为n0,度为2的节点数为n2,则n0=n2+1
    原因:一颗二叉树有n个节点,度为0的节点为n0个,度为1的节点为n1个,度为2的节点为n2个,有x个分支。则(式一)n=n0+n1+n2;(式二)n=x+1;(式三)x=n1+2n2,由一,二,三式可得(式四)n=n1+2n2+1,由一,四式可得n0=n2+1
  4. 满二叉树:深度为k且含有2^k-1个节点的二叉树
  5. 完全二叉树:深度为k且含有n个节点的二叉树,当且仅当其每一个节点都与深度为k的满二叉树中的节点编号一致时,称为完全二叉树

在这里插入图片描述

特点:对深度为k的完全二叉树来说

  1. 叶子节点仅出现在第k和第k-1层
  2. 对于任一节点,若其右分支的最大层次为L,则其左分支的最大层次必为L或L+1。

2.4 二叉树的遍历

2.4.1前序遍历

先序遍历(PreOrder) 的操作过程如下:
1)访问根结点;
2)先序遍历左子树;
3)先序遍历右子树。

先序遍历
先序遍历结果为:A B D H I E J C F K G

代码实现:

void preOrderTraversal(Node root) {
        if(root == null) {//若为空直接返回
            return;
        }
        System.out.print(root.val+" ");//先打印
        preOrderTraversal(root.left);//向左子树递归
        preOrderTraversal(root.right);//向右子树递归
    }

2.4.2中序遍历

中序遍历( InOrder)的操作过程如下:
1)中序遍历左子树;
2)访问根结点;
3)中序遍历右子树。

中序遍历

中遍历结果为:H D I B E J A F K C G

代码实现:

    void inOrderTraversal(Node root) {
        if(root == null) {
            return;
        }
        inOrderTraversal(root.left);//先向左子树递归
        System.out.print(root.val+" ");//打印
        inOrderTraversal(root.right);//向右子树递归
    }

2.4.3后序遍历

后序遍历(PostOrder) 的操作过程如下:
1)后序遍历左子树;
2)后序遍历右子树;
3)访问根结点。

后序遍历

后序遍历结果:H I D J E B K F G C A

代码实现:

    void postOrderTraversal(Node root) {
        if(root == null) {
            return;
        }
        postOrderTraversal(root.left);//向左子树递归
        postOrderTraversal(root.right);//向右子树递归
        System.out.print(root.val+" ");//打印
    }

2.4.4层序遍历

层次遍历很好理解,就是从根节点开始,一层一层,从上到下,每层从左到右,依次写值就可以了

层序遍历

遍历结构:A B C D E F G H I J K

代码实现:

进行层次遍历时构建一个辅助队列,先将二叉树根节点入队,然后出队,访问出队结点,若它有左子树,将左子树根节点入队,然后出队,访问出队结点…,右子树也同样如此,循环往复直到队列为空。

    public void levelOrderTraversal(Node root) {
        if(root == null) return;
        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);//将根节点入队
        while (!queue.isEmpty()) {
            Node top = queue.poll();//弹出一个元素,记录节点
            System.out.print(top.val+" ");//打印该元素
            //每弹出一个元素,就将该元素的左右子树入队
            if(top.left != null) {
                queue.offer(top.left);
            }
            if(top.right!=null) {
                queue.offer(top.right);
            }
        }
    }

参考内容

排序二叉树

排序二叉树(Binary SearchTree) :在此树中,每个节点的数值比左子树上的每个节点都大,比所有右子树上的节点都小。

如下图所示:

在这里插入图片描述

构造二叉排序树

public class BST {
    static class TreeNode {
        public int value;
        public TreeNode left;//左子树
        public TreeNode right;//右子树
        public TreeNode(int value) {
            this.value = value;
        }
    }
    private TreeNode root;
}

二叉排序树的查找

在这里插入图片描述

代码实现:

public TreeNode get(int key) {
	TreeNode current = root;//从根路径开始
	while(current != null && current.value != key){//终止条件
		//进行迭代
		if(key < current.value){//key偏小,从左子树中找
		current = current.left;
		}else if(key > current.value){//key偏大,从右子树中找
		current =current.right;
		}
	}
	return current== null ? null : current;
}

二叉排序树的插入

在这里插入图片描述

public void insert(int key) {
	if(root == null){//如果根节点为null则把根节点变为该值
		root = new TreeNode(key);
		return;
    }
	TreeNode current = root;//你要插入的位置
	TreeNode parent = null;//你要插入位置的父亲
	while(true){//遍历循环,迭代寻找要插入的位置
		parent = current;
		if(key < parent.value){//插入的值小于父节点
			current = parent.left;
			if(current == null) {
				parent.left = new TreeNode(key);
				return; 
			}
		}else if(key > parent.value){//插入的值大于父节点
			current = parent.right;
			if(current == null) {
				parent.right = new TreeNode(key); 
				return;
			}
		} else {//插入的值等于父节点,已经将key插入,直接返回即可
			return; //二叉搜索树没有相同的节点
		}
	}
}

二叉排序树的删除

第一种情况:要删除的节点就是叶子结点,直接删除即可

Case1

第二种情况:只有左孩子或者右孩子,让孩子代替其位置即可

Case2

第三种情况:同时有左孩子和右孩子

Case3

public boolean delete(int key) {
    TreeNode parent = root;
    TreeNode current = root;
    boolean isLeftChild = false;//是否是左孩子
    while(current != null && current.value != key){//寻找要删除的节点
        parent = current;
        if(current.value > key){
            isLeftChild = true;
            current = current.left;//向左边寻找
        } else {//向右边寻找
            isLeftChild = false;
            current = current.right;
        }
    }
    if(current == null) {//没找到要删除的节点直接返回
        return false;
    }
    // Case 1: 第一种情况:要删除的节点就是叶子结点,直接删除即可
    if(current.left == null && current.right == null){
        if(current == root) {//若为根节点直接将其置为null
            root = null;
        } else if(isLeftChild){//若要删除的节点为左孩子,设为null
            parent.left = null;
        } else {//同理
            parent.right = null;
        }
    // Case 2:第二种情况:只有左孩子或者只有右孩子,让孩子代替其位置即可
    } else if (current.right == null){//若右孩子为null
        if(current == root) {//当前节点为根节点
            root = current.left;//直接让左孩子替代其位置
        } else if (isLeftChild){//向左子树迭代
            parent.left = current.left;
        } else {//向右子树迭代
            parent.right = current.left;
        }
    } else if (current.left == null){//若左孩子为null,同上
        if(current == root) {
            root = current.right;
        } else if (isLeftChild) {
            parent.left = current.right;
        } else {
            parent.right = current.right;
        }
    // Case 3: current.left != null && current.right != null
    //同时有左孩子和右孩子
    } else {
    //找到要删除节点的右子树上最小的那一个
    //将要删除节点的右子树最小的那个节点对要删除的节点进行覆盖
        TreeNode successor = getSuccessor(current);
        if (current == root) {
            root = successor;
        } else if (isLeftChild) {
            parent.left = successor;
        } else {
            parent.right = successor;
        }
        successor.left = current.left;
    }
    return true;
}
private TreeNode getSuccessor(TreeNode node) {
    TreeNode successor = null;//最后要覆盖删除位置的节点
    TreeNode successorParent = null;//用于记录父亲节点
    TreeNode current = node.right;
    while (current != null){
    //找到要删除节点的右子树上最小的那一个(右孩子的最左边的那一个)
        successorParent = successor;
        successor = current;
        current = current.left;
    }
    if (successor != node.right){//说明要删除节点是有左孩子的
        successorParent.left = successor.right;//节点交换
        successor.right = node.right;
    }
    return successor;
}

二叉排序树总结

二叉排序树以链接的方式存储,保持了链接存储结构在执行插入或删除操作时不用移动元素的优点,只要找到合适的插入和删除位置后,仅需修改链接指针即可。插入删除的时间性能比较好。而对于二叉排序树的查找,走的就是从根结点到要查找的结点的路径,其比较次数等于给定值的结点在二叉排序树中的层数。极端情况,最少为1次,即根结点就是要找的结点,最多也不会超过树的深度。也就是说,二叉排序树的查找性能取决于二叉排序树的形状。可问题就在于,二叉排序树的形状是不确定的。

例如{62,88,58,47,35,73,51,99,37,93}这样的数组,我们可以构建如左下图所示的二叉排序树。但如果数组元素的次序是从小到大有序,如{35,37,47,51,58,62,73,88,93,99},则二叉排序树就成了极端的右斜树,注意它依然是一棵二叉排序树,如右下图。此时,同样是查找结点99,左下图只需要两次比较,而右下图就需要10次比较才可以得到结果,二者差异很大。

在这里插入图片描述

也就是说,我们希望二叉排序树是比较平衡的,即其深度与完全二叉树相同,均为[log2n]+1,那么查找的时间复杂也就为O(logn),近似于折半查找,事实上,左上图也不够平衡,明显的左重右轻。

不平衡的最坏情况就是像右上图的斜树,查找时间复杂度为O(n),这等同于顺序查找。
因此,如果我们希望对一个集合按二叉排序树查找,最好是把它构建成一棵平衡的二叉排序树。这样我们就引申出另一个问题,如何让二叉排序树平衡的问题。
二叉排序树的查找性能取决于二叉排序树的形状,在O(log2n)和O(n)之间

平衡二叉树

平衡二叉树(AVL Tree)任何节点的两颗子树的高度差不大于1的排序二叉树。

平衡二叉树:或者是一棵空的二叉排序树,或者是具有下列性质的二叉排序树:
(1)根结点的左子树和右子树的深度最多相差1;
(2)根结点的左子树和右子树也都是平衡二叉树

平衡因子:结点的平衡因子是该结点的左子树的深度与右子树的深度之差

在这里插入图片描述
在平衡树中,结点的平衡因子可以是1,0, -1。

最小不平衡子树:在平衡二叉树的构造过程中,以距离插入结点最近的、且平衡因子的绝对值大于1的结点为的子树。

B树

B树(B-Tree):B树和平衡二插树一样,只不过它是一种多叉树(一个节点的子节点数量可以超过二)

在这里插入图片描述

红黑树

红黑树(Red- -Black Tree)是一种自平衡二叉寻找树。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值