1、数据结构之:树的相关定义和二叉树

news/2025/1/8 19:11:55/

数据结构之:树的相关定义和二叉树

一、相关定义

1.1、树的定义

在这里插入图片描述

·N个节点组成的具有层次关系的优先集合,其中N>=0,当N=0时称为空树,在任意非空树中:
1、有且只有一个根节点,根节点是没有父节点的
2、每个节点都有0个或多个子节点
3、除根节点之外的其余节点可分为互不相交的有限集,其中每个集合本身亦是一棵树,
称为原树的子树·节点:上图中的每一个圈都是一个节点
·根节点:上图中的A就是根节点
·父节点:A就是BC的父节点,B就是DEF的父节点,G是I的父节点
·子节点:B和C是A的子节点,DEF是B的子节点,GH是C的子节点
·兄弟节点:有同一个父节点的节点即兄弟节点,DEF就是兄弟节点,BC也是兄弟节点
·叶子结点:没有子节点的节点就是叶子结点
·节点的度:节点的子节点个数,B的度是3,A的度是2,C的度是2,G的度是1
·节点层次:从根节点开始,根为第一层,跟的子节点为第二层,依次类推
·树的深度:树中节点的最大层次就是根的深度

1.2、二叉树的定义

在这里插入图片描述

·N个节点组成的具有层次关系的有限集合,其中N>=0,当N=0时称为空树
1、每个节点最多有2个子节点,所以二叉树中不存在度大于2的节点
2、左子树和右子树是有顺序的,次序不能任意颠倒
3、即使树中某个节点只有1个子节点,也要区分它是左子树还是右子树

常见的二叉树二叉树、完全二叉树、二叉查找树、平衡二叉树、红黑树

1.2.1、满二叉树和完美二叉树定义

在这里插入图片描述

·满二叉树:
除了叶子节点,每个节点的度都为2,不存在度为1的节点
·完美二叉树
每一层节点的数都达到最大值(第一层1个节点,第二层2个节点,第三层4个节点,
第四层8个节点,依次类推)
1.2.2、完全二叉树

在这里插入图片描述

1、如果二叉树中除去最后一层节点外为满二叉树
2、且最后一层的节点一次从左到右分布

二、二叉查找树

在这里插入图片描述

