数据结构——二叉树(Binary Tree)
一、二叉树(Binary Tree)
(一)基本概念
二叉树是每个节点最多有两个子节点的树结构,通常称为左子节点(left child)和右子节点(right child)。
(二)基本特性
节点结构:每个节点包含三个部分
数据域(存储节点的值)
左指针(指向左子节点)
右指针(指向右子节点)
基本性质:
第i层最多有2^(i-1)个节点
深度为k的二叉树最多有2^k - 1个节点
对于任何非空二叉树,叶节点数等于度为2的节点数加1
(三)常见类型
满二叉树:每一层的节点数都达到最大值
完全二叉树:除最后一层外其他层都是满的,且最后一层节点都靠左排列
二叉搜索树(BST):左子树所有节点值小于根节点,右子树所有节点值大于根节点
平衡二叉树(AVL树):任何节点的左右子树高度差不超过1
红黑树:自平衡的二叉搜索树,保证了最坏情况下的操作时间复杂度
(四)遍历方式
1. 基本概念
树的遍历是指按照某种顺序访问树中的所有节点,确保每个节点都被访问且仅被访问一次。根据访问顺序的不同。
2. 遍历类型
深度优先遍历(DFS):
前序遍历:根→左→右
中序遍历:左→根→右
后序遍历:左→右→根
广度优先遍历(BFS):
层次遍历:按从上到下、从左到右的顺序访问节点
3. 遍历方法详解
前序遍历(Preorder Traversal)
访问顺序:根节点 → 左子树 → 右子树
算法步骤:
访问根节点
前序遍历左子树
前序遍历右子树
示例: 对于二叉树:
A/ \B C/ \
D E
前序遍历结果为:A → B → D → E → C
中序遍历(Inorder Traversal)
访问顺序:左子树 → 根节点 → 右子树
算法步骤:
中序遍历左子树
访问根节点
中序遍历右子树
示例: 使用前例中的二叉树: 中序遍历结果为:D → B → E → A → C
后序遍历(Postorder Traversal)
访问顺序:左子树 → 右子树 → 根节点
算法步骤:
后序遍历左子树
后序遍历右子树
访问根节点
示例: 使用前例中的二叉树: 后序遍历结果为:D → E → B → C → A
层次遍历(Level Order Traversal)
按树的层次从上到下,从左到右访问节点
实现方法: 通常使用队列辅助实现:
将根节点入队
当队列不为空时:
出队一个节点并访问
将其左右子节点(如果存在)依次入队
示例: 使用前例中的二叉树: 层次遍历结果为:A → B → C → D → E
二、有序二叉树(Binary Search Tree)
(一)基本概念
有序二叉树(Binary Search Tree,BST)是一种特殊的二叉树数据结构,它具有以下关键特性:
有序性:对于树中的每个节点:
左子树中所有节点的值都小于该节点的值
右子树中所有节点的值都大于该节点的值
没有重复节点(可以有变体允许重复)
结构特性:
每个节点最多有两个子节点
通常包含三个属性:值、左子节点指针和右子节点指针
(二)基本操作
1. 查找操作
查找流程:
从根节点开始比较
如果查找值等于当前节点值,返回该节点
如果查找值小于当前节点值,递归查找左子树
如果查找值大于当前节点值,递归查找右子树
如果到达空节点,则查找失败
2. 插入操作
插入流程:
如果树为空,创建新节点作为根节点
否则,从根节点开始比较
如果插入值小于当前节点值:
如果左子节点为空,插入为左子节点
否则递归处理左子树
如果插入值大于当前节点值:
如果右子节点为空,插入为右子节点
否则递归处理右子树
3. 删除操作
删除有三种情况:
删除叶子节点:直接删除
删除有一个子节点的节点:用其子节点替换
删除有两个子节点的节点:
找到右子树的最小节点(或左子树的最大节点)
用该节点值替换要删除的节点值
递归删除该最小/最大节点
4. 遍历操作
三、实现示例(Java实现)
public class BinaryTree {public Node root;// 插入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;}index=index.left;}else {if(index.right==null) {index.right=node;return;}index=index.right;}}}// 遍历
// 先序遍历public void beforeOrder(Node node) {if(node==null) {return;}System.out.println(node.value);beforeOrder(node.left);beforeOrder(node.right);}// 中序遍历public void inOrder(Node node) {if(node==null) {return;}inOrder(node.left);System.out.println(node.value);inOrder(node.right);}// 后序遍历public void afterOrder(Node node) {if(node==null) {return;}afterOrder(node.left);afterOrder(node.right);System.out.println(node.value);} // 广度优先遍历public void levelOrder() {Queue<Node> queue = new LinkedList<Node>();queue.add(root);while(!queue.isEmpty()) {Node index = queue.poll();System.out.println(index.value);if(index.left!=null) {queue.add(index.left);}if(index.right!=null) {queue.add(index.right);}}}// 查找public Node search(int num) {if(root==null) {return null;}Node index = root;while(index!=null) {if(index.value==num) {//相等,找到了return index;}else if(index.value<num) {index = index.right;}else {index = index.left;}}return null;}// 找父级节点public Node searchParent(int num) {if(root.value==num) {//根节点的值和目标值相等return null;}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 void delete(int num) {//找目标节点Node target = search(num);if(target==null) {System.out.println("没有这个节点");return;}//找目标节点的父节点Node parent = searchParent(num);//删除if(target.left==null&&target.right==null) {//删除叶子节点//如果没有父节点if(parent==null) {root=null;return;}//如果有父节点//判断目标节点是左孩子还是右孩子if(parent.left!=null&&parent.left.value==num) {//左孩子parent.left = null;}else {//右孩子parent.right=null;}}else if (target.left!=null&&target.right!=null) {//删除有两颗子树的节点//目标节点右子树的最小值ֵNode index = target.right;while (index.left!=null) {index=index.left;}//最小值ֵint min = index.value;//先删除delete(min);//替换target.value = min;}else {//删除只有一颗子树的节点//没有父节点if(parent==null) {//判断目标节点是左孩子还是右孩子if(target.left!=null) {//左子树root = target.left;}else {//右子树root = target.right;}return;}//有父节点//判断目标节点是左孩子还是右孩子if(parent.left!=null&&parent.left.value==num) {//左孩子//判断目标节点有左子树还是右子树if(target.left!=null) {//左子树parent.left = target.left;}else {//右子树parent.left = target.right;}}else {//右孩子//判断目标节点有左子树还是右子树if(target.left!=null) {//左子树parent.right = target.left;}else {//右子树parent.right = target.left;}}}}@Overridepublic String toString() {return "BinaryTree [root=" + root + "]";}
}