当前位置: 首页 > news >正文

数据结构——树

1 定义:


树是由 n(n≥0)个节点组成的有限集合。当 n=0 时,称为空树;在任意一棵非空树中,有且仅有一个特定的称为根(Root)的节点,当 n>1 时,其余节点可分为 m(m>0)个互不相交的有限集 T1、T2、……、Tm,其中每个集合本身又是一棵树,并且称为根的子树。

2 基本术语

  • 节点的度:一个节点拥有的子树个数。
  • 树的度:树中节点的最大度数。
  • 叶子节点:度为 0 的节点,也称为终端节点。
  • 非叶子节点:度不为 0 的节点,也称为分支节点。
  • 节点的层次:从根节点开始,根为第一层,根的子节点为第二层,以此类推。
  • 树的高度或深度:树中节点的最大层次。

节点的定义

public class Node {
		int value;
		Node left;
		Node right;
		public Node(int val) {
			value=val;
			
			
		}
		@Override
		public String toString() {
			return "Node [value=" + value + ", left=" + left + ", right=" + right + "]";
		}
		
		

3 常见的树结构

  • 二叉树:每个节点最多有两个子树的树结构,分别称为左子树和右子树。二叉树具有一些特殊的性质,例如在满二叉树中,第 i 层的节点数为 2^(i - 1);深度为 k 的满二叉树,节点总数为 2^k - 1。
  • 完全二叉树:除了最后一层外,其他各层节点数都达到最大值,且最后一层的节点都集中在左侧。
  • 哈夫曼树:一种带权路径长度最短的二叉树,常用于数据压缩等领域。通过贪心算法构建,将权值最小的节点不断合并,最终形成哈夫曼树。
  • B 树和 B + 树:常用于数据库索引等场景。B 树是一种平衡的多路查找树,节点可以存储多个关键字,并且能够在插入、删除操作时保持树的平衡。B + 树是 B 树的一种变体,所有数据都存储在叶子节点,非叶子节点只存储关键字和指向子节点的指针,更适合范围查询。

6 树的性质

1 树中的结点数等于所有结点的度数加1.
2 度为m mm的树中第i ii层上至多有m^(i-1)个节点
高度为h 的m叉树至多有( m^h − 1 ) / ( m − 1 )个结点。
具有n个结点的m 叉树的最小高度为[ l o g m ( n ( m − 1 ) + 1 ) ]

5 树的遍历

  • 前序遍历:先访问根节点,然后递归地遍历左子树,最后递归地遍历右子树。
//	先序
	public  void beforeOrder(Node node) {
		if(node==null) {
			return;
		}
		System.out.print(node.value+" ");
		beforeOrder(node.left);
		beforeOrder(node.right);
	
	}
  • 中序遍历:先递归地遍历左子树,然后访问根节点,最后递归地遍历右子树。对于二叉搜索树,中序遍历可以得到一个有序的序列。
//	中序
	public  void inOrder(Node node) {
		if(node==null) {
			return;
		}
	
		inOrder(node.left);
		System.out.print(node.value+" ");
		inOrder(node.right);
		
	}
  • 后序遍历:先递归地遍历左子树,然后递归地遍历右子树,最后访问根节点。
//	后序
	public  void afterOrder(Node node) {
		if(node==null) {
			return;
		}
	
		afterOrder(node.left);
		
		afterOrder(node.right);
		System.out.print(node.value+" ");
		
	}

6  二叉搜索树插入

二叉搜索树具有如下性质:对于树中的每个节点,其左子树中的所有节点值都小于该节点的值,而右子树中的所有节点值都大于该节点的值。插入操作的步骤如下:

  1. 若树为空,将新节点作为根节点。
  2. 若新节点的值小于当前节点的值,递归地插入到左子树中。
  3. 若新节点的值大于当前节点的值,递归地插入到右子树中。
	public void insert(int num) {
		
		Node node =new Node(num);
		if(root==null) {
			root=node;
			return;
		}
		Node index=root;
		while(true) {
			if(index.value>num) {
				if(index.left==null) {
					index.left=node;
					return;
				}
				else {
					
					index=index.left;
				}
				
			}
			else {
				if(index.right==null) {
					index.right=node;
					return;
										
				}
				else {
					index=index.right;
					
				}
			}
			
		}

	}
  • 时间复杂度:在平均情况下为 O(logn),这里的 n 是树中节点的数量。不过在最坏情况下(树退化为链表),时间复杂度会变为 O(n)。
  • 空间复杂度:递归调用栈的深度平均为 O(logn),最坏情况下为 O(n)

7 遍历

具体步骤
  1. 队列初始化:创建一个Queue对象,用于存储待遍历的节点。这里使用LinkedList来实现队列。
  2. 根节点入队:若根节点不为空,就把根节点添加到队列中。
  3. 循环遍历:只要队列不为空,就持续从队列头部取出节点,打印其值,接着将该节点的左右子节点(若存在)添加到队列尾部。
  4. 结束输出:遍历完成后换行。
import java.util.LinkedList;
import java.util.Queue;

class Node {
    int value;
    Node left;
    Node right;

    public Node(int val) {
        value = val;
    }
}

class BinaryTree {
    Node root;