·二叉查找树 又叫做 二叉排序树、二叉搜索树·二叉查找树定义:
1、若左子树不为空,则左子树上所有节点的值均小于它的根节点的值
2、若右子树不为空,则右子树上所有节点的值均大于它的根节点的值
3、左、右子树也分别为二叉排序树
4、没有键值相等的节点·前驱结点定义:
1、前驱结点是小于该节点的最大节点
2、对二叉树中序遍历,遍历后的顺序,当前节点的前一个节点为该节点的前驱结点·后继节点定义:
1、后继节点是大于该节点的最小节点
2、对二叉树中序遍历,遍历后的顺序,当前节点的后一个节点为该节点的后继节点·操作:查找节点
1、若根节点的关键字值等于查找的关键字,成功
2、否则,若小于根节点的关键字值,递归查左子树
3、若大于根节点的关键字值,递归查右子树
4、若子树为空,查找不成功·操作:遍历节点
1、前序遍历:根节点--左子树--右子树8  5  3  2  4  7  6  11  9  12
2、中序遍历:左子树--根节点--右子树2  3  4  5  6  7  8  9  11  12 
3、后序遍历:左子树--右子树--根节点2  4  3  6  7  5  9  12  11  8
4、层序遍历:一层一层遍历(从上到下 从左到右)8  5  11  3  7  9  12  2  4  6·操作:插入节点
假入插入节点为N
1、若果根节点为空,直接把N设为根节点
2、如果N小于根节点,则把N插入根节点的左子树,将根节点的左子树作为根节点,回到第一步
3、如果N大于根节点,则把N插入根节点的右子树,将根节点的右子树作为根节点,回到第一步
4、如果N等于根节点,直接返回根节点·操作:删除节点
1、叶子节点:直接删除
2、节点有一个左子节点:删除该节点,让左子节点替代自己
3、节点有一个右子节点:删除该节点,让右子节点替代自己
4、节点有两个子节点:删除该节点,让该节点左子树中最大的或者右子树中最小的替代自己(即让该节点的前驱结点或后继节点来替代自己)
java">/*** 节点*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Node<K extends Comparable,V> {private K key;private V value;private Node leftNode;private Node rightNode;private Node parentNode;
}
java">/*** 树工具类*/
public class TreeOperation {// 用于获得树的层数public static int getTreeDepth(Node root) {return root == null ? 0 : (1 + Math.max(getTreeDepth(root.getLeftNode()), getTreeDepth(root.getRightNode())));}private static void writeArray(Node currNode, int rowIndex, int columnIndex, String[][] res, int treeDepth) {// 保证输入的树不为空if (currNode == null) return;// 先将当前节点保存到二维数组中res[rowIndex][columnIndex] = String.valueOf(currNode.getValue());// 计算当前位于树的第几层int currLevel = ((rowIndex + 1) / 2);// 若到了最后一层,则返回if (currLevel == treeDepth) return;// 计算当前行到下一行,每个元素之间的间隔(下一行的列索引与当前元素的列索引之间的间隔)int gap = treeDepth - currLevel - 1;// 对左儿子进行判断,若有左儿子,则记录相应的"/"与左儿子的值if (currNode.getLeftNode() != null) {res[rowIndex + 1][columnIndex - gap] = "/";writeArray(currNode.getLeftNode(), rowIndex + 2, columnIndex - gap * 2, res, treeDepth);}// 对右儿子进行判断,若有右儿子,则记录相应的"\"与右儿子的值if (currNode.getRightNode() != null) {res[rowIndex + 1][columnIndex + gap] = "\\";writeArray(currNode.getRightNode(), rowIndex + 2, columnIndex + gap * 2, res, treeDepth);}}public static void show(Node root) {if (root == null) System.out.println("EMPTY!");// 得到树的深度int treeDepth = getTreeDepth(root);// 最后一行的宽度为2的(n - 1)次方乘3,再加1// 作为整个二维数组的宽度int arrayHeight = treeDepth * 2 - 1;int arrayWidth = (2 << (treeDepth - 2)) * 3 + 1;// 用一个字符串数组来存储每个位置应显示的元素String[][] res = new String[arrayHeight][arrayWidth];// 对数组进行初始化,默认为一个空格for (int i = 0; i < arrayHeight; i ++) {for (int j = 0; j < arrayWidth; j ++) {res[i][j] = " ";}}// 从根节点开始,递归处理整个树// res[0][(arrayWidth + 1)/ 2] = (char)(root.val + '0');writeArray(root, 0, arrayWidth/ 2, res, treeDepth);// 此时,已经将所有需要显示的元素储存到了二维数组中,将其拼接并打印即可for (String[] line: res) {StringBuilder sb = new StringBuilder();for (int i = 0; i < line.length; i ++) {sb.append(line[i]);if (line[i].length() > 1 && i <= line.length - 1) {i += line[i].length() > 4 ? 2: line[i].length() - 1;}}System.out.println(sb.toString());}}
}
java">/*** 二叉查找树*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class BinaryTree<K extends Comparable,V> {private Node<K,V> root;/*** 插入*/public void insert(K key,V value){//根节点为null,直接设为根节点if(root==null){this.root = new Node<>(key,value,null,null,null);return;}//查找要插入的节点的父节点Node parentNode = null;Node rootNode = this.root;int compRes = 0;while(rootNode!=null){parentNode = rootNode;compRes = key.compareTo(rootNode.getKey());if(compRes>0){//放入右子树rootNode = rootNode.getRightNode();}else if(compRes==0){//相等,替换valuerootNode.setValue(value);return;}else{//放入左子树rootNode = rootNode.getLeftNode();}}//插入Node tempNode = parentNode;if(compRes>0){parentNode.setRightNode(new Node<K,V>(key,value,null,null,tempNode));}else{parentNode.setLeftNode(new Node<K,V>(key,value,null,null,tempNode));}}/*** 查找*/public Node findNode(K key){if(this.root==null){return null;}Node rootNode = this.root;while(rootNode!=null){int compRes = key.compareTo(rootNode.getKey());if(compRes<0){rootNode = rootNode.getLeftNode();}else if(compRes==0){return rootNode;}else{rootNode = rootNode.getRightNode();}}return null;}/*** 前序遍历NLR: 根节点--左子树--右子树* @param root*/public void preOrderTraversal(Node root){if(root!=null){//根节点System.out.print(root.getKey()+"  ");//左子树if(root.getLeftNode()!=null){preOrderTraversal(root.getLeftNode());}//右子树if(root.getRightNode()!=null){preOrderTraversal(root.getRightNode());}}}/*** 中序遍历LNR: 左子树--根节点--右子树* @param root*/public void inorderTraversal(Node root){if(root!=null){//左子树if(root.getLeftNode()!=null){inorderTraversal(root.getLeftNode());}//根节点System.out.print(root.getKey()+"  ");//右子树if(root.getRightNode()!=null){inorderTraversal(root.getRightNode());}}}/*** 后序遍历LRN: 左子树--右子树--根节点* @param root*/public void postorderTraversal(Node root){if(root!=null){//左子树if(root.getLeftNode()!=null){postorderTraversal(root.getLeftNode());}//右子树if(root.getRightNode()!=null){postorderTraversal(root.getRightNode());}//根节点System.out.print(root.getKey()+"  ");}}/*** 层序遍历:一层一层遍历(从上到下 从左到右)* @param root*/public void levelOrderTraversal(Node root){if(root!=null){Queue<Node> queue = new LinkedList();queue.add(root);while (!queue.isEmpty()){Node removeNode = queue.remove();System.out.print(removeNode.getKey()+"  ");Node leftNode = removeNode.getLeftNode();if(leftNode!=null){queue.add(leftNode);}Node rightNode = removeNode.getRightNode();if(rightNode!=null){queue.add(rightNode);}}}}/*** 删除:* 1、叶子节点:直接删除* 2、节点有一个左子节点:删除该节点,让左子节点替代自己* 3、节点有一个右子节点:删除该节点,让右子节点替代自己* 4、节点有两个子节点:删除该节点,让该节点左子树中最大的或者右子树中最小的替代自己*                  (即让该节点的前驱结点或后继节点来替代自己)*/public void deleteNode(K key){Node select = findNode(key);if(select!=null){if(select.getLeftNode()==null && select.getRightNode()==null){//1、删除的是叶子节点Node parentNode = select.getParentNode();if(parentNode==null){//删除的节点是根节点this.root = null;}else{//删除的节点不是根节点if(parentNode.getLeftNode()==select){//删除的节点是其父节点的左节点,将其父节点的左节点置为空parentNode.setLeftNode(null);}else{//删除的节点是其父节点的右节点,将其父节点的右节点置为空parentNode.setRightNode(null);}}}else if(select.getLeftNode()==null || select.getRightNode()==null){//2、删除的节点有一个子节点Node parentNode = select.getParentNode();Node childrenNode = select.getLeftNode()!=null ? select.getLeftNode() : select.getRightNode();if(parentNode==null){//删除的节点是根节点childrenNode.setParentNode(null);this.root = childrenNode;}else{//删除的节点不是根节点if(parentNode.getLeftNode()==select){//删除的节点是其父节点的左节点,将其子节点设置为父节点的左节点parentNode.setLeftNode(childrenNode);}else{//删除的节点是其父节点的右节点,将其子节点设置为父节点的右节点parentNode.setRightNode(childrenNode);}//将其子节点的父节点变更为其父节点childrenNode.setParentNode(parentNode);}}else{//3、删除的节点有两个子节点--我们采用将删除节点的后继节点替代自己//既然是后继节点那么后继节点要么没有子节点要么只有右节点Node parentNode = select.getParentNode();//因为删除的节点有两个子节点,所以后继几点肯定不为nullNode nextNode = findNextNode(select);//后继节点的父节点:--后继节点肯定是其父节点的左子节点(后继节点肯定有父节点)//后继节点的父节点==删除节点//后继节点的父节点!=删除节点Node nextNodeParentNode = nextNode.getParentNode();//后继节点的子节点(后继节点只能有右子节点或者没有子节点)Node nextNodeChildrenNode = nextNode.getRightNode();//3.1、后继接点和删除节点互换if(parentNode==null){//删除的节点是根节点--后继节点替代自己nextNode.setParentNode(null);this.root = nextNode;}else{//删除的节点不是根节点--后继节点替代自己nextNode.setParentNode(parentNode);if(parentNode.getLeftNode()==select){parentNode.setLeftNode(nextNode);}else{parentNode.setRightNode(nextNode);}}//删除节点的左子树成为后继节点的左子树if(select.getLeftNode()!=null){select.getLeftNode().setParentNode(nextNode);nextNode.setLeftNode(select.getLeftNode());}//3.2、后继节点的父节点和后继节点的子节点挂钩if(nextNodeChildrenNode==null){//后继节点没有子节点if(nextNodeParentNode==select){//后继节点的父节点==删除节点(不做任何操作)}else{//后继节点的父节点!=删除节点(设置后继节点的父节点的左子树为null)nextNodeParentNode.setLeftNode(null);}}else{//后继节点有子节点if(nextNodeParentNode==select){//后继节点的父节点==删除节点(不做任何操作)}else{//后继节点的父节点!=删除节点//(设置后继节点的父节点的左子树为后继节点的子节点)//(设置后继节点的子节点的父节点为后继节点的父节点)nextNodeParentNode.setLeftNode(nextNodeChildrenNode);nextNodeChildrenNode.setParentNode(nextNodeParentNode);}}//3.3、删除节点的右子树成为后继节点的右子树if(select.getRightNode()!=nextNode){select.getRightNode().setParentNode(nextNode);nextNode.setRightNode(select.getRightNode());}}}}/*** 查询后继节点*/public Node  findNextNode(Node currentNode){if(currentNode!=null){Node nextNode = currentNode.getRightNode();if(nextNode!=null){while(nextNode.getLeftNode()!=null){nextNode = nextNode.getLeftNode();}return nextNode;}}return null;}/*** 查询前驱结点*/public Node findPreNode(Node currentNode){if(currentNode!=null){Node preNode = currentNode.getLeftNode();if(preNode!=null){while(preNode.getRightNode()!=null){preNode = preNode.getRightNode();}return preNode;}}return null;}public static void main(String[] args) {BinaryTree  bt = new BinaryTree();bt.insert(8,8);bt.insert(5,5);bt.insert(11,11);bt.insert(3,3);bt.insert(2,2);bt.insert(4,4);bt.insert(7,7);bt.insert(6,6);bt.insert(9,9);bt.insert(12,12);System.out.println("**********打印树1***********");TreeOperation.show(bt.getRoot());System.out.println("**********查找***********");Node node = bt.findNode(7);System.out.println(node.getKey());System.out.println("***********前序遍历**********");bt.preOrderTraversal(bt.getRoot());System.out.println("\n**********中序遍历***********");bt.inorderTraversal(bt.getRoot());System.out.println("\n**********后序遍历***********");bt.postorderTraversal(bt.getRoot());System.out.println("\n**********层序遍历***********");bt.levelOrderTraversal(bt.getRoot());BinaryTree  bt2 = new BinaryTree();bt2.insert(8,8);bt2.insert(7,7);bt2.insert(4,4);bt2.insert(12,12);bt2.insert(2,2);bt2.insert(5,5);bt2.insert(9,9);bt2.insert(14,14);bt2.insert(1,1);bt2.insert(3,3);bt2.insert(10,10);bt2.insert(13,13);bt2.insert(16,16);bt2.insert(15,15);System.out.println("\n**********打印树2***********");TreeOperation.show(bt2.getRoot());System.out.println("\n**********删除节点***********");bt2.deleteNode(14);TreeOperation.show(bt2.getRoot());}
}
##测试:
**********打印树1***********8            /     \         5           11     /   \       /   \    3       7   9       12 / \     /               
2   4   6                
**********查找***********
7
***********前序遍历**********
8  5  3  2  4  7  6  11  9  12  
**********中序遍历***********
2  3  4  5  6  7  8  9  11  12  
**********后序遍历***********
2  4  3  6  7  5  9  12  11  8  
**********层序遍历***********
8  5  11  3  7  9  12  2  4  6  
**********打印树2***********8                        /       \                    7               12               /               /     \             4               9           14         /   \               \       /   \        2       5               10  13      16     / \                                 /       1   3                               15       **********删除节点***********8                        /       \                    7               12               /               /     \             4               9           15         /   \               \       /   \        2       5               10  13      16     / \                                         1   3   

