构建哈夫曼树
package du;
import java.util.ArrayList;
import java.util.Collections;
public class m17 {
/*
哈夫曼树概述
也叫赫夫曼树
如果树的带权路径长度(wpl)最小
那么就是最优二叉树
也叫哈夫曼树
权值较大的节点离根最近
权就是该节点对应的值
相关概念
路径
一个节点往下可以达到子节点或孙子节点的通路
路径长度
通路中的分支数目叫做路径长度
如果根节点层数为1
那么到达L层路径长度为L-1
说通俗点就是线的个数
带权路径长度
路径长度与该节点权的乘积
树的带权路径长度
所有叶子节点带权路径长度之和
wpl最小就是哈夫曼树
*/
/*
哈夫曼树构建步骤
先上题
给定数列{13,7,8,3,29,6,1}
要求转换成哈夫曼树
这个数列的每一个值最终都会成为叶子节点
构建哈夫曼树的步骤
* 从小到大排序,每个数据看作一个节点
每个节点看作最简单的二叉树,也就是左右指针都为空的
* 取出根节点权值最小的2个二叉树
* 2个节点作为左子节点和右子节点,构建一棵新的二叉树,
新二叉树权值就是两个子节点权值的和
* 然后这棵新二叉树的权值将替换掉原来前两个位置的权值
继续进行从小到大排序构建树的步骤
*/
public static void main(String[] args) {
int[] array = {13, 7, 8, 3, 29, 6, 1};
Node root = createHuffmanTree(array);
preOrderTraversal(root);
}
/**
* 构建哈夫曼树的方法
* @param array 构建树需要的数组
* @return 哈夫曼树的根节点
*/
private static Node createHuffmanTree(int[] array) {
/*
程序开始运行
遍历数组
将数组中每一个元素构建成Node
再将Node放入到ArrayList中
便于管理
并进行排序
*/
ArrayList<Node> arrayList = new ArrayList<>();
for (int i : array) {
arrayList.add(new Node(i));
}
while (arrayList.size() > 1) {
/*
程序进入while循环
此时数组长度是2及以上
可以构建新的二叉树
*/
Collections.sort(arrayList);
/*
程序运行到此处
可以进行代码测试
判断是否已经排好序
测试通过
可以进行第2步
取出权值最小的两个节点
构建二叉树
*/
Node node1 = arrayList.get(0);
Node node2 = arrayList.get(1);
Node node = new Node(node1.weighting + node2.weighting);
node.leftNode = node1;
node.rightNode = node2;
/*
程序运行到此处
说明新二叉树已经构架好了
需要删除已经处理过的二叉树
再把新构建的树加入ArrayList集合
此时新构建的节点放在了最后一位
需要用while循环处理
*/
arrayList.remove(node1);
arrayList.remove(node2);
arrayList.add(node);
/*
程序运行到此处
可以进行代码测试
测试第1处理是否成功
*/
}
/*
程序运行到此处
哈夫曼树构建完成
返回根节点
*/
return arrayList.get(0);
}
/**
* 遍历哈夫曼树
* @param root 树的根节点
*/
public static void preOrderTraversal(Node root) {
if (root != null) {
root.preOrderTraversal();
}else {
throw new Error("Empty Huffman Tree");
}
}
}
/**
* @author main stack frame
* <p>
* Node class
* Node类需要支持排序
* 实现Comparable接口
*/
class Node implements Comparable<Node> {
/*
节点权值
左子节点
右子节点
*/
int weighting;
Node leftNode;
Node rightNode;
public Node(int weighting) {
this.weighting = weighting;
}
/**
* 前序遍历
*/
public void preOrderTraversal() {
System.out.println(this);
if (this.leftNode != null) {
this.leftNode.preOrderTraversal();
}
if (this.rightNode != null) {
this.rightNode.preOrderTraversal();
}
}
@Override
public String toString() {
return "Node{" + "weighting=" + weighting + '}';
}
@Override
public int compareTo(Node o) {
/*
从小到大进行排序
*/
return this.weighting - o.weighting;
}
}
二叉排序树
class Test21 {
/*
给你一个数列 {7,3,10,12,5,1,9}
要求能够高效完成数据的查询和添加
二叉排序树(BST)可以让检索插入删除效率较高
对于二叉排序树的所有非叶子节点
要求左子树节点值比当前值小
右子节点比当前节点值大
检索效率较高
类似于二分法查找
插入元素不会导致元素的移动
所以插入速度也是非常快的
*/
public static void main(String[] args) {
int[] array = {7,3,10,12,5,1,9};
BinarySortedTree binarySortedTree = new BinarySortedTree();
for (int i = 0; i < array.length; i++) {
binarySortedTree.insertNode(new Node1(array[i]));
}
binarySortedTree.middleOrderTraversal();
/*
此处运行中序遍历的方法
正好是升序排列的数列
*/
}
}
class BinarySortedTree {
private Node1 root;
/**
* 插入元素的方法
* @param node1 需要插入的元素
*/
public void insertNode(Node1 node1){
if (root == null) {
root = node1;
}else {
root.insertNode(node1);
}
}
/**
* 中序遍历的方法
*/
public void middleOrderTraversal(){
if (root != null) {
root.middleOrderTraversal();
}else {
throw new Error("Empty Binary Sorted Tree");
}
}
}
/*
节点类
*/
class Node1 {
int value;
Node1 leftNode;
Node1 rightNode;
public Node1(int value) {
this.value = value;
}
/**
* 插入节点的方法
* @param node 需要插入的节点
*/
public void insertNode(Node1 node) {
if (node == null) {
return;
}
/*
程序运行到此处
此时可以判断插入的节点和根节点的关系
this.value表示根节点的值
*/
if (node.value < this.value) {
if (this.leftNode == null) {
/*
程序运行到此处
说明当前插入的节点比根节点的值小
并且根节点的左子树位空
直接插入即可
*/
this.leftNode = node;
}else {
this.leftNode.insertNode(node);
}
}else {
/*
程序运行到此处
说明当前节点比根节点大
需要插入到右子树
*/
if (this.rightNode == null) {
this.rightNode = node;
}else {
this.rightNode.insertNode(node);
}
}
}
/**
* 中序遍历的方法
*/
public void middleOrderTraversal(){
if (this.leftNode != null) {
this.leftNode.middleOrderTraversal();
}
System.out.println(this);
if (this.rightNode != null) {
this.rightNode.middleOrderTraversal();
}
}
@Override
public String toString() {
return "Node1{ value= " + value +" }";
}
}
二叉树删除节点思路分析
/*
二叉排序树删除节点的3种情况
第1种情况:删除叶子节点
先找到需要删除的节点targetNode
再找到需要删除节点的父节点
确定targetNode是parent的左子节点还是右子节点
然后根据情况来删除就行了
parent.leftNode/rightNode = null;
第2种情况:删除只有1棵子树的节点
前两步和第1种情况一样
多加一个步骤是判断子树是左/右子节点
再多加一个步骤是判断targetNode是parentNode
的左/右字节点
目前来看有4种情况
parent.leftNode = targetNode.leftNode
parent.leftNode = targetNode.rightNode
parent.rightNode = targetNode.leftNode
parent.rightNode = targetNode.rightNode
第3种情况:删除有2棵子树的节点
前两步和第1种情况一样
找到targetNode右子树中最小的节点
用一个临时变量保存temp = 12
再删除12这个节点
然后再
targetNode.value = temp
*/
删除叶子节点代码实现
// 下面2个方法插入到Node类里面
/**
* 查找需要删除的节点
* @param value 需要删除节点的值
* @return 需要删除的节点
*/
public Node1 findNodeToBeDelete(int value){
if (value == this.value) {
return this;
}else if (value < this.value) {
/*
程序运行到此处
说明当前需要查找的值
小于当前节点(第1次一定是根节点)
需要向左子树遍历
同时需要判断左子节点是否为空
*/
if (this.leftNode == null) {
return null;
}
this.leftNode.findNodeToBeDelete(value);
}else {
/*
程序运行到此处
说明当前需要查找的值
大于当前节点(第1次一定是根节点)
需要向右子树遍历
同时需要判断右子节点是否为空
*/
if (this.rightNode == null) {
return null;
}
this.rightNode.findNodeToBeDelete(value);
}
return null;
}
/**
* 查找需要删除节点的父节点
* @param value 需要删除节点
* @return 需要删除节点的父节点
*/
public Node1 parentOfNodeToBeDelete(int value){
if ((this.leftNode != null && this.leftNode.value == value) ||
(this.rightNode != null && this.rightNode.value == value)) {
/*
程序运行到此处
说明找到了需要删除节点的父节点
直接返回即可
*/
return this;
}else {
if (value < this.value && this.leftNode != null) {
/*
程序运行到此处
查找的节点小于当前节点
需要向左递归查找
*/
return this.leftNode.parentOfNodeToBeDelete(value);
} else if (value >= this.value && this.rightNode != null) {
/*
程序运行到此处
查找的节点大于等于当前节点
需要向左递归查找
加等号的原因是
在添加方法的时候
如果插入元素等于当前元素
是放在右子树的
*/
return this.rightNode.parentOfNodeToBeDelete(value);
}else {
/*
程序运行到此处
说明当前递归程序左右子树有可能为空
不满足需要递归的条件
*/
return null;
}
}
}
// 下面3个方法插入到BinarySortedTree类里面
/**
* 查找需要删除的节点
* @param value
* @return
*/
public Node1 findNodeToBeDelete(int value){
if (root == null) {
return null;
}else {
return root.findNodeToBeDelete(value);
}
}
/**
* 查找需要删除节点的父节点
* @param value
* @return
*/
public Node1 parentOfNodeToBeDelete(int value) {
if (root == null) {
return null;
}else {
return root.parentOfNodeToBeDelete(value);
}
}
/**
* 删除叶子节点的方法
* @param value 需要删除节点的值
*/
public void deleteNode(int value){
if (root == null) {
return;
}else {
Node1 targetNode = findNodeToBeDelete(value);
/*
程序开始运行
首先查找要删除的节点和需要删除节点的父节点
做两层安全控制
*/
if (targetNode == null) {
return;
}
if (root.leftNode == null && root.rightNode == null) {
root = null;
return;
}
Node1 parentOftNode = parentOfNodeToBeDelete(value);
if (targetNode.leftNode == null && targetNode.rightNode == null) {
/*
程序能够进入if语句当中
说明当前需要删除的节点是叶子节点
此时需要判断targetNode是
父元素的左子节点还是右子节点
*/
if (parentOftNode.leftNode != null
&& parentOftNode.leftNode.value == value) {
parentOftNode.leftNode = null;
} else if (parentOftNode.rightNode != null
&& parentOftNode.rightNode.value == value) {
parentOftNode.rightNode = null;
}
}
}
}
删除节点综合代码实现
/**
* 删除节点的方法
* @param value 需要删除节点的值
*/
public void deleteNode(int value){
if (root == null) {
return;
}else {
Node1 targetNode = findNodeToBeDelete(value);
/*
程序开始运行
首先查找要删除的节点和需要删除节点的父节点
做两层安全控制
*/
if (targetNode == null) {
return;
}
if (root.leftNode == null && root.rightNode == null) {
root = null;
return;
}
Node1 parentOftNode = parentOfNodeToBeDelete(value);
if (targetNode.leftNode == null && targetNode.rightNode == null) {
/*
程序能够进入if语句当中
说明当前需要删除的节点是叶子节点
此时需要判断targetNode是
父元素的左子节点还是右子节点
*/
if (parentOftNode.leftNode != null
&& parentOftNode.leftNode.value == value) {
parentOftNode.leftNode = null;
} else if (parentOftNode.rightNode != null
&& parentOftNode.rightNode.value == value) {
parentOftNode.rightNode = null;
}
} else if (targetNode.leftNode != null && targetNode.rightNode != null) {
/*
程序运行到此处
说明该节点有2棵子树
*/
targetNode.value = deleteMinimumValueOfRightSubtree(targetNode.rightNode);
} else {
/*
程序运行到此处
经过排除
该节点只有1棵子树
注意需要判空操作
注意一共有4种情况
*/
if (targetNode.leftNode != null) {
/*
程序运行到此处
说明当前节点有1棵左子树
*/
if (parentOftNode.leftNode.value == value) {
/*
程序运行到此处
说明当前节点是父节点的
左子节点
*/
parentOftNode.leftNode = targetNode.leftNode;
}else {
/*
程序运行到此处
说明当前节点是父节点的
右子节点
*/
parentOftNode.rightNode = targetNode.leftNode;
}
}else {
/*
程序运行到此处
说明当前节点有1棵右子树
*/
if (parentOftNode.leftNode.value == value) {
parentOftNode.leftNode = targetNode.rightNode;
}else {
parentOftNode.rightNode = targetNode.rightNode;
}
}
}
}
}
/**
* 辅助方法
* 删除右子树的最小值
* @param node 传入的根节点
* @return 以node作为根节点二叉排序树最小值
*/
public int deleteMinimumValueOfRightSubtree(Node1 node) {
Node1 target = node;
/*
程序运行到此处
此时会根据二叉排序树左小右大的原则
一直向左子树方向查找
最左下角位置的值为最小值
这里也可以删除左子树最大的
*/
while (target.leftNode != null) {
target = target.leftNode;
}
/*
程序运行到此处
此时target指向最小节点
*/
deleteNode(target.value);
return target.value;
}
B树,B+树和B*树
/*
二叉树问题分析
问题1:在构建二叉树时,如果数据量巨大,需要进行多次IO操作
问题2:海量节点,会造成树的高度很大,降低操作速度
B树介绍
实际上是1种多叉树
B树通过重新组织节点,降低树的高度,减少IO操作,提高效率
2-3树
所有叶子节点都在同一层,所有B树都满足这个条件
有2个子节点的节点叫做2节点,2节点要么没有子树,要么有2个子节点
有3个子节点的节点叫做3节点,3节点要么没有子树,要么有3个子节点
3节点左边的值小于当前节点左边的数据项,中间的值介于2个数据项之间,右边的值大于2个数据项
2-3树要比二叉树效率要高
代码实现非常复杂
2-3-4树
和2-3树相似
允许4节点的存在
这里就不具体说明了
B树
B树的阶:节点最多子节点的个数,2-3树的阶为3,2-3-4树的阶为4
B树的搜索:从根节点开始,进行n分查找,命中则结束,否则进入所属范围的儿子节点,直到所指向的儿子节点为空
B+树
所有真实数据全部在叶子节点,也叫做稠密索引
叶子节点实际上是1串1串的链表,并且链表之间是有序的
不可能在非叶子节点命中,非叶子节点相当于叶子节点的索引,也叫做稀疏索引
更加适合文件索引系统
类似于哈希表
但相对于哈希表而言是有序的
相对于B树砍掉的数据块更多
B*树
在B+树的基础上
非叶子节点添加1个指针指向兄弟节点
*/