视频链接:https://www.bilibili.com/video/BV1HQ4y1d7th
视频范围P121 - P167
1.哈夫曼树
- 给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)
- 哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
- 树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL
- 权值越大的结点距离根结点越近的二叉树才是最优二叉树
- WPL最小的就是哈夫曼树
结点类:
package huffman;
public class Node implements Comparable<Node>{
public int value;
public Node left;
public Node right;
public Node(int value) {
this.value = value;
}
//this和node之间比较
@Override
public int compareTo(Node node) {
return this.value - node.value;
}
//前序遍历
public void preSelect(){
System.out.println(this);
if (this.left != null){
this.left.preSelect();
}
if (this.right != null){
this.right.preSelect();
}
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
}
哈夫曼树:
package huffman;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class HuffmanTree {
public static void main(String[] args) {
int[] array = new int[]{13,7,8,29,6,1};
Node root = createHuffmanTree(array);
preSelect(root);
}
//把数列转换为哈夫曼树
public static Node createHuffmanTree(int[] array){
//1.遍历数组
//2.将每一个元素构成一个结点Node
//3.将Node放入到一个集合中并且已经是排序的,从小到大
List<Node> nodes = new ArrayList<Node>();
//将元素转为Node对象
for(int i : array){
nodes.add(new Node(i));
}
//排序从小到大
//取出最小的两个数 从集合 index:0, index:1
while (nodes.size() > 1){
//排序 从小到大
Collections.sort(nodes);
System.out.println("集合中的元素:" + nodes);
Node leftnode = nodes.get(0);
Node rightnode = nodes.get(1);
//创建新的结点
Node root = new Node(leftnode.value + rightnode.value);
root.left = leftnode;
root.right = rightnode;
//删除结点
nodes.remove(leftnode);
nodes.remove(rightnode);
//添加新结点
nodes.add(root);
}
return nodes.get(0);
}
//前序遍历
public static void preSelect(Node root){
if (root != null){
root.preSelect();
}else{
System.out.println("空树,无数据...");
}
}
}
哈夫曼树:
测试结果:
2.哈夫曼编码和编码解压
哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)
结点类:
package huffman;
public class NodeCode implements Comparable<NodeCode>{
//用来描述文字(字母)十进制数
public Byte data;
public int weight;
public NodeCode left;
public NodeCode right;
public NodeCode(Byte data, int weight) {
this.data = data;
this.weight = weight;
}
public NodeCode(int weight) {
this.weight = weight;
}
@Override
public int compareTo(NodeCode o) {
return this.weight - o.weight;
}
@Override
public String toString() {
return "NodeCode{" +
"data=" + data +
", weight=" + weight +
'}';
}
//前序遍历
public void preSelect(){
System.out.println(this);
if (this.left != null){
this.left.preSelect();
}
if (this.right != null){
this.right.preSelect();
}
}
}
哈夫曼代码:
package huffman;
import java.util.*;
public class HuffmanCodes {
//1.需要统计字母所出现的次数
//2.创建哈夫曼树
//3.获取哈夫曼编码
//4.数据压缩
//存储每一个叶子结点的路径
private static Map<Byte,String> huffmanCode = new HashMap<>();
//存储拼接哈夫曼编码的
private static StringBuilder stringBuilder = new StringBuilder();
public static void main(String[] args) {
String string = "Im liujie laizi sichuangguangyuanwhereareyou";
//原始数据 获取ASCII码值
byte[] bytes = string.getBytes();
List<NodeCode> nodeWeight = getNodeWeight(bytes);
//System.out.println(nodeWeight);
NodeCode root = createHuffmanTree(nodeWeight);
Map<Byte, String> huffmanCodes = createHuffmanCodes(root);
System.out.println("生成的哈夫曼编码:" + huffmanCodes);
//压缩后的数据
byte[] zip = zip(bytes, huffmanCodes);
System.out.println("压缩后的数组:" + Arrays.toString(zip));
float p = ((float) bytes.length - (float)zip.length) / (float)bytes.length;
System.out.println(p);
String string1 = byteToString(false, (byte) -1);
System.out.println(string1);
System.out.println("=========================");
byte[] decode = decode(huffmanCode,zip);
System.out.println(new String(decode));
}
//统计字符串所出现的次数
//返回集合 [NodeCode{data = 32,weight = 4}]
public static List<NodeCode> getNodeWeight(byte[] bytes){
List<NodeCode> nodeCodes = new ArrayList();
//每一个byte出现的次数,使用一个map集合, key = byte,value = weight
Map<Byte,Integer> counts = new HashMap<>();
for (byte aByte : bytes){
Integer count = counts.get(aByte);
if (count == null){
counts.put(aByte,1);
}else {
counts.put(aByte,count + 1);
}
}
//把每一个map对象转Node对象,存放在集合中
for (Map.Entry<Byte,Integer> entry : counts.entrySet()){
nodeCodes.add(new NodeCode(entry.getKey(),entry.getValue()));
}
return nodeCodes;
}
//创建哈夫曼树
public static NodeCode createHuffmanTree(List<NodeCode> nodeCodes){
//循环处理
while (nodeCodes.size() > 1){
//排序 从小到大 根据权重值(也就是次数)排序
Collections.sort(nodeCodes);
//找到权重值最小的两个元素(结点)
NodeCode leftNode = nodeCodes.get(0);
NodeCode rightNode = nodeCodes.get(1);
//两个最小结点weight相加,生成一个新的结点
NodeCode root = new NodeCode(leftNode.weight + rightNode.weight);
//将新的结点作为父节点,构成一个新的二叉树
root.left = leftNode;
root.right = rightNode;
//将两个结点删除
nodeCodes.remove(leftNode);
nodeCodes.remove(rightNode);
//将新的结点添加到集合中
nodeCodes.add(root);
}
return nodeCodes.get(0);
}
//获取哈夫曼编码
//1.往左取0,往右取1 a:101 b:01000
//完整的哈夫曼编码:10101000
//StringBuilder codes:用来拼接完整的哈夫曼编码
//Map 用来存储叶子结点路径 key = 32 value = 101
public static void getHuffmanCode(NodeCode nodeCode,String code,StringBuilder stringBuilder){
StringBuilder newString = new StringBuilder(stringBuilder);
//上面等价于
//StringBuilder newString = new StringBuilder();
//newString.append(stringBuilder.toString());
newString.append(code);
if (nodeCode != null){
if (nodeCode.data == null){
//向左递归
getHuffmanCode(nodeCode.left,"0",newString);
//向右递归
getHuffmanCode(nodeCode.right,"1",newString);
}else {
huffmanCode.put(nodeCode.data,newString.toString());
}
}
}
//设置统一入口
public static Map<Byte,String> createHuffmanCodes(NodeCode root){
if (root == null){
return null;
}
//处理左子树
getHuffmanCode(root.left,"0",stringBuilder);
//处理右子树
getHuffmanCode(root.right,"1",stringBuilder);
return huffmanCode;
}
//数据压缩
public static byte[] zip(byte[] bytes,Map<Byte,String> huffmanCode){
//用来拼接所有叶子结点路径
StringBuilder stringBuilder = new StringBuilder();
for (byte b : bytes){
stringBuilder.append(huffmanCode.get(b));
}
int length;
if (stringBuilder.length() % 8 == 0){
length = stringBuilder.length() / 8;
}else {
length = stringBuilder.length() / 8 + 1;
}
//压缩数据的数组
byte[] huffmanCodeByte = new byte[length];
int index = 0;
for (int i = 0; i < stringBuilder.length(); i += 8) {
String str;
if (i + 8 > stringBuilder.length()){
str = stringBuilder.substring(i);
}else {
str = stringBuilder.substring(i,i + 8);
}
huffmanCodeByte[index] = (byte)Integer.parseInt(str,2);
index++;
}
return huffmanCodeByte;
}
//数据解压过程
//1.首先把压缩数组转成哈夫曼编码,哈夫曼编码可以使用字符串进行接受拼接
//2.得到哈夫曼编码,转二进制
public static String byteToString(boolean isRepair,byte b){
int temp = b;
if (isRepair){
//或运算
temp |= 256;//1000 0000 和 0000 0001 = 1000 0001
}
String string = Integer.toBinaryString(temp);//把int类型数据转二进制补码
if (isRepair){
return string.substring(string.length() - 8);
}else {
return string;
}
}
//哈夫曼解码
public static byte[] decode(Map<Byte,String> huffmanCode,byte[] huffmanBytes){
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < huffmanBytes.length; i++) {
byte b = huffmanBytes[i];
boolean flg = false;
if (i == huffmanBytes.length - 1){
flg = true;
}
stringBuilder.append(byteToString(!flg,b));
}
//将原键值反过来存取
Map<String,Byte> map = new HashMap<>();
for (Map.Entry<Byte,String> entry : huffmanCode.entrySet()){
map.put(entry.getValue(),entry.getKey());
}
List<Byte> bytes = new ArrayList<>();
for (int i = 0; i < stringBuilder.length(); ) {
int count = 1;
boolean flg = true;
Byte b = null;
while (flg){
String key = stringBuilder.substring(i,i + count);
b = map.get(key);
if (b == null){
count++;
}else {
flg = false;
bytes.add(b);
}
}
i += count;
}
byte[] buff = new byte[bytes.size()];
for (int i = 0; i < buff.length; i++) {
buff[i] = bytes.get(i);
}
return buff;
}
}
运行结果:
总结:此代码还有BUG,必须将字符串调整到一个合适的情况,才能正确的解码
3.二叉排序树
- 二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。是数据结构中的一类
- 在一般情况下,查询效率比链表结构要高
- 如果有相同的值,可以将该节点放在左子节点和右子节点上
- 同一集数据对应的二叉排序树不唯一。但经过中序遍历得到的关键码序列都是一个递增序列。
结点类:
package binarysort;
import java.lang.annotation.ElementType;
public class Node {
public int value;
public Node left;
public Node right;
public Node(int value) {
this.value = value;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
'}';
}
//添加结点
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 void infixSelect(){
if (this.left != null){
this.left.infixSelect();
}
System.out.println(this);
if (this.right != null){
this.right.infixSelect();
}
}
//输入关键码值,输出删除的结点
public Node selectByVal(int val){
if (this.value == val){
return this;
}else if (val < this.value){
//查左子树
if (this.left == null){
return null;
}
return this.left.selectByVal(val);
}else{
//查询右子树
if (this.right == null){
return null;
}
return this.right.selectByVal(val);
}
}
/**
*
* @param val 删除结点关键码值
* @return 删除结点的父结点对象
*/
public Node selectByParent(int val){
if ((this.left != null && this.left.value == val) ||(this.right != null && this.right.value == val)){
return this;
}else{
if (val < this.value && this.left != null){
return this.left.selectByParent(val);
}else if (val >= this.value && this.right != null){
return this.right.selectByParent(val);
}else {
return null;
}
}
}
}
二叉树类:
package binarysort;
public class BinarySortTree {
private Node root;
public Node getRoot() {
return root;
}
//查询待删除结点
public Node selectByVal(int val){
if (root == null){
return null;
}else {
return root.selectByVal(val);
}
}
//查询待删除结点的父结点
public Node selectByParent(int val){
if (root == null){
System.out.println("root is null");
return null;
}else {
return root.selectByParent(val);
}
}
//添加结点
public void add(Node node){
if (root == null){
root = node;
}else {
root.add(node);
}
}
//中序遍历
public void infixSelect(){
if (root != null){
root.infixSelect();
}else {
System.out.println("空树...");
}
}
//找出右子树中最小值
public int rightTreeMin(Node node){
Node target = node;
while (target.left != null){
target = target.left;
}
delNode(target.value);
return target.value;
}
//删除结点
//1.如果删除的结点是叶子结点,找到父结点直接删除关联
//2.如果删除的结点带有左子树或者带有右子树,把左子树或右子树的结点交给删除该结点的父结点
//3.如果删除的结点带有双亲结点,可以从左子树中找到一个最大结点替换删除结点,也可以找右子树中最小的结点替换要删除的结点
public void delNode(int val){
if (root == null){
return;
}else{
//找到待查询的结点
Node target = selectByVal(val);
if (target == null){
return;
}
//在只有一个结点的情况下,直接删除
if (root.left == null && root.right == null){
root = null;
return;
}
//找到目标结点的父结点
Node parentNode = selectByParent(val);
//1.假设删除的是一个叶子结点
if (target.left == null && target.right == null){
//判断删除结点是父结点的左结点,还是右结点
if (parentNode.left != null && parentNode.left.value == val){
//左结点
parentNode.left = null;
}else if(parentNode.right != null && parentNode.right.value == val){
//右结点
parentNode.right = null;
}
}else if (target.left != null && target.right != null){
//待删除的结点为中间结点【非叶子结点非根结点】
//找到右边子树的最小值
int minVal = rightTreeMin(target.right);
target.value = minVal;
}else {
//删除的只有一个子树(可能右子树,可能是左子树)
if (target.left != null){//左子树
if (parentNode != null){
//确定是左子结点
if (parentNode.left.value == val){
parentNode.left = target.left;
}else {//右结点
parentNode.right = target.left;
}
}else{
root = target.left;
}
}else{//右子树
if (parentNode != null){
//确定是左子结点
if (parentNode.left.value == val){
parentNode.left = target.right;
}else {//右结点
parentNode.right = target.right;
}
}else{
root = target.right;
}
}
}
}
}
}
测试类:
package binarysort;
public class Test {
public static void main(String[] args) {
int[] array = new int[]{7,3,10,12,5,1,9,2};
BinarySortTree binarySortTree = new BinarySortTree();
for (int i = 0; i < array.length; i++) {
binarySortTree.add(new Node(array[i]));
}
binarySortTree.infixSelect();
System.out.println("======================");
binarySortTree.delNode(3);
binarySortTree.infixSelect();
System.out.println("======================");
}
}
运行结果:
4.多路查找树
4.1 二叉树和B树
- 二叉树中每个结点有一个数据项,最多有两个子节点,如果允许树的每个节点可以有两个以上的子节点,那么这个树就称为n阶的多叉树,或者称为n叉树
- 多叉树通过重新组织节点,减少树的高度,能对二叉树进行优化
4.2 2-3树
树是最简单的B树结构,具有如下特点
- 2-3树的所有叶子节点都在同一层.(只要是B树都满足这个条件)
- 有两个子节点的节点叫二节点,二节点要么没有子节点,要么有两个子节点
- 有三个子节点的节点叫三节点,三节点要么没有子节点,要么有三个子节点
- 2-3树是由二 点和三节点构成的树
4.3 B树、B+树、B*树
- B树通过重新组织节点,降低树的高度,并且减少i/o读写次数来提升效率
- B-tree 树即B树,B即 Balanced,平衡的意思。有人把 B-tree 翻译成B-树,容易让人产生误解。会以为B-树是一种树,而B树又是另一种树。实际上,B-tree就是指的B树。
- 2-3树和2-3-4树,他们就是B树(英语: B-tree也写成B-树),这里再做一个说明,在学习Mysql 时,经常听到说某种类型的索引是基于B树或者B+树的
- B+树是B树的变体,也是一种多路搜索树。
- B+树的搜索与B树也基本相同,区别是 B+树只有达到叶子结点才命中(B树可以在非叶子结点命中),其性能也等价于在关键字全集做一次二分查找
- 所有关键字都出现在叶子结点的链表中(即数据只能在叶子节点【也叫稠密索引】),且链表中的关键字(数据)恰好是有序的。
- 不可能在非叶子结点命中
- 非叶子结点相当于是叶子结点的索引(稀疏索引),叶子结点相当于是存储(关键字)数据的数据层
- 更适合文件索引系统
- B树和 B+树各有自己的应用场景,不能说B+树完全比B树好,反之亦然.
B*树是 B+树的变体,在 B+树的非根和非叶子结点再增指向兄弟的指针
- B*树定义了非叶子结点关键字个数至少为(2/3)*M,即块的最低使用率为2/3,而 B+树的块的最低使用率为的1/2。
- 从第1个特点可以看出,B*树分配新结点的概率比 B+树要低,空间使用率更高