三、平衡二叉树

当我们录入二叉查询树的时候可能会出现  2->3->4->5的链表结构,345全部成了2的右子树·二叉平衡树AVL
平衡二叉树的每个节点的左子树和右子树的高度最多差1·平衡因子
该节点左子树的高度-该节点右子树的高度,即左右子树的高度差【减小的二叉树的高度,这样查询的时候速度更快】

在这里插入图片描述

二叉树四种需要调整平衡的情况

在这里插入图片描述


http://www.ppmy.cn/news/1561617.html

相关文章

PyCharm简单调试

本文简单讲述一下PyCharm中经常用到的调试操作。 示例代码如下&#xff1a; for i in range(10):print("hello", i)if i > 2:print("ok!")在代码前面打上断点&#xff0c;如下图所示&#xff1a; 单机调试按钮Debug 单机Resume Program按钮&#xf…

STM32之CAN通讯(十一)

STM32F407 系列文章 - CAN通讯&#xff08;十一&#xff09; 目录 前言 一、CAN 二、CAN驱动电路 三、CAN软件设计 1.CAN状态初始化 2.头文件相关定义 3.接收中断服务函数 4.用户层使用 1.用户层相关定义 2.发送数据 3.接收数据 1.查询方式处理 2.中断方式处理 3…