    public void levelOrder() {
        // 创建一个队列用于存储待遍历的节点
        Queue<Node> queue = new LinkedList<Node>();
        // 如果根节点不为空,将根节点加入队列
        if (root != null) {
            queue.add(root);
        }
        // 用于临时存储从队列中取出的节点
        Node index = null;
        // 当队列不为空时,持续进行遍历
        while (!queue.isEmpty()) {
            // 从队列头部取出一个节点
            index = queue.poll();
            // 打印该节点的值
            System.out.print(index.value + " ");
            // 如果该节点的左子节点不为空,将左子节点加入队列
            if (index.left != null) {
                queue.add(index.left);
            }
            // 如果该节点的右子节点不为空,将右子节点加入队列
            if (index.right != null) {
                queue.add(index.right);
            }
        }
        // 遍历结束后换行
        System.out.println();
    }
}

8 删除

二叉搜索树删除思路

在二叉搜索树中删除节点,需要考虑三种情况:

  1. 要删除的节点是叶子节点:直接删除该节点,即把其父节点指向该节点的指针置为 null
  2. 要删除的节点只有一个子节点:用该子节点替换要删除的节点,也就是让其父节点指向该子节点。
  3. 要删除的节点有两个子节点:找到该节点右子树中的最小节点(也可以是左子树中的最大节点),用这个最小节点的值替换要删除节点的值,然后再删除右子树中的最小节点。
class Node {
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }
}

class BinarySearchTree {
    Node root;

    // 查找
    public Node search(int num) {
        Node index = root;
        while (index != null && index.value != num) {
            if (index.value > num) {
                index = index.left;
            } else {
                index = index.right;
            }
        }
        return index;
    }

    // 查找目标节点父节点
    public Node searchFather(int num) {
        Node index = root;
        while (index != null) {
            if ((index.left != null && index.left.value == num) || (index.right != null && index.right.value == num)) {
                return index;
            } else if (index.value > num) {
                index = index.left;
            } else {
                index = index.right;
            }
        }
        return null;
    }

    // 找一颗树最小值
    public int min(Node treeNode) {
        if (treeNode == null) {
            throw new IllegalArgumentException("传入的节点不能为 null");
        }
        Node index = treeNode;
        while (index.left != null) {
            index = index.left;
        }
        return index.value;
    }

    // 删除
    public void delete(int num) {
        if (root == null) {
            return;
        }
        // 找目标节点
        Node target = search(num);
        // 没有节点结束
        if (target == null) {
            System.out.println("没有节点");
            return;
        }
        // 找目标节点父节点
        Node parent = searchFather(num);

        if (target.left == null && target.right == null) {
            // 删除叶子节点
            if (parent == null) {
                root = null;
            } else if (parent.left != null && parent.left.value == num) {
                parent.left = null;
            } else {
                parent.right = null;
            }
        } else if (target.left != null && target.right != null) {
            // 有两颗子树
            int minValue = min(target.right);
            delete(minValue);
            target.value = minValue;
        } else {
            // 一颗子树
            Node child = target.left != null ? target.left : target.right;
            if (parent == null) {
                root = child;
            } else if (parent.left != null && parent.left.value == num) {
                parent.left = child;
            } else {
                parent.right = child;
            }
        }
    }
}

代码功能分析

1. search(int num) 方法

此方法用于在二叉搜索树中查找值为 num 的节点。它从根节点开始,依据节点值与 num 的大小关系,向左或向右遍历树,直至找到目标节点或者遍历到空节点。

2. searchFather(int num) 方法

该方法用于查找值为 num 的节点的父节点。同样从根节点开始遍历,若当前节点的左子节点或右子节点的值等于 num,则返回当前节点;否则,依据节点值与 num 的大小关系继续向左或向右遍历。

3. min(Node treeNode) 方法

此方法用于查找以 treeNode 为根的子树中的最小值节点。由于二叉搜索树的性质,最小值节点位于最左侧,所以不断向左遍历直至找到最左侧的节点。

相关文章:

  • 《大数据视角下美团优选消费者购买决策影响因素研究》开题报告
  • nest学习(5)
  • 《AI大模型趣味实战 》第7集:多端适配 个人新闻头条 基于大模型和RSS聚合打造个人新闻电台(Flask WEB版) 1
  • Web网页
  • Windows下编译安装Qt5.15.0指南
  • Kubernetes 学习详细资料
  • 【Python机器学习】3.7. 主成分分析(PCA)实战
  • HT9126DA芯片为生活增添光彩的LED灯IC
  • Qt程序增加Dump文件保存
  • Keras和 Estimator的创建历史是什么
  • 第五章 | Solidity 数据类型深度解析
  • Mysql的锁
  • lodash 学习笔记/使用心得
  • 2.企业级AD活动目录架构与设计原则实战指南
  • C# 调用 VITS,推理模型 将文字转wav音频net8.0 跨平台
  • Python FastApi(3):路径参数
  • 使用AI一步一步实现若依前端(16)
  • Elasticsearch 中的数据分片问题
  • Deepseek浪潮下,汽车芯片开启“大变局”,谁将领跑?
  • 进程地址空间(上)【Linux】
  • 新华时评:任凭风云变幻,中俄关系从容前行
  • 纪念苏联伟大卫国战争胜利80周年阅兵彩排,解放军仪仗队亮相
  • 60岁济南石化设计院党总支书记、应急管理专家李有臣病逝
  • 习近平离京赴莫斯科对俄罗斯进行国事访问并出席纪念苏联伟大卫国战争胜利80周年庆典
  • 黄仁勋:中国AI市场将达500亿美元,美国企业若无法参与是巨大损失
  • 加拿大总理访美与特朗普“礼貌交火”