数据结构中的树与实现
树的概念
-
树是一种非线性数据结构
-
树的定义:树是 n 个结点的有限集合,有且仅有一个根结点,其余结点可分为 m 个根结点的子树
-
树的相关概念
- 结点的度:一个结点拥有子树的个数称为度。比如A的度为3,C的度为2,H的度为0.度为0的结点称为叶子结点(D,F,G,H)。树的度是树中所有结点的度的最大值,此树的度为3
- 树中结点的最大层次称为树的深度或高度,此树的深度为4
- 父节点A的子节点是B,C,D;B,C,D 是兄弟结点
- 树的集合称为森林,树和森林之间有着密切的关系,删除一个树的根结点,其所有原来的子树都是树,构成森林用。用一个结点连接到森林的所有树的根结点就构成树
-
特点
i. 每个结点有0 个或多个子结点
ii.没有父结点的结点称为根结点
iii.每一个非根结点有且只有一个父结点
iiii.除了根结点外,每个子结点可以分为多个不相交的子树
二叉树
二叉树的特点
- 每个结点最多有两颗子树,结点的度最大为2
- 左子树和右子树是有序的,次序不能颠倒
- 即使某结点只有一个子树,也要区分左右子树
二叉树是一种比较有用的折中方案,它添加、删除元素都很快,并且在查找方面也有很多的算法优化,所以二叉树既有链表的好处,也有数组的好处,是两者的优化方案,在处理大批量的动态数据方面非常有用
扩展
二叉树有很多扩展的数据结构,包括平衡二叉树、红黑树、B+树等,这些数据结构在二叉树的基础上衍生了很多的功能,在实际应用中广泛用到。例如mysql的数据库索引结构用到的就是 B+树,还有 HashMap的底层源码中用到了红黑树。
代码实现(包含3种遍历方式及相关常用方法)
/**
* TODO
* 二叉树
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class BianryTree {
TreeNode root;
//设置根节点
public void setRoot(TreeNode root){
this.root = root;
}
//获取根节点
public TreeNode getRoot(){
return root;
}
//前序遍历
public void frontShow(){
if(root != null){
root.frontShow();
}
}
public void midShow(){
if(root != null){
root.midShow();
}
}
public void afterShow(){
if(root != null){
root.afterShow();
}
}
//查找节点信息
public TreeNode frontSearch(int i){
return root.frontSearch(i);
}
//删除结点
public void delete(int i){
root.delete(i);
}
}
/**
* TODO
* 二叉树
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class TreeNode {
//节点权值
int value;
//左子节点
TreeNode leftNode;
//右子节点
TreeNode rightNode;
public TreeNode(int value){
this.value = value;
}
//设置左儿子
public void setLeftNode(TreeNode leftNode){
this.leftNode = leftNode;
}
//设置右儿子
public void setRightNode(TreeNode rightNode){
this.rightNode = rightNode;
}
//各种遍历方法的实现
public void frontShow(){
//先遍历当前节点的内容
System.out.println(value);
//左节点
if(leftNode != null){
leftNode.frontShow();
}
//右节点
if(rightNode != null){
rightNode.frontShow();
}
}
public void midShow(){
if(leftNode != null){
leftNode.midShow();
}
System.out.println(value);
if(rightNode != null){
rightNode.midShow();
}
}
public void afterShow(){
if(leftNode != null){
leftNode.afterShow();
}
if(rightNode != null){
rightNode.afterShow();
}
System.out.println(value);
}
//前序遍历查找节点
public TreeNode frontSearch(int i){
TreeNode target = null;
//对比当前节点的值
if(this.value == i){
return this;
}else {
//查找左儿子
if(leftNode != null){
//有可能可以找到,也可能找不到
//找不到的话 target 还是 null
target = leftNode.frontSearch(i);
}
//如果不为空,说明在左儿子中已经找到
if(target != null){
return target;
}
//查找右儿子
if(rightNode != null){
target = rightNode.frontSearch(i);
}
}
return target;
}
//删除节点
public void delete(int i){
TreeNode parent = this;
//判断左儿子
if(parent.leftNode!=null
&& parent.leftNode.value == i){
parent.leftNode = null;
return;
}
//判断右儿子
if(parent.rightNode != null
&& parent.rightNode.value == i){
parent.rightNode = null;
return;
}
//递归检查并删除左儿子
parent = leftNode;
if(parent != null){
parent.delete(i);
}
//递归检查并删除右儿子
parent = rightNode;
if(parent != null){
parent.delete(i);
}
}
@Override
public String toString() {
return "TreeNode{" +
"value=" + value +
'}';
}
}
/**
* TODO
* 二叉树
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class TestBianryTree {
public static void main(String[] args) {
//创建一颗树
BianryTree bianryTree = new BianryTree();
//创建一个根节点
TreeNode root = new TreeNode(1);
//把根节点赋给树
bianryTree.setRoot(root);
//创建一个左节点
TreeNode rootL = new TreeNode(2);
//把新创建的结点设置为根节点的子节点
root.setLeftNode(rootL);
//创建一个右节点
TreeNode rootR = new TreeNode(3);
//把新创建的节点设置为根节点的子节点
root.setRightNode(rootR);
//下面为根节点的子节点再添加子节点,方便遍历
TreeNode rootLL = new TreeNode(4);
rootL.setLeftNode(rootLL);
TreeNode rootLR = new TreeNode(5);
rootL.setRightNode(rootLR);
TreeNode rootRL = new TreeNode(6);
rootR.setLeftNode(rootRL);
TreeNode rootRR = new TreeNode(7);
rootR.setRightNode(rootRR);
//前序遍历树
bianryTree.frontShow();
System.out.println("================");
TreeNode result = bianryTree.frontSearch(6);
System.out.println("找到节点信息为:"+result);
System.out.println("下面删除该结点");
bianryTree.delete(6);
bianryTree.frontShow();
}
}
满二叉树
高度为 h ,由 2^h - 1个结点构成的二叉树称为满二叉树
完全二叉树
完全二叉树是由满二叉树而引出来的,若设二叉树的深度为 h,除第 h 层外,其他各层(1到h-1)的结点数都达到最大个数(即1到h-1层为一个满二叉树),第h层所有的结点都连续集中在最左边,这既是完全二叉树。(所有叶子结点都在最后一层或倒数第二层,且最后一层的叶子结点在左边连续,倒数第二层的叶子结点在右边连续)
堆一般都是用完全二叉树来实现。
二叉树的应用中,常要求在树中查找具有某种特征的节点,或对树中全部节点进行某种处理,此时涉及二叉树的遍历。
3个基本单元:根节点,左子树,右子树
先序遍历:先访问根节点,再先序遍历左子树,最后遍历右子树。(根-左-右)
中序遍历:先中序遍历左子树,再访问根节点,最后中序遍历右子树。(左-根-右)
后序遍历:先后序遍历左子树,在后续遍历右子树,最后访问根节点。(左-右-根)
层序遍历:所有深度为d的节点要在深度d+1的节点之前执行;用到队列、属于广度优先。
- 树与二叉树的区别
二叉树每个节点最多有2个节点,树则无限制,二叉树的平均深度为O(sqrt(N));
二叉树中子树分为左子树和右子树,二叉树是有序的(即使某节点只有一棵子树也要指明是左/右);
树决不能为空,至少有一个节点,但二叉树可以为空。
顺序二叉树
顺序二叉树存储的二叉树在用数组实现的时候只考虑 完全二叉树/满二叉树(因为当某个结点的左/右子结点为空时,用数组来存储可能会出现某个下标对应的数值为空的情况造成错误)
- 相关性质:
第 n 个结点的左/右子结点是 :2n + 1/2n + 2
第 n 个结点的父结点是: (n-1)/2
代码实现
/**
* TODO
* 顺序二叉树
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class ArrayBinaryTree {
int[] data;
public ArrayBinaryTree(int[] data){
this.data = data;
}
//无参 遍历顺序二叉树-前序遍历
public void frontShow(){
frontShow(0);
}
//前序遍历
public void frontShow(int index){
if(data == null || data.length == 0){
return;
}
//先遍历当前节点的内容
System.out.println(data[index]);
//2*index+1:处理左子树
if(2*index+1<data.length){
frontShow(2*index+1);
}
//2*index+2:处理右子树
if(2*index+2<data.length){
frontShow(2*index+2);
}
}
}
/**
* TODO
* 测试顺序二叉树的遍历
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class TestArrayBinaryTree {
public static void main(String[] args) {
int[] data = new int[]{1,2,3,4,5,6,7};
ArrayBinaryTree tree = new ArrayBinaryTree(data);
//前序遍历:1245367
tree.frontShow();
}
}
线索二叉树
为了解决“指针”的浪费——类似双向链表,以达到可以获取结点的前驱后继
如下图,5的右节点应该指向1,即需要额外标志来区别前后指向是子节点还是排序的顺序。
代码实现
/**
* TODO
* 线索二叉树
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class ThreadedBinaryTree {
ThreadedNode root;
//用于临时存储前驱节点
ThreadedNode pre = null;
//设置根节点
public void setRoot(ThreadedNode root){
this.root = root;
}
//遍历线索二叉树
public void threadIterate(){
//用于临时存储当前遍历节点
ThreadedNode node = root;
while(node != null){
//循环找到最开始的节点
while(node.leftType==0){
node = node.leftNode;
}
//打印当前节点的值
System.out.println(node.value);
//如果当前节点的右指针指向的是后继节点
//可能后继节点还有后继节点
while (node.rightType == 1){
node = node.rightNode;
System.out.println(node.value);
}
//替换遍历的节点
node = node.rightNode;
}
}
//中序遍历
public void midShow(){
if(root != null){
root.midShow();
}
}
//中序线索化二叉树-无参方法
public void threadNodes(){
threadNodes(root);
}
public void threadNodes(ThreadedNode node){
//当前节点如果为 null,直接返回
if(node == null){
return;
}
//处理左子树
threadNodes(node.leftNode);
//处理前驱节点
if(node.leftNode == null){
//让当前节点的左指针指向前驱节点
node.leftNode=pre;
//改变当前节点左指针的类型
node.leftType=1;
}
//处理前驱的右指针,如果前驱接地那的右指针是null
if(pre!=null&&pre.rightNode==null){
//让前驱节点的右指针指向当前节点
pre.rightNode = node;
//改变前驱节点的右指针类型
pre.rightType = 1;
}
//每处理一个节点,当前节点就是下一个节点的前驱节点
pre = node;
//处理右子树
threadNodes(node.rightNode);
}
}
/**
* TODO
* 线索二叉树
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class ThreadedNode {
//节点的权
int value;
//左右儿子
ThreadedNode leftNode;
ThreadedNode rightNode;
//标识指针类型:0-子树,1-前驱后继
int leftType;
int rightType;
public ThreadedNode(int value){
this.value = value;
}
public void midShow(){
if(leftNode != null){
leftNode.midShow();
}
System.out.println(value);
if(rightNode != null){
rightNode.midShow();
}
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public ThreadedNode getLeftNode() {
return leftNode;
}
public void setLeftNode(ThreadedNode leftNode) {
this.leftNode = leftNode;
}
public ThreadedNode getRightNode() {
return rightNode;
}
public void setRightNode(ThreadedNode rightNode) {
this.rightNode = rightNode;
}
public int getLeftType() {
return leftType;
}
public void setLeftType(int leftType) {
this.leftType = leftType;
}
public int getRightType() {
return rightType;
}
public void setRightType(int rightType) {
this.rightType = rightType;
}
}
/**
* TODO
* 线索二叉树测试
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class TestThreadedBinaryTree {
public static void main(String[] args) {
//创建一颗树
ThreadedBinaryTree binTree = new ThreadedBinaryTree();
//创建一个根节点
ThreadedNode root = new ThreadedNode(1);
//把根节点赋给树
binTree.setRoot(root);
//创建一个左节点
ThreadedNode rootL = new ThreadedNode(2);
//把新创建的结点设置为根节点的子节点
root.setLeftNode(rootL);
//创建一个右节点
ThreadedNode rootR = new ThreadedNode(3);
//把新创建的节点设置为根节点的子节点
root.setRightNode(rootR);
//为第二层的左节点创建两个子节点
rootL.setLeftNode(new ThreadedNode(4));
ThreadedNode fiveNode = new ThreadedNode(5);
rootL.setRightNode(fiveNode);
//为第二层的右节点创建两个子节点
rootR.setLeftNode(new ThreadedNode(6));
rootR.setRightNode(new ThreadedNode(7));
//中序遍历树
binTree.midShow();
System.out.println("====线索化中====");
//中序线索化二叉树
binTree.threadNodes();
ThreadedNode afterNode = fiveNode.rightNode;
System.out.println(afterNode.value);
System.out.println("开始遍历线索二叉树");
binTree.threadIterate();
}
}
线索二叉树的遍历不需要递归
赫夫曼树
-
最优二叉树。它是n个带权叶子结点构成的所有二叉树中,带权路径长度最小的二叉树。
-
叶节点的带权路径:从根结点到叶子结点经过的结点数 * 叶子结点的权值
-
树的带权路径长度 WPL:树中所有叶子结点的带权路径长度之和
WPL(a)=9x2+4x2+5x2+2x2=40
WPL(b)=9x1+5x2+4x3+2x3=37
WPL©=4x1+2x2+5x3+9x3=50 -
权值越大的结点离根结点越近的二叉树才是最优二叉树
代码实现
/**
* TODO
* 赫夫曼树节点类
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class Node implements Comparable<Node>{
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
@Override
public int compareTo(Node o) {
//降序
return -(this.value - o.value);
//return this.value - o.value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
}
/**
* TODO
* 创建赫夫曼树测试类
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class TestHuffmanTree {
public static void main(String[] args) {
int[] arr = new int[]{3,5,7,8,11,14,23,29};
Node node = createHuffmanTree(arr);
System.out.println(node);
}
//创建赫夫曼树
public static Node createHuffmanTree(int[] arr){
//先使用数组中所有的元素创建若干个二叉树(只有一个节点)
List<Node> nodes = new ArrayList<>();
for(int value:arr){
nodes.add(new Node(value));
}
//循环处理
while(nodes.size() > 1){
// 排序
Collections.sort(nodes);
//取出权值最小的两个二叉树
//取出权值最小的二叉树,因为是降序排序故在最后面
Node left = nodes.get(nodes.size()-1);
//取出权值第二小的二叉树
Node right = nodes.get(nodes.size()-2);
//创建一颗新的二叉树
Node parent = new Node(left.value + right.value);
//把取出来的两颗二叉树移除
nodes.remove(left);
nodes.remove(right);
// 放入原来的二叉树集合
nodes.add(parent);
}
//System.out.println(nodes.get(0).value);
return nodes.get(0);
}
}
二叉查找树/二叉排序树/二叉搜索树
定义
-
若左子树不为空,则左子树上所有结点的值均小于他的根结点的值
-
若右子树不为空,则右子树上所有结点的值均大于他的根结点的值
-
左右子树也分别为二叉排序树
-
没有键值相等的结点
-
性质
二叉查找树的平均深度为O(log(N)) -
二叉查找树的构建
-
性能分析
当给定值相同但顺序不同时,所构建的二叉查找树形态是不同的
-
不同形态二叉查找树的ASL(平均查找长度)不同
i.含有n个结点的二叉查找树的平均查找长度和树的形态有关
ii.最坏的情况:当先后插入的关键字有序时,构成的二叉查找树蜕变成单支树,树的深度为n,其平均查找长度为(n+1)/2:
iii.最好的情况:二叉查找树的形态和折半查找的判定树相同,其 ASL和 log2n成正比
iiii.平均情况:二叉查找树的 ASL 和 logn 等数量级。
为获得更好的性能,二叉查找树构建过程需要“平衡化处理”,使查找树的高度为 O(log(n))
二叉查找树删除结点分析
首先需要定位包含该元素的结点,以及他的父结点:
若无子结点,即叶子结点可以直接删除;
若有子节点,则考虑两种情况:
- 需要删除的结点下有一个子结点(左/右),那么只需要将 父结点 和 当前节点 的子节点相连(让当前节点的子节点成为父结点的新右子结点——保证根结点的值永远小于右边的值)
- 需要删除的结点下有两个子结点。首先需要的要删除的结点的右子树中
i. 找到一个最小的值(因为右子树中的结点值一定大于根结点)
ii. 然后用找到的“最小值”与要需要删除的结点的值替
iii. 最后将“最小值”的原结点进行删除
(简单来说就是保证 结点 永远比左边大,比右边小)
代码实现
/**
* TODO
* 二叉排序树
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class BinarySoreTree {
Node root;
/**
* 删除节点
* 三种情况:
* 1.删除叶子节点
* 2.删除有一个子节点的节点
* 3.删除有两个子节点的节点
*/
public void delete(int value){
if(root == null){
return;
}else{
//找到该节点
Node target = search(value);
//若找不到该节点
if(target == null){
return;
}
//找到该节点的父节点
Node parent = searchParent(value);
//1.要删除的结点是叶子节点
if(target.left==null && target.right==null){
//要删除的结点是左子节点
if(parent.left.value==value){
parent.left = null;
}else {
parent.right = null;
}
//删除有两个子节点的节点
}else if(target.left!=null && target.right!=null){
//删除右子树中值最小的节点,获取到该节点的值
int min = deleteMin(target.right);
//替换目标结点的值
target.value = min;
//删除有一个子节点的节点
}else{
//有左子节点
if(target.left!=null){
//要删除的节点是父节点的左子节点
if(parent.left.value == value){
//将目标节点的子节点赋给目标节点父节点作为新节点
parent.left = target.left;
//要删除的节点是父节点的右子节点
}else {
parent.right = target.left;
}
//有右子节点
}else {
//要删除的节点是父节点的左子节点
if(parent.left.value == value){
parent.left = target.right;
//要删除的节点是父节点的右子节点
}else {
parent.right = target.right;
}
}
}
}
}
/**
* 删除一棵树中最小节点
* @param node 实际要删除的目标结点的右子节点
* @return 返回 右子树 中的最小值
*/
private int deleteMin(Node node){
Node target = node;
//递归向左找
while(target.left != null){
target = target.left;
}
//删除最小的这个节点
delete(target.value);
return target.value;
}
/**
* 寻找父节点
*/
public Node searchParent(int value){
if(root == null){
return null;
}else{
return root.searchParent(value);
}
}
/**
* 向二叉排序树中添加节点
*/
public void add(Node node){
//如果是空树
if(root == null){
root=node;
}else {
root.add(node);
}
}
/**
* 查找节点
*/
public Node search(int value){
if(root == null){
return null;
}else{
return root.search(value);
}
}
/**
* 遍历树
*/
public void midShow(){
if(root != null){
root.midShow(root);
}
}
}
/**
* TODO
* 二叉排序树
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class Node {
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
public void midShow(Node node){
if(node == null){
return;
}
midShow(node.left);
System.out.println(node.value);
midShow(node.right);
}
/**
* 查找节点
*/
public Node search(int value){
if(this.value == value){
return this;
}else if(value < this.value){
if(left == null){
return null;
}
return left.search(value);
}else {
if(right == null){
return null;
}
return right.search(value);
}
}
/**
* 寻找节点的父节点
* @param value 要查找的节点
* @return 返回节点父节点
*/
public Node searchParent(int value){
if((this.left != null && this.left.value == value)
|| (this.right != null && this.right.value == value)){
return this;
}else{
if(this.value>value && this.left!=null){
return this.left.searchParent(value);
}else if(this.value<value && this.right!=null){
return this.right.searchParent(value);
}
return null;
}
}
/**
* 往子树中添加节点
* @param node
*/
public void add(Node node){
if(node == null){
return;
}
//判断传入的结点的值跟当前子树的节点的值大小关系
if(node.value<this.value){
//判断左节点是否为空
if(this.left == null){
this.left = node;
}else {
this.left.add(node);
}
}else{
if(this.right == null){
this.right = node;
}else{
this.right.add(node);
}
}
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
}
/**
* TODO
* 二叉排序树测试
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class test {
public static void main(String[] args) {
int[] arr = new int[]{7,3,10,12,5,1,9};
//创建一颗二叉排序树
BinarySoreTree bst = new BinarySoreTree();
//循环添加
for(int i:arr){
bst.add(new Node(i));
}
//中序遍历查看树
bst.midShow();
// System.out.println("查找中");
// Node t = bst.search(10);
// System.out.println(t.value);
// Node s = bst.search(15);
// System.out.println(s);
// System.out.println("======查找父节点=====");
// Node p = bst.searchParent(10);
// System.out.println(p);
// System.out.println("==========删除叶子节点==========");
// bst.delete(12);
// bst.midShow();
// System.out.println("-----");
// bst.delete(10);
// bst.midShow();
System.out.println("-=-=-=-=-=-=-");
bst.delete(7);
bst.midShow();
}
}
平衡二叉树/AVL(二叉排序树进化版)
平衡二叉树又称 AVL 树,他或者是一颗空树,或者是具有下列性质的二叉树:
- 左子树和右子树都是平衡二叉树
- 且左子树和右子树的深度之差的绝对值不超过1
AVL 树是最先发明的自平衡二叉查找树算法。在 AVL 中任何结点的两个儿子子树的高度之差为1,所以他也被称为高度平衡树。
特点:
- n 个结点的 AVL 树最大深度约 1.44log2 n
- 在高度为 h 的 AVL 树中,最少结点数 S(h) 由 S(h-1) + S(h-2)+1 给出
增加和删除可能需要通过一次或多次树旋转来重新平衡这个树
单旋转:
- 左子树高度比右子树高度高:右旋转
- 右子树高度比左子树高度高:左旋转
- 向矮的一方旋转
以右旋转举例(即左子树高度比右子树高度>=2)
- 创建一个新结点,其值等于当前节点的值
-
将当前结点的右子结点设置成新结点的右子结点
-
将当前节点的左子节点的右子结点设置为新结点的左子结点(如果左子结点无右子结点跳过)
-
将当前节点的值,设置成左子结点的值
-
将当前节点的左子节点设置(替换成)成左子节点的左子节点
原树剩下
- 将当前节点的右子结点设置成新结点
总的来说就是要保证每个结点的“排序性”
双旋转
- 较复杂。单旋转是左子树或者右子树整体高度比另一个子树高度>=2
- 而当 左右子树高度相差 >= 2 的同时内部 子树的左右子树也发生了高度差 >= 2 ,且与上一层子树相反(如上一层的子树中是左子树比右子树高度>=2,而在内层中的子树是右子树比左子树高度>=2)
- 此时旋转一次并不能解决问题,需要两次旋转
即:旋转的顺序要看最底层的左右子树的情况,根据高度来判断先进行哪种旋转。
代码实现
/**
* TODO
* 二叉平衡树
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class BinarySoreTree {
Node root;
/**
* 删除节点
* 三种情况:
* 1.删除叶子节点
* 2.删除有一个子节点的节点
* 3.删除有两个子节点的节点
*/
public void delete(int value){
if(root == null){
return;
}else{
//找到该节点
Node target = search(value);
//若找不到该节点
if(target == null){
return;
}
//找到该节点的父节点
Node parent = searchParent(value);
//1.要删除的结点是叶子节点
if(target.left==null && target.right==null){
//要删除的结点是左子节点
if(parent.left.value==value){
parent.left = null;
}else {
parent.right = null;
}
//删除有两个子节点的节点
}else if(target.left!=null && target.right!=null){
//删除右子树中值最小的节点,获取到该节点的值
int min = deleteMin(target.right);
//替换目标结点的值
target.value = min;
//删除有一个子节点的节点
}else{
//有左子节点
if(target.left!=null){
//要删除的节点是父节点的左子节点
if(parent.left.value == value){
//将目标节点的子节点赋给目标节点父节点作为新节点
parent.left = target.left;
//要删除的节点是父节点的右子节点
}else {
parent.right = target.left;
}
//有右子节点
}else {
//要删除的节点是父节点的左子节点
if(parent.left.value == value){
parent.left = target.right;
//要删除的节点是父节点的右子节点
}else {
parent.right = target.right;
}
}
}
}
}
/**
* 删除一棵树中最小节点
* @param node 实际要删除的目标结点的右子节点
* @return 返回 右子树 中的最小值
*/
private int deleteMin(Node node){
Node target = node;
//递归向左找
while(target.left != null){
target = target.left;
}
//删除最小的这个节点
delete(target.value);
return target.value;
}
/**
* 寻找父节点
*/
public Node searchParent(int value){
if(root == null){
return null;
}else{
return root.searchParent(value);
}
}
/**
* 向二叉排序树中添加节点
*/
public void add(Node node){
//如果是空树
if(root == null){
root=node;
}else {
root.add(node);
}
}
/**
* 查找节点
*/
public Node search(int value){
if(root == null){
return null;
}else{
return root.search(value);
}
}
/**
* 遍历树
*/
public void midShow(){
if(root != null){
root.midShow(root);
}
}
}
/**
* TODO
* 二叉平衡树
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class Node {
int value;
Node left;
Node right;
public Node(int value){
this.value = value;
}
public void midShow(Node node){
if(node == null){
return;
}
midShow(node.left);
System.out.println(node.value);
midShow(node.right);
}
/**
* 查找节点
*/
public Node search(int value){
if(this.value == value){
return this;
}else if(value < this.value){
if(left == null){
return null;
}
return left.search(value);
}else {
if(right == null){
return null;
}
return right.search(value);
}
}
/**
* 寻找节点的父节点
* @param value 要查找的节点
* @return 返回节点父节点
*/
public Node searchParent(int value){
if((this.left != null && this.left.value == value)
|| (this.right != null && this.right.value == value)){
return this;
}else{
if(this.value>value && this.left!=null){
return this.left.searchParent(value);
}else if(this.value<value && this.right!=null){
return this.right.searchParent(value);
}
return null;
}
}
/**
* 往子树中添加节点
* @param node
*/
public void add(Node node){
if(node == null){
return;
}
//判断传入的结点的值跟当前子树的节点的值大小关系
if(node.value<this.value){
//判断左节点是否为空
if(this.left == null){
this.left = node;
}else {
this.left.add(node);
}
}else{
if(this.right == null){
this.right = node;
}else{
this.right.add(node);
}
}
//检查是否平衡-判断左右子树高度差是否>1
//左长右短,右旋转
if(leftHeight() - rightHeight() >= 2){
//判断内层子树的高度情况
// 内部是否需要双旋转
// 即在整体左子树高度>右子树高度时,内部出现“左”<“右”
if(left!=null && left.leftHeight()<left.rightHeight()){
//子树先旋转
left.leftRotate();
//子树完成后再整体右旋转
rightRotate();
}else{
//默认单旋转-左长右短,右旋转
rightRotate();
}
}
//左短右长,左旋转
if(leftHeight() - rightHeight() <= -2){
//判断内层子树的高度情况
// 内部是否需要双旋转
// 即在整体左子树高度<右子树高度时,内部出现“左”>“右”
if(right!=null && right.rightHeight()<right.leftHeight()){
//子树先旋转
right.rightRotate();
//子树完成后再整体左旋转
leftRotate();
}else{
//默认单旋转-左短右长,左旋转
leftRotate();
}
}
}
/**
* 左旋转
*/
public void leftRotate(){
Node newLeft = new Node(value);
newLeft.left = left;
newLeft.right = right.left;
value = right.value;
right = right.right;
left = newLeft;
}
/**
* 右旋转
*/
private void rightRotate(){
//创建一个新的节点,值等于当前节点的值
Node newRight = new Node(value);
//把新节点的右子树设置成当前节点的右子树
newRight.right = right;
//把新节点的左子树设置成当前节点的左子树,的右子树
newRight.left = left.right;
//把当前节点的值设置成左子节点的值
value = left.value;
//把当前节点的左子树,设置成左子树的左子树(谋权篡位集体升级)
left = left.left;
//把当前节点的右子树设置为新节点
right = newRight;
}
/**
* 获取左子树的高度
* @return 左子树高度
*/
public int leftHeight(){
if(left == null){
return 0;
}
return left.height();
}
/**
* 获取右子树的高度
* @return 右子树高度
*/
public int rightHeight(){
if(right == null){
return 0;
}
return right.height();
}
/**
* 返回当前节点的高度
* @return 高度
*/
public int height(){
//取左右子树的高度较大的,加上本身 1 ,就是该节点所在子树的高度
return Math.max(left==null?0:left.height(),right==null?0:right.height())+1;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public Node getLeft() {
return left;
}
public void setLeft(Node left) {
this.left = left;
}
public Node getRight() {
return right;
}
public void setRight(Node right) {
this.right = right;
}
}
/**
* TODO
* 二叉平衡树测试
* @author Redamancy
* @version 1.0
* @since jdk 1.8
*/
public class test {
public static void main(String[] args) {
//用于构建二叉排序树的数据
// int[] arr = new int[]{7,3,10,12,5,1,9};
//用于右旋转测试的数据
// int[] arr = new int[]{8,9,6,7,5,4};
//用于左旋转测试的数据
// int[] arr = new int[]{2,1,4,3,5,6};
//用于双旋转测试的数据-先左后右
int[] arr = new int[]{8,9,5,4,6,7};
//创建一颗二叉排序树
BinarySoreTree bst = new BinarySoreTree();
//循环添加
for(int i:arr){
//添加元素的时候构建平衡二叉树
bst.add(new Node(i));
}
//中序遍历查看树
//bst.midShow();
// System.out.println("查找中");
// Node t = bst.search(10);
// System.out.println(t.value);
// Node s = bst.search(15);
// System.out.println(s);
// System.out.println("======查找父节点=====");
// Node p = bst.searchParent(10);
// System.out.println(p);
// System.out.println("==========删除叶子节点==========");
// bst.delete(12);
// bst.midShow();
// System.out.println("-----");
// bst.delete(10);
// bst.midShow();
// System.out.println("-=-=-=-=-=-=-");
// bst.delete(7);
//
//旋转测试
System.out.println(bst.root.height());
System.out.println(bst.root.value);
}
}
多路查找树- B树 & B+树
2-3 树
B 树所有叶子结点都在同一层
二结点:要么有两个子结点,要么没有子结点
三结点:要么有三个子结点,要么没有子结点
2-3-4 树
以此类推,节点要么有2,3,4个节点,要么没有
B树的阶:
2-3树是 3 阶的B树
2-3-4树是 4 阶的B树
B+ 树
B树的变形(实际例子:MySql索引)
- 非叶子结点只存储索引信息,不存储数据
- 叶子结点最右边的指针指向下一个相邻的叶子结点
- 所有的叶子结点形成了一个有序链表