【Vue】:解决动态更新 <video> 标签 src 属性后视频未刷新的问题

问题描述 在 Vue.js 项目&#xff0c;当尝试动态更新 <video> 标签的 <source> 元素 src 属性来切换视频时&#xff0c;遇到了一个问题&#xff1a;即使 src 属性已更改&#xff0c;浏览器仍显示旧视频。具体表现为用户选择新视频后&#xff0c;视频区域继续显示之…

前端-计算机网络篇

一.网络分类 1.按照网络的作用范围进行分类 &#xff08;1&#xff09;广域网WAN(Wide Area Network) 广域网的作用范围通常为几十到几千公里,因而有时也称为远程网&#xff08;long haul network&#xff09;。广域网是互联网的核心部分&#xff0c;其任务是长距离运送主机…

关于Linux PAM模块下的pam_listfile

讲《Linux下禁止root远程登录访问》故事的时候&#xff0c;说好会另开一篇讲讲pam_listfile。我们先看看pam_listfile的man文档怎么介绍的。 下面这些就好比人物的简介&#xff0c;甚是恼人&#xff1b;让人看得不明就里&#xff0c;反正“他大舅他二舅都是他舅”。可以直接跳…

Js:面向对象的特点

一、封装&#xff1a;安全性 二、继承&#xff1a;扩展性 可以通过 extends 关键字实现继承 1、减少重复的代码&#xff1a; class Animals { // 父类&#xff08;超类&#xff09;constructor(name,age){this.name name;this.age age;}run(){console.log(跑)} }class Dog…

AWS DMS基础知识

1.AWS Database Migration Service &#xff08;DMS&#xff09; 概述 AWS DMS 定义&#xff1a;它能助力以最少停机时间把数据库迁移至 AWS&#xff0c;支持同构&#xff08;如 Oracle 到 Oracle&#xff09;、异构&#xff08;如 Oracle 到 PostgreSQL &#xff09;迁移。常…

【Qt】主窗口

目录 Qt主窗口的构成 菜单栏 创建菜单栏 向菜单栏中添加菜单 向菜单中添加菜单项 工具栏 创建工具栏 工具栏的停靠位置 工具栏的浮动属性 工具栏的移动属性 状态栏 创建状态栏 向状态栏中添加的信息 浮动窗口 浮动窗口的停靠位置 向浮动窗口中添加控件 Qt主窗口的…