Java,数据结构和算法,八大数据结构,树,二叉树、B树
lipiwang 2024-11-21 17:43 6 浏览 0 评论
树
树:是由结点(顶点)和边组成,可能是非线性的,且不存在着任何环的一种数据结构;
空树:没有结点的树称为空(null或empty)树;
非空树:一棵非空的树包括一个根结点,还(很可能)有多个附加结点,所有结点构成一个多级分层结构;
结点:使用树结构存储的每一个数据元素都被称为“结点”;图中,数据元素 A 就是一个结点;
父结点(双亲结点)、子结点和兄弟结点:对于图 中的结点 A、B、C、D 来说,A是 B、C、D结点的父结点(也称为“双亲结点”),而 B、C、D 都是 A结点的子结点(也称“孩子结点”)。对于B、C、D来说,它们都有相同的父结点,所以它们互为兄弟结点。
结点的度和层次:对于一个结点,拥有的子树数(结点有多少分支)称为结点的度(Degree);图中,根结点 A下分出了 3个子树,所以,结点 A的度为 3。
深度:定义一棵树的根结点层次为1,其他结点的层次是其父结点层次加1,一棵树中所有结点的层次的最大值称为这棵树的深度。
有序树和无序树:如果树中结点的子树从左到右看,谁在左边,谁在右边,是有规定的,这棵树称为有序树;反之称为无序树。
森林:由 m(m >= 0)个互不相交的树组成的集合被称为森林。
二叉树
二叉树:每个节点最多只能有两个子节点;简单地理解,满足以下两个条件的树就是二叉树:
1、本身是有序树;
2、树中包含的各个节点的度不能超过 2,即只能是 0、1或者 2;
满二叉树:
如果二叉树中除了叶子结点,每个结点的度都为 2,则此二叉树称为满二叉树。
完全二叉树:
一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
二叉树的遍历方式:前序遍历,后续遍历,中序遍历;
二叉树的遍历
前序遍历:先遍历父节点,在遍历左子树;然后是右子树;
中序遍历:先遍历左子树,在遍历父点击,在遍历右子树;
后序遍历:先遍历左子树,在遍历右子树,然后是父节点;
二叉树的删除
1、如果删除的叶子节点,删除该节点;
2、如果删除的非叶子节点,删除该子树;
顺序存储二叉树
将二叉树存储在一个数组中,通过存储元素的下标反映元素之间的父子关系;
以数组的方式来存储二叉树,但依然可以前序、中序、后序遍历树;
第N个元素的左子树节点为:2*N+1;
第N个元素的右子树节点为:2*N+2;
第N个元素的父节点为:(N-1)/2;
N表示二叉树中的第几个元素(从0开始编号)
堆排序会用到顺序存储二叉树;
线索化二叉树
N个节点的二叉链表中含有N+1【公式2n-(n-1)=n+1】个空指针域;
利用二叉链表中的空指针域,存放指向该节点在某种遍历次序下的前驱和后继节点的指针(珍重附加的指针称为"线索");
这种加了线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树(Threaded BinaryTree);
根据线索性质的不同,线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树;
一个节点的前一个节点,称为前驱;
一个节点的后一个节点,称为后继;
顺序存储二叉树——规则
第N个元素的左子树节点为:2*N+1;
第N个元素的右子树节点为:2*N+2;
第N个元素的父节点为:(N-1)/2;
N表示二叉树中的第几个元素(从0开始编号)
B树
B树,即二叉搜索树:1、所有非叶子结点至多拥有两个儿子(Left和Right);2、所有结点存储一个关键字;3、非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树;
B-树
B-树,是一种多路搜索树(并不是二叉的):1、定义任意非叶子结点最多只有M个儿子;且M>2;2、根结点的儿子数为[2, M];3、除根结点以外的非叶子结点的儿子数为[M/2, M];4、每个结点存放至少M/2-1(取上整)和至多M-1个关键字;(至少2个关键字);5、非叶子结点的关键字个数=指向儿子的指针个数-1;6、非叶子结点的关键字:K[1], K[2], …, K[M-1];且K[i] < K[i+1];7、非叶子结点的指针:P[1], P[2], …, P[M];其中P[1]指向关键字小于K[1]的子树,P[M]指向关键字大于K[M-1]的子树,其它P[i]指向关键字属于(K[i-1], K[i])的子树;8、所有叶子结点位于同一层;
B+树
B+树是应文件系统所需而产生的一种B-树的变形树,一棵m阶的B+树和m 阶的B-。
树的差异在于:1、有n 棵子树的结点中含有n 个关键码;2、所有的叶子结点中包含了全部关键码的信息,及指向含有这些关键码记录的指针,且叶子结点本身依关键码的大小自小而大的顺序链接;3、所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键码。
B*树
B*树是B+树的变体,在B+树的非根和非叶子结点再增加指向兄弟的指针;
B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3(代替B+树的1/2);B+树的分裂:当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针;B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针;B*树的分裂:当一个结点满时,如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了);如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针;所以,B*树分配新结点的概率比B+树要低,空间使用率更高;
案例:二叉树实现
public class Element<E> {
// 存放数据
private E data;
// 左子树
private Element<E> leftElement;
// 右子树
private Element<E> rightElement;
public Element(E data) {
super();
this.data = data;
}
public E getData() {
return data;
}
public void setData(E data) {
this.data = data;
}
public Element<E> getLeftElement() {
return leftElement;
}
public void setLeftElement(Element<E> leftElement) {
this.leftElement = leftElement;
}
public Element<E> getRightElement() {
return rightElement;
}
public void setRightElement(Element<E> rightElement) {
this.rightElement = rightElement;
}
@Override
public String toString() {
return "BinaryTree [data=" + data + "]";
}
// ================================================================================//
// ===operate
// ================================================================================//
// 前序遍历
public void preOrderTraversal() {
// 1、输出父节点
System.out.println(this);
// 2、递归左子树
if (this.leftElement != null) {
this.leftElement.preOrderTraversal();
}
// 3、递归右子树
if (this.rightElement != null) {
this.rightElement.preOrderTraversal();
}
}
// 中序遍历
public void infixOrderTraversal() {
// 1、递归左子树
if (this.leftElement != null) {
this.leftElement.infixOrderTraversal();
}
// 2、输出父节点
System.out.println(this);
// 3、递归右子树
if (this.rightElement != null) {
this.rightElement.infixOrderTraversal();
}
}
// 后序遍历
public void postOrderTraversal() {
// 1、递归左子树
if (this.leftElement != null) {
this.leftElement.postOrderTraversal();
}
// 2、递归右子树
if (this.rightElement != null) {
this.rightElement.postOrderTraversal();
}
// 3、输出父节点
System.out.println(this);
}
}
/**
* 二叉树节点
*
* @param <E>
*/
public class BinaryTree<E> {
private Element<E> root;
public void setRoot(Element<E> root) {
this.root = root;
}
// ================================================================================//
// ===operate
// ================================================================================//
// 前序遍历
public void preOrderTraversal() {
if (this.root != null) {
this.root.preOrderTraversal();
} else {
System.out.println("二叉树为空");
}
}
// 中序遍历
public void infixOrderTraversal() {
if (this.root != null) {
this.root.infixOrderTraversal();
} else {
System.out.println("二叉树为空");
}
}
// 后续遍历
public void postOrderTraversal() {
if (this.root != null) {
this.root.postOrderTraversal();
} else {
System.out.println("二叉树为空");
}
}
}
public class BinaryTreeDemo {
public static void main(String[] args) {
BinaryTree<String> binaryTree = new BinaryTree<String>();
// 手动创建二叉树
Element<String> element1 = new Element<String>("1");
Element<String> element2 = new Element<String>("2");
Element<String> element3 = new Element<String>("3");
Element<String> element4 = new Element<String>("4");
Element<String> element5 = new Element<String>("5");
Element<String> element6 = new Element<String>("6");
Element<String> element7 = new Element<String>("7");
// 1 的子节点:2 和 3
element1.setLeftElement(element2);
element1.setRightElement(element3);
// 2的子节点: 4和5
element2.setLeftElement(element4);
element2.setRightElement(element5);
// 3的子节点: 6和7
element3.setLeftElement(element6);
element3.setRightElement(element7);
binaryTree.setRoot(element1);
System.out.println("前序遍历:");
binaryTree.preOrderTraversal();
System.out.println("中序遍历:");
binaryTree.infixOrderTraversal();
System.out.println("后序遍历:");
binaryTree.postOrderTraversal();
}
}
案例:顺序存储二叉树
/**
* 顺序存储二叉树
*/
public class ArrayBinaryTree {
private int[] array;
public ArrayBinaryTree(int[] array) {
this.array = array;
}
public void preOrder() {
this.preOrder(0);
}
/**
* 前序遍历
*
* @param index 数组的下标
*/
public void preOrder(int index) {
// 数组为空
if (array == null || array.length <= 0) {
System.out.println("数组为空,不能按照前序遍历.");
return;
}
System.out.println(array[index]);
// 向左递归
if ((2 * index + 1) < array.length) {
preOrder(2 * index + 1);
}
// 向右递归
if ((2 * index + 2) < array.length) {
preOrder(2 * index + 2);
}
}
public void infixOrder() {
this.infixOrder(0);
}
/**
* 中序遍历
*
* @param index 数组的下标
*/
public void infixOrder(int index) {
// 数组为空
if (array == null || array.length <= 0) {
System.out.println("数组为空,不能按照前序遍历.");
return;
}
// 向左递归
if ((2 * index + 1) < array.length) {
infixOrder(2 * index + 1);
}
System.out.println(array[index]);
// 向右递归
if ((2 * index + 2) < array.length) {
infixOrder(2 * index + 2);
}
}
public void postOrder() {
this.postOrder(0);
}
/**
* 后序遍历
*
* @param index 数组的下标
*/
public void postOrder(int index) {
// 数组为空
if (array == null || array.length <= 0) {
System.out.println("数组为空,不能按照前序遍历.");
return;
}
// 向左递归
if ((2 * index + 1) < array.length) {
postOrder(2 * index + 1);
}
// 向右递归
if ((2 * index + 2) < array.length) {
postOrder(2 * index + 2);
}
System.out.println(array[index]);
}
}
public class ArrayBinaryTreeDemo01 {
public static void main(String[] args) {
int[] array = { 1, 2, 3, 4, 5, 6, 7 };
ArrayBinaryTree arrayBinaryTree = new ArrayBinaryTree(array);
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++");
arrayBinaryTree.preOrder(); // 1,2,3,4,3,6,7
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++");
arrayBinaryTree.infixOrder(); // 4,2,5,1,6,3,7
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++");
arrayBinaryTree.postOrder(); // 4,5,2,6,7,3,1
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++++++");
}
}
相关推荐
- linux实例之设置时区的方式有哪些
-
linux系统下的时间管理是一个复杂但精细的功能,而时区又是时间管理非常重要的一个辅助功能。时区解决了本地时间和UTC时间的差异,从而确保了linux系统下时间戳和时间的准确性和一致性。比如文件的时间...
- Linux set命令用法(linux cp命令的用法)
-
Linux中的set命令用于设置或显示系统环境变量。1.设置环境变量:-setVAR=value:设置环境变量VAR的值为value。-exportVAR:将已设置的环境变量VAR导出,使其...
- python环境怎么搭建?小白看完就会!简简单单
-
很多小伙伴安装了python不会搭建环境,看完这个你就会了Python可应用于多平台包括Linux和MacOSX。你可以通过终端窗口输入"python"命令来查看本地是否...
- Linux环境下如何设置多个交叉编译工具链?
-
常见的Linux操作系统都可以通过包管理器安装交叉编译工具链,比如Ubuntu环境下使用如下命令安装gcc交叉编译器:sudoapt-getinstallgcc-arm-linux-gnueab...
- JMeter环境变量配置技巧与注意事项
-
通过给JMeter配置环境变量,可以快捷的打开JMeter:打开终端。执行jmeter。配置环境变量的方法如下。Mac和Linux系统在~/.bashrc中加如下内容:export...
- C/C++|头文件、源文件分开写的源起及作用
-
1C/C++编译模式通常,在一个C++程序中,只包含两类文件——.cpp文件和.h文件。其中,.cpp文件被称作C++源文件,里面放的都是C++的源代码;而.h文件则被称...
- linux中内部变量,环境变量,用户变量的区别
-
unixshell的变量分类在Shell中有三种变量:内部变量,环境变量,用户变量。内部变量:系统提供,不用定义,不能修改环境变量:系统提供,不用定义,可以修改,可以利用export将用户变量转为环...
- 在Linux中输入一行命令后究竟发生了什么?
-
Linux,这个开源的操作系统巨人,以其强大的命令行界面而闻名。无论你是初学者还是经验丰富的系统管理员,理解在Linux终端输入一条命令并按下回车后发生的事情,都是掌握Linux核心的关键。从表面上看...
- Nodejs安装、配置与快速入门(node. js安装)
-
Nodejs是现代JavaScript语言产生革命性变化的一个主要框架,它使得JavaScript从一门浏览器语言成为可以在服务器端运行、开发各种各样应用的通用语言。在不同的平台下,Nodejs的安装...
- Ollama使用指南【超全版】(olaplex使用方法图解)
-
一、Ollama快速入门Ollama是一个用于在本地运行大型语言模型的工具,下面将介绍如何在不同操作系统上安装和使用Ollama。官网:https://ollama.comGithub:http...
- linux移植(linux移植lvgl)
-
1uboot移植l移植linux之前需要先移植一个bootlader代码,主要用于启动linux内核,lLinux系统包括u-boot、内核、根文件系统(rootfs)l引导程序的主要作用将...
- Linux日常小技巧参数优化(linux参数调优)
-
Linux系统参数优化可以让系统更加稳定、高效、安全,提高系统的性能和使用体验。下面列出一些常见的Linux系统参数优化示例,包括修改默认配置、网络等多方面。1.修改默认配置1.1修改默认编辑器默...
- Linux系统编程—条件变量(linux 条件变量开销)
-
条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等...
- 面试题-Linux系统优化进阶学习(linux系统的优化)
-
一.基础必备优化:1.关闭SElinux2.FirewalldCenetOS7Iptables(C6)安全组(阿里云)3.网络管理服务||NetworkManager|network...
- 嵌入式Linux开发教程:Linux Shell
-
本章重点介绍Linux的常用操作和命令。在介绍命令之前,先对Linux的Shell进行了简单介绍,然后按照大多数用户的使用习惯,对各种操作和相关命令进行了分类介绍。对相关命令的介绍都力求通俗易懂,都给...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)