• 数据结构和算法——树结构


    二叉树

    又叫二叉排序树

    节点是数量为,2^{n}-1,n为层数。

    满二叉树:所有的叶子节点都在最后一层。

    完全二叉树:如果所有叶子节点都在最后一层和倒数第二层,而且每个叶子节点都有左右子节点。

    完全二叉树
    完全二叉树

    前序遍历

    1、先输出当前节点(初始是root节点)。

    2、如果左子节点不为空,则递归继续前序遍历。

    3、如果右子节点不为空,则递归继续前序遍历。

    1. class HeroNode {
    2. private int no;
    3. private String name;
    4. private HeroNode left, right;
    5. }
    1. public HeroNode preOrderSearch(int no) {
    2. if (this.no == no) {
    3. return this;
    4. }
    5. HeroNode resNode;
    6. if (this.left != null) {
    7. resNode = this.left.preOrderSearch(no);
    8. if (resNode != null) {
    9. return resNode;
    10. }
    11. }
    12. if (this.right != null) {
    13. resNode = this.right.preOrderSearch(no);
    14. if (resNode != null) {
    15. return resNode;
    16. }
    17. }
    18. return null;
    19. }

    中序遍历

    1、如果当前节点的左子节点不为空,则递归中序遍历。

    2、输出当前节点。

    3、如果当前节点的右子节点不为空,则递归中序遍历。

    1. public HeroNode infixOrderSearch(int no) {
    2. HeroNode resNode;
    3. if (this.left != null) {
    4. resNode = this.left.infixOrderSearch(no);
    5. if (resNode != null) {
    6. return resNode;
    7. }
    8. }
    9. if (this.no == no) {
    10. return this;
    11. }
    12. if (this.right != null) {
    13. resNode = this.right.infixOrderSearch(no);
    14. if (resNode != null) {
    15. return resNode;
    16. }
    17. }
    18. return null;
    19. }

    后序遍历

    左右中

    1、如果当前节点的左子节点不为空,则递归后序遍历。

    2、如果当前节点的右子节点不为空,则递归后序遍历。

    3、输出当前节点。

    1. public HeroNode postOrderSearch(int no) {
    2. HeroNode resNode;
    3. if (this.left != null) {
    4. resNode = this.left.postOrderSearch(no);
    5. if (resNode != null) {
    6. return resNode;
    7. }
    8. }
    9. if (this.right != null) {
    10. resNode = this.right.postOrderSearch(no);
    11. if (resNode != null) {
    12. return resNode;
    13. }
    14. }
    15. if (this.no == no) {
    16. return this;
    17. }
    18. return null;
    19. }

    二叉树节点的删除,如果是中间节点,则整个中间节点都删除。

    1. public void delNode(int no) {
    2. if (this.no == no) {
    3. return;
    4. }
    5. if (this.left != null) {
    6. if (this.left.no == no) {
    7. this.left = null;
    8. } else {
    9. this.left.delNode(no);
    10. }
    11. }
    12. if (this.right != null) {
    13. if (this.right.no == no) {
    14. this.right = null;
    15. } else {
    16. this.right.delNode(no);
    17. }
    18. }
    19. }

    顺序存储二叉树

    顺序二叉树通常是完全二叉树

    第n(n是下标)个元素的左子节点为 2 * n + 1

    第n个元素的右子节点为 2 * n + 2

    第n个元素的父节点为 (n - 1) / 2

    堆排序用到顺序存储二叉树的结构。

    线索化二叉树

    充分的利用到了叶子节点的空指针。

    1. class HeroNode {
    2. private int no;
    3. private String name;
    4. private HeroNode left, right;
    5. private int leftType; // 0 指向的是左子树 1指向前驱节点
    6. private int rightType; // 0 指向的是右子树 1指向后继节点
    7. }

     对线索二叉树进行中序线索化的方法

    1. public void threadedNodes(HeroNode node) {
    2. if (node == null) {
    3. return;
    4. }
    5. threadedNodes(node.getLeft()); // 先线索化左子树
    6. // 再线索化当前节点
    7. if (node.getLeft() == null) { // 前驱
    8. node.setLeft(pre);
    9. node.setLeftType(1);
    10. }
    11. if (pre != null && pre.getRight() == null) { // 后继
    12. pre.setRight(node);
    13. pre.setRightType(1);
    14. }
    15. pre = node;
    16. threadedNodes(node.getRight()); // 最后线索化右子树
    17. }

    线索化二叉树的中序遍历

    1. class ThreadedBinaryTree {
    2. private HeroNode root;
    3. private HeroNode pre = null; // 指向前驱节点
    4. public void threadedList() {
    5. HeroNode node = root;
    6. while (node != null) {
    7. while (node.getLeftType() == 0) { // 到左下角
    8. node = node.getLeft();
    9. }
    10. System.out.println(node);
    11. while (node.getRightType() == 1) {
    12. node = node.getRight();
    13. System.out.println(node);
    14. }
    15. node = node.getRight();
    16. }
    17. }
    18. }

    什么是大顶堆,什么是小顶堆? 

    每个节点的值都小于等于其左右孩子节点的值,称为小顶堆。反之是大顶堆。

    大顶堆的特点:arr[i] >= arr[2 * i + 1] && arr[i] >= arr[2 * i + 2]

    小顶堆的特点:arr[i] <= arr[2 * i + 1] && arr[i] <= arr[2 * i + 2]

    i 是下标

    堆排序:数据结构和算法——排序算法-CSDN博客

    赫夫曼树

    路径长度等于层数-1;

    节点的权就是节点的值;

    节点的带权路径长度为:从根节点到该节点之间的路径长度与该节点的权的乘积;

    树的带权路径长度(WPL)为:所有叶子节点的带权路径长度之和;

    赫夫曼树是带权路径长度(WPL)最短的树,权值越大的节点离根节点越近;

    这不是赫夫曼树WPL=13*2+7*2+8*2+3*2=62
    这是赫夫曼树WPL=13*1+2*2+7*3+3*3=59

    如何构建一棵赫夫曼树?

    1)将数组从小到大排序,每个数据都是一个节点,每个节点都是一棵二叉树树;

    2)取出根节点权值最小的两棵二叉树;

    3)组成一棵新的二叉树,新二叉树的根节点的权值是前两棵二叉树根节点权值的和。

    4)再将新二叉树放到数组中。不断重复1-2-3-4的步骤,直到数组只剩下最后一个元素。

    通过数组构建赫夫曼树

    1. public class HuffmanTree {
    2. public static void main(String[] args) {
    3. int[] arr = {13, 7, 8, 3, 29, 6, 1}; // {1, 3, 6, 7, 8, 13, 29}
    4. Node root = createHuffmanTree(arr);
    5. preOrder(root);
    6. }
    7. // 数组 to 赫夫曼树
    8. public static Node createHuffmanTree(int[] arr) {
    9. List list = new ArrayList<>();
    10. for (int value : arr) { // int[] to ArrayList
    11. list.add(new Node(value));
    12. }
    13. while (list.size() > 1) {
    14. Collections.sort(list);
    15. // 1、取出最小的两个数
    16. Node leftNode = list.get(0); // 取出权值最小的二叉树
    17. Node rightNode = list.get(1); // 取出权值第二小的二叉树
    18. // 2、构建一棵新树
    19. Node parent = new Node(leftNode.value + rightNode.value);
    20. parent.left = leftNode;
    21. parent.right = rightNode;
    22. // 3、从ArrayList里删掉掉leftNode和rightNode
    23. list.remove(leftNode);
    24. list.remove(rightNode);
    25. // 4、新树加到原数组中
    26. list.add(parent);
    27. }
    28. return list.get(0);
    29. }
    30. public static void preOrder(Node root) {
    31. if (root != null) {
    32. root.preOrder();
    33. } else {
    34. System.out.println("空树不能遍历");
    35. }
    36. }
    37. }
    38. class Node implements Comparable {
    39. int value; // 节点的权值
    40. Node left, right;
    41. public Node(int value) {
    42. this.value = value;
    43. }
    44. // 前序遍历
    45. public void preOrder() {
    46. System.out.println(this);
    47. if (this.left != null) {
    48. this.left.preOrder();
    49. }
    50. if (this.right != null) {
    51. this.right.preOrder();
    52. }
    53. }
    54. @Override
    55. public String toString() {
    56. return "Node{" +
    57. "value=" + value +
    58. '}';
    59. }
    60. @Override
    61. public int compareTo(Node node) {
    62. return this.value - node.value;
    63. }
    64. }

    赫夫曼编码

    也称为可变字长编码(每个字符用来表示的二进制位长度不确定),把字符串发放到赫夫曼树里,可以对数据进行压缩和解压,压缩率可以达到20%~90%。是无损压缩,每次构建的赫夫曼树有可能不同;

    1. public class HuffmanCode {
    2. public static void main(String[] args) {
    3. String content = "i like like like java do you like a java";
    4. // 10100
    5. byte[] contentBytes = content.getBytes(); // oldString to ascii 例如:171717
    6. // 1、压缩
    7. byte[] huffmanZip = huffmanZip(contentBytes);
    8. // 2、解压
    9. byte[] sourceBytes = decode(huffmanCodes, huffmanZip);
    10. String oldString = new String(sourceBytes);
    11. }
    12. /**
    13. * 将byte转成二进制的字符串
    14. *
    15. * @param flag 是否需要删除高位
    16. * @param b 十进制
    17. * @return 二进制
    18. */
    19. private static String bytesToBitString(boolean flag, byte b) {
    20. int temp = b;
    21. if (flag) {
    22. temp |= 256;
    23. String str = Integer.toBinaryString(temp);
    24. return str.substring(str.length() - 8);
    25. } else {
    26. String str = Integer.toBinaryString(temp);
    27. return str;
    28. }
    29. }
    30. /**
    31. * 解压
    32. *
    33. * @param huffmanCodes 赫夫曼对应的编码表
    34. * @param huffmanBytes 压缩赫夫曼码
    35. * @return oldString
    36. */
    37. private static byte[] decode(Map huffmanCodes, byte[] huffmanBytes) {
    38. // 1、转成101格式
    39. StringBuilder stringBuilder1 = new StringBuilder();
    40. for (int i = 0; i < huffmanBytes.length - 1; i++) {
    41. stringBuilder1.append(bytesToBitString(true, huffmanBytes[i]));
    42. } // 最后一个不需要补0
    43. stringBuilder1.append(bytesToBitString(false, huffmanBytes[huffmanBytes.length - 1]));
    44. // 2、对照赫夫曼编码表转成oldString
    45. HashMap map = new HashMap<>(); // 反转map
    46. for (Map.Entry entry : huffmanCodes.entrySet()) {
    47. map.put(entry.getValue(), entry.getKey());
    48. }
    49. List list = new ArrayList<>();
    50. for (int i = 0; i < stringBuilder1.length(); i++) {
    51. int count = 1;
    52. boolean flag = true;
    53. Byte b = null;
    54. while (flag) {
    55. String key = stringBuilder1.substring(i, i + count);
    56. b = map.get(key);
    57. if (b == null) { // 没有找到
    58. count++;
    59. } else {
    60. flag = false;
    61. }
    62. }
    63. list.add(b);
    64. i += count - 1;
    65. }
    66. byte[] b = new byte[list.size()];
    67. for (int i = 0; i < b.length; i++) {
    68. b[i] = list.get(i);
    69. }
    70. return b;
    71. }
    72. /**
    73. * oldString to huffmanZip
    74. *
    75. * @param bytes 原始数组
    76. * @return 压缩后的数组
    77. */
    78. private static byte[] huffmanZip(byte[] bytes) {
    79. List nodes = getNodes(bytes); // 每个字符各出现了多少次 例如:105->9次
    80. Node root = createHuffmanTree(nodes); // 生成赫夫曼树
    81. Map huffmanCodes = getCodes(root); // 生成对应赫夫曼编码 例如:32->01
    82. byte[] huffmanCodeBytes = zip(bytes, huffmanCodes); // 压缩 例如:-88,-65,-56
    83. return huffmanCodeBytes;
    84. }
    85. /**
    86. * 压缩,将字符串对应的byte[]数组,转换成压缩后的byte[]
    87. *
    88. * @param bytes 原始的byte[]数组
    89. * @param huffmanCodes 赫夫曼编码表
    90. * @return 返回压缩后的byte[]数组
    91. */
    92. private static byte[] zip(byte[] bytes, Map huffmanCodes) {
    93. StringBuilder stringBuilder1 = new StringBuilder();
    94. for (byte b : bytes) {
    95. stringBuilder1.append(huffmanCodes.get(b));
    96. }
    97. int len;
    98. if (stringBuilder1.length() % 8 == 0) {
    99. len = stringBuilder1.length() / 8;
    100. } else {
    101. len = stringBuilder1.length() / 8 + 1;
    102. }
    103. byte[] huffmanCodeBytes = new byte[len];
    104. int index = 0; // 第几个byte
    105. for (int i = 0; i < stringBuilder1.length(); i += 8) { // 每8位对应一个byte,所以步长是8
    106. String strByte;
    107. if (i + 8 < stringBuilder1.length()) {
    108. strByte = stringBuilder1.substring(i, i + 8);
    109. } else { // 最末尾的
    110. strByte = stringBuilder1.substring(i);
    111. }
    112. huffmanCodeBytes[index] = (byte) Integer.parseInt(strByte, 2);
    113. index++;
    114. }
    115. return huffmanCodeBytes;
    116. }
    117. // 赫夫曼编码表Map 32(空格)->01
    118. static Map huffmanCodes = new HashMap<>();
    119. static StringBuilder stringBuilder = new StringBuilder();
    120. private static Map getCodes(Node root) {
    121. if (root == null) {
    122. return null;
    123. }
    124. getCodes(root.left, "0", stringBuilder); // 生成赫夫曼编码
    125. getCodes(root.right, "1", stringBuilder); // 生成赫夫曼编码
    126. return huffmanCodes;
    127. }
    128. /**
    129. * 将传入的node节点的所有叶子节点的赫夫曼编码得到,并放入到huffmanCodes集合
    130. *
    131. * @param node 根节点
    132. * @param code 路径 左子节点0 右子节点1
    133. * @param stringBuilder 用于拼接路径
    134. */
    135. private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
    136. StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
    137. stringBuilder2.append(code);
    138. if (node != null) {
    139. if (node.data == null) { // 非叶子节点,没有内容,只有权值
    140. getCodes(node.left, "0", stringBuilder2);
    141. getCodes(node.right, "1", stringBuilder2);
    142. } else { // 叶子节点
    143. huffmanCodes.put(node.data, stringBuilder2.toString());
    144. }
    145. }
    146. }
    147. private static void preOrder(Node root) {
    148. if (root != null) {
    149. root.preOrder();
    150. } else {
    151. System.out.println("赫夫曼树为空");
    152. }
    153. }
    154. private static List getNodes(byte[] bytes) {
    155. ArrayList nodes = new ArrayList<>();
    156. HashMap counts = new HashMap<>();
    157. for (byte b : bytes) {
    158. Integer count = counts.get(b);
    159. if (count == null) {
    160. counts.put(b, 1);
    161. } else {
    162. counts.put(b, count + 1);
    163. }
    164. }
    165. for (Map.Entry entry : counts.entrySet()) {
    166. nodes.add(new Node(entry.getKey(), entry.getValue()));
    167. }
    168. return nodes;
    169. }
    170. private static Node createHuffmanTree(List nodes) {
    171. while (nodes.size() > 1) {
    172. Collections.sort(nodes);
    173. Node leftNode = nodes.get(0);
    174. Node righNode = nodes.get(1);
    175. Node parentNode = new Node(null, leftNode.value + righNode.value);
    176. parentNode.left = leftNode;
    177. parentNode.right = righNode;
    178. nodes.add(parentNode);
    179. nodes.remove(leftNode);
    180. nodes.remove(righNode);
    181. }
    182. return nodes.get(0);
    183. }
    184. }
    185. class Node implements Comparable {
    186. Byte data;
    187. int value; // 权值
    188. Node left, right;
    189. public Node(Byte key, Integer value) {
    190. this.data = key;
    191. this.value = value;
    192. }
    193. void preOrder() {
    194. }
    195. @Override
    196. public int compareTo(Node node) {
    197. return this.value - node.value;
    198. }
    199. @Override
    200. public String toString() {
    201. return "Node{" +
    202. "data=" + data +
    203. ", value=" + value +
    204. '}';
    205. }
    206. }

    如何用赫夫曼树压缩文件?

    1. import java.io.*;
    2. import java.util.*;
    3. public class HuffmanCode2 {
    4. public static void main(String[] args) {
    5. String srcFile = "d:\\Users\\JJH\\Desktop\\1.jpg";
    6. String descFile = "d:\\Users\\JJH\\Desktop\\download.zip";
    7. // 1、文件的压缩,写入目标路径
    8. zipFile(srcFile, descFile);
    9. String descFile2 = "d:\\Users\\JJH\\Desktop\\2.jpg";
    10. // 2、文件的解压
    11. unZipFile(descFile, descFile2);
    12. }
    13. public static void unZipFile(String zipFile, String destFile) {
    14. // 文件输入流
    15. InputStream is = null;
    16. // 对象输入流
    17. ObjectInputStream ois = null;
    18. // 文件输出流
    19. OutputStream os = null;
    20. try {
    21. // 文件输入流
    22. is = new FileInputStream(zipFile);
    23. ois = new ObjectInputStream(is);
    24. // 文件读取出来的二进制数
    25. byte[] huffmanBytes = (byte[]) ois.readObject();
    26. // 文件读取出来的赫夫曼编码表
    27. Map huffmanCodes = (Map) ois.readObject();
    28. // 定义文件输出流
    29. os = new FileOutputStream(destFile);
    30. // 1、解压
    31. byte[] decode = decode(huffmanCodes, huffmanBytes);
    32. // 2、写入文件
    33. os.write(decode);
    34. } catch (Exception e) {
    35. throw new RuntimeException(e);
    36. } finally {
    37. try {
    38. if (ois != null) {
    39. ois.close();
    40. }
    41. if (os != null) {
    42. os.close();
    43. }
    44. if (is != null) {
    45. is.close();
    46. }
    47. } catch (IOException e) {
    48. throw new RuntimeException(e);
    49. }
    50. }
    51. }
    52. /**
    53. * 解压
    54. *
    55. * @param huffmanCodes 赫夫曼对应的编码表
    56. * @param huffmanBytes 压缩赫夫曼码
    57. * @return oldString
    58. */
    59. private static byte[] decode(Map huffmanCodes, byte[] huffmanBytes) {
    60. // 1、转成101格式
    61. StringBuilder stringBuilder1 = new StringBuilder();
    62. for (int i = 0; i < huffmanBytes.length - 1; i++) {
    63. stringBuilder1.append(bytesToBitString(true, huffmanBytes[i]));
    64. } // 最后一个需要补0,例如最后一个数是113的情况
    65. stringBuilder1.append(bytesToBitString(false, huffmanBytes[huffmanBytes.length - 1]));
    66. // 2、对照赫夫曼编码表转成oldString
    67. HashMap map = new HashMap<>(); // 反转map
    68. for (Map.Entry entry : huffmanCodes.entrySet()) {
    69. map.put(entry.getValue(), entry.getKey());
    70. }
    71. List list = new ArrayList<>();
    72. for (int i = 0; i < stringBuilder1.length(); ) {
    73. int count = 1;
    74. boolean flag = true;
    75. Byte b = null;
    76. while (flag) {
    77. String key = stringBuilder1.substring(i, i + count);
    78. b = map.get(key);
    79. if (b == null) { // 没有找到
    80. count++;
    81. } else {
    82. flag = false;
    83. }
    84. }
    85. list.add(b);
    86. i += count;
    87. }
    88. byte[] b = new byte[list.size()];
    89. for (int i = 0; i < b.length; i++) {
    90. b[i] = list.get(i);
    91. }
    92. return b;
    93. }
    94. /**
    95. * 将byte转成二进制的字符串
    96. *
    97. * @param flag 是否需要删除高位
    98. * @param b 十进制
    99. * @return 二进制
    100. */
    101. private static String bytesToBitString(boolean flag, byte b) {
    102. int temp = b;
    103. if (flag) { // 不要补0的情况
    104. /**
    105. * 00001111 和
    106. * 1111
    107. */
    108. temp |= 256;
    109. String str = Integer.toBinaryString(temp);
    110. return str.substring(str.length() - 8);
    111. } else { // 要补零的情况
    112. temp |= 256;
    113. String str = Integer.toBinaryString(temp);
    114. return str.substring(str.length() - 8);
    115. }
    116. }
    117. /**
    118. * 文件的压缩
    119. *
    120. * @param srcFile 源路径
    121. * @param destFile 目标路径
    122. */
    123. public static void zipFile(String srcFile, String destFile) {
    124. FileInputStream is = null;
    125. FileOutputStream os = null;
    126. ObjectOutputStream oos = null;
    127. try {
    128. // 1、创建输入流
    129. is = new FileInputStream(srcFile);
    130. // 创建一个和源文件大小一样的byte[]
    131. byte[] b = new byte[is.available()];
    132. is.read(b);
    133. byte[] huffmanZip = huffmanZip(b);
    134. // 2、创建输出流
    135. os = new FileOutputStream(destFile);
    136. oos = new ObjectOutputStream(os); // 对象输出流
    137. oos.writeObject(huffmanZip);
    138. // 还要把赫夫曼编码写入压缩文件
    139. oos.writeObject(huffmanCodes);
    140. } catch (FileNotFoundException e) {
    141. throw new RuntimeException(e);
    142. } catch (IOException e) {
    143. throw new RuntimeException(e);
    144. } finally {
    145. try {
    146. if (oos != null) {
    147. oos.close();
    148. }
    149. if (os != null) {
    150. os.close();
    151. }
    152. if (is != null) {
    153. is.close();
    154. }
    155. } catch (IOException e) {
    156. throw new RuntimeException(e);
    157. }
    158. }
    159. }
    160. /**
    161. * oldString to huffmanZip
    162. *
    163. * @param bytes 原始数组
    164. * @return 压缩后的数组
    165. */
    166. private static byte[] huffmanZip(byte[] bytes) {
    167. List nodes = getNodes(bytes); // 每个字符各出现了多少次 例如:105->9次
    168. Node root = createHuffmanTree(nodes); // 生成赫夫曼树
    169. Map huffmanCodes = getCodes(root); // 生成对应赫夫曼编码 例如:32->01
    170. byte[] huffmanCodeBytes = zip(bytes, huffmanCodes); // 压缩 例如:-88,-65,-56
    171. return huffmanCodeBytes;
    172. }
    173. private static List getNodes(byte[] bytes) {
    174. ArrayList nodes = new ArrayList<>();
    175. HashMap counts = new HashMap<>();
    176. for (byte b : bytes) {
    177. Integer count = counts.get(b);
    178. if (count == null) {
    179. counts.put(b, 1);
    180. } else {
    181. counts.put(b, count + 1);
    182. }
    183. }
    184. for (Map.Entry entry : counts.entrySet()) {
    185. nodes.add(new Node(entry.getKey(), entry.getValue()));
    186. }
    187. return nodes;
    188. }
    189. private static Node createHuffmanTree(List nodes) {
    190. while (nodes.size() > 1) {
    191. Collections.sort(nodes);
    192. Node leftNode = nodes.get(0);
    193. Node righNode = nodes.get(1);
    194. Node parentNode = new Node(null, leftNode.value + righNode.value);
    195. parentNode.left = leftNode;
    196. parentNode.right = righNode;
    197. nodes.add(parentNode);
    198. nodes.remove(leftNode);
    199. nodes.remove(righNode);
    200. }
    201. return nodes.get(0);
    202. }
    203. static StringBuilder stringBuilder = new StringBuilder();
    204. static Map huffmanCodes = new HashMap<>();
    205. private static Map getCodes(Node root) {
    206. if (root == null) {
    207. return null;
    208. }
    209. getCodes(root.left, "0", stringBuilder); // 生成赫夫曼编码
    210. getCodes(root.right, "1", stringBuilder); // 生成赫夫曼编码
    211. return huffmanCodes;
    212. }
    213. /**
    214. * 将传入的node节点的所有叶子节点的赫夫曼编码得到,并放入到huffmanCodes集合
    215. *
    216. * @param node 根节点
    217. * @param code 路径 左子节点0 右子节点1
    218. * @param stringBuilder 用于拼接路径
    219. */
    220. private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
    221. StringBuilder stringBuilder2 = new StringBuilder(stringBuilder);
    222. stringBuilder2.append(code);
    223. if (node != null) {
    224. if (node.data == null) { // 非叶子节点,没有内容,只有权值
    225. getCodes(node.left, "0", stringBuilder2);
    226. getCodes(node.right, "1", stringBuilder2);
    227. } else { // 叶子节点
    228. huffmanCodes.put(node.data, stringBuilder2.toString());
    229. }
    230. }
    231. }
    232. /**
    233. * 压缩,将字符串对应的byte[]数组,转换成压缩后的byte[]
    234. *
    235. * @param bytes 原始的byte[]数组
    236. * @param huffmanCodes 赫夫曼编码表
    237. * @return 返回压缩后的byte[]数组
    238. */
    239. private static byte[] zip(byte[] bytes, Map huffmanCodes) {
    240. // 1001
    241. StringBuilder stringBuilder1 = new StringBuilder();
    242. for (byte b : bytes) {
    243. stringBuilder1.append(huffmanCodes.get(b));
    244. }
    245. int len;
    246. if (stringBuilder1.length() % 8 == 0) {
    247. len = stringBuilder1.length() / 8;
    248. } else {
    249. len = stringBuilder1.length() / 8 + 1;
    250. }
    251. byte[] huffmanCodeBytes = new byte[len];
    252. int index = 0; // 第几个byte
    253. for (int i = 0; i < stringBuilder1.length(); i += 8) { // 每8位对应一个byte,所以步长是8
    254. String strByte;
    255. if (i + 8 < stringBuilder1.length()) {
    256. strByte = stringBuilder1.substring(i, i + 8);
    257. } else { // 最末尾的
    258. strByte = stringBuilder1.substring(i);
    259. }
    260. huffmanCodeBytes[index] = (byte) Integer.parseInt(strByte, 2);
    261. index++;
    262. }
    263. return huffmanCodeBytes;
    264. }
    265. }

    二叉排序树

    1、左子节点小于当前节点,右子节点大于当前节点;

    2、尽量避免相同节点,相同节点可以放在左边或者右边;

    如果树不平衡怎么办?

    平衡二叉树就是对此的优化;

    平衡二叉树

    一棵树的左右两棵子树的高度差的绝对值不超过1;

    左旋转

    1、创建一个新节点newNode,值等于当前节点的值;

    2、把新节点的左子树设置为当前节点的左子树;

    3、把新节点的右子树设置为当前节点右子树的左子树;

    4、把当前节点(root)的值换为右子节点的值;

    5、把当前节点的右子树设置为右子树的右子树;

    6、把当前节点的左子树设置为新节点; 

    右旋转

    1、创建新节点,以当前根节点的值
    2、把新节点的右子节点设置为当前节点的右子节点
    3、把新节点的左子节点设置为当前节点的左子节点的右子节点
    4、把当前节点的值设置为左子节点的值
    5、当前节点左子节点设置位当前节点左子节点的左子节点
    6、当前节点的右子节点设置为新节点

    在添加新节点时旋转

    1. package org.example.tree.avl;
    2. public class AVLTreeDemo {
    3. public static void main(String[] args) {
    4. // int[] arr = {4, 3, 6, 5, 7, 8};
    5. // int[] arr = {10,11,7,6,8,9};
    6. int[] arr = {2, 1, 6, 5, 7, 3};
    7. AVLTree avlTree = new AVLTree();
    8. for (int i = 0; i < arr.length; i++) {
    9. avlTree.add(new Node(arr[i]));
    10. }
    11. }
    12. }
    13. class AVLTree {
    14. private Node root;
    15. public Node getRoot() {
    16. return root;
    17. }
    18. // 查找要删除的节点
    19. public Node search(int val) {
    20. if (root == null) {
    21. return null;
    22. } else {
    23. return root.search(val);
    24. }
    25. }
    26. // 查找要删除的节点的父节点
    27. public Node searchParent(int val) {
    28. if (root == null) {
    29. return null;
    30. } else {
    31. return root.searchParent(val);
    32. }
    33. }
    34. /**
    35. * 1、删除叶子节点
    36. * 2、删除有一棵子树的节点
    37. * 左子节点还是右子节点?
    38. * 3、删除有两课子树的节点
    39. * 右子节点的最小左子节点
    40. */
    41. public void delNode(int val) {
    42. if (root == null) {
    43. return;
    44. } else {
    45. Node targetNode = search(val);
    46. if (targetNode == null) {
    47. return; //没找到
    48. }
    49. if (root.left == null && root.right == null) {
    50. // 树只有一个节点
    51. root = null;
    52. return;
    53. }
    54. Node parent = searchParent(val);
    55. // 1、删除叶子节点
    56. if (targetNode.left == null && targetNode.right == null) {
    57. // 判断ta rgetNode是parent的左子节点还是父子节点
    58. if (parent.left != null && parent.left == targetNode) {
    59. // 删除的是左子节点
    60. parent.left = null;
    61. } else if (parent.right != null && parent.right == targetNode) {
    62. // 删除的是右子节点
    63. parent.right = null;
    64. }
    65. } else if (targetNode.left != null && targetNode.right != null) {
    66. // 2、删除有两棵子节点的节点
    67. // 从target的右子节点找最小的节点
    68. int min = delRightTreeMin(targetNode.right);
    69. targetNode.val = min;
    70. } else {
    71. // 3、删除只有一个子节点的节点
    72. if (targetNode.left != null) {
    73. // target只有左子节点
    74. if (parent != null) {
    75. if (parent.left.val == val) {
    76. // target是父节点的左子节点
    77. parent.left = targetNode.left;
    78. } else if (parent.right.val == val) {
    79. // target是父节点的右子节点
    80. parent.right = targetNode.left;
    81. }
    82. } else {
    83. // target是根节点
    84. root = targetNode.left;
    85. }
    86. } else {
    87. if (parent != null) {
    88. // target只有右子节点
    89. if (parent.left.val == val) {
    90. // target是父节点的左子节点
    91. parent.left = targetNode.right;
    92. } else {
    93. // target是父节点的右子节点
    94. parent.right = targetNode.right;
    95. }
    96. } else {
    97. // target是根节点
    98. root = targetNode.right;
    99. }
    100. }
    101. }
    102. }
    103. }
    104. // 删除右子节点的最左子节点并返回
    105. public int delRightTreeMin(Node node) {
    106. Node target = node;
    107. while (target.left != null) {
    108. target = target.left;
    109. }
    110. int val = target.val;
    111. delNode(val);
    112. return val;
    113. }
    114. public void add(Node node) {
    115. if (root == null) {
    116. root = node;
    117. } else {
    118. root.add(node);
    119. }
    120. }
    121. /*public void infixOrder() {
    122. if (root != null) {
    123. root.infixOrder();
    124. } else {
    125. System.out.println("二叉排序树为空,不能遍历");
    126. }
    127. }*/
    128. }
    129. class Node {
    130. int val;
    131. Node left, right;
    132. public Node(int val) {
    133. this.val = val;
    134. }
    135. public int leftHeight() {
    136. if (left == null) {
    137. return 0;
    138. }
    139. return left.height();
    140. }
    141. public int rightHeight() {
    142. if (right == null) {
    143. return 0;
    144. }
    145. return right.height();
    146. }
    147. // 以该节点为根节点的树的高度
    148. public int height() {
    149. return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
    150. }
    151. // 查找要删除的节点
    152. public Node search(int val) {
    153. if (val == this.val) {
    154. return this;
    155. } else if (val < this.val) {
    156. if (this.left == null) {
    157. return null; // 找不到
    158. } else {
    159. return this.left.search(val);
    160. }
    161. } else {
    162. if (this.right == null) {
    163. return null; // 找不到
    164. } else {
    165. return this.right.search(val);
    166. }
    167. }
    168. }
    169. // 查找要删除的节点的父节点
    170. public Node searchParent(int val) {
    171. // 如果当前节点是要删除的节点的父节点
    172. if (this.left != null && this.left.val == val ||
    173. this.right != null && this.right.val == val) {
    174. return this;
    175. } else {
    176. if (val < this.val && this.left != null) {
    177. return this.left.searchParent(val);
    178. } else if (val > this.val && this.right != null) {
    179. return this.right.searchParent(val);
    180. }
    181. return null;
    182. }
    183. }
    184. public void add(Node node) {
    185. if (node == null) {
    186. return;
    187. }
    188. if (node.val < this.val) {
    189. if (this.left == null) {
    190. this.left = node;
    191. } else {
    192. this.left.add(node);
    193. }
    194. }
    195. if (node.val > this.val) {
    196. if (this.right == null) {
    197. this.right = node;
    198. } else {
    199. this.right.add(node);
    200. }
    201. }
    202. // 添加完节点后,是否需要旋转
    203. if (rightHeight() - leftHeight() > 1) {
    204. if (right != null && right.leftHeight() > right.rightHeight()) {
    205. // 左旋转
    206. right.rightRotate();
    207. }
    208. // 左旋转
    209. leftRotate();
    210. return;
    211. }
    212. if (leftHeight() - rightHeight() > 1) {
    213. if (left != null && left.leftHeight() < left.rightHeight()) {
    214. // 左旋转
    215. left.leftRotate();
    216. }
    217. // 右旋转
    218. rightRotate();
    219. }
    220. }
    221. private void leftRotate() {
    222. // 创建新节点,以当前根节点的值
    223. Node newNode = new Node(val);
    224. // 把新节点的左子节点设置为当前节点的左子节点
    225. newNode.left = left;
    226. // 把新节点的右子节点设置为当前节点的右子节点的左子节点
    227. newNode.right = right.left;
    228. // 把当前节点的值设置为右子节点的值
    229. val = right.val;
    230. // 把当前节点的右子节点设置为当前节点的右子节点的右子节点
    231. right = right.right;
    232. // 把当前节点的左子节点设置为新节点
    233. left = newNode;
    234. }
    235. private void rightRotate() {
    236. // 创建新节点,以当前根节点的值
    237. Node newNode = new Node(val);
    238. // 把新节点的右子节点设置为当前节点的右子节点
    239. newNode.right = right;
    240. // 把新节点的左子节点设置为当前节点的左子节点的右子节点
    241. newNode.left = left.right;
    242. // 把当前节点的值设置为左子节点的值
    243. val = left.val;
    244. // 把当前节点的左子节点设置为当前节点的左子节点的左子节点
    245. left = left.left;
    246. // 把当前节点的右子节点设置为新节点
    247. right = newNode;
    248. }
    249. /*public void infixOrder() {
    250. if (this.left != null) {
    251. this.left.infixOrder();
    252. }
    253. System.out.println(this);
    254. if (this.right != null) {
    255. this.right.infixOrder();
    256. }
    257. }*/
    258. @Override
    259. public String toString() {
    260. return "Node{" +
    261. "val=" + val +
    262. '}';
    263. }
    264. }

    多叉树

    多叉树的存在是为了减少树的高度,以减少IO的次数;

    B树

    一个节点的大小通常是4K,这样每个节点只需要一次IO就可以完全载入;

    B树的阶:节点的最多子节点的个数,例如23树的阶就是3;

    文件系统和和数据库系统的设计者采用预读原理,将一个节点的大小设置为一个页(一个页通常是4K),这样每个节点就只需要1次IO就可以完全载入;

    将树的度M设置为1024;

    2-3树

    2-3树是最简单的B树;

    2-3树的所有叶子节点都在同一层;

    三个子节点的节点叫三节点,三节点要么没有子节点,要么有三个子节点,二节点同理;

    2-3树是由二节点和三节点构成的树;

    2-3-4树

    B+树

    B+树和B树的区别:B+树的所有数据都放在叶子节点;

    B*树

    参考视频:【尚硅谷】数据结构与算法(Java数据结构与算法)_哔哩哔哩_bilibili

  • 相关阅读:
    NPDP考什么?难度大不大?
    如何在Python中实现安全的密码存储与验证
    C++_重载_指针_引用
    pytorch 保存和加载模型
    MySQL篇—事务和隔离级别介绍
    C++使用二维码识别库Zbar识别二维码图像
    【Android】ViewRootImpl、WindowManagerGlobal和WindowManager之间的关系
    ROS Bridge 笔记(01)— apt 安装、源码编译安装、安装依赖、运行显示
    硬件管理平台 - 公共项目搭建(Nancy部分)
    线性判别分析的多分类情况
  • 原文地址:https://blog.csdn.net/m0_58961367/article/details/133832293