-
编写一个方法,完成对压缩文件的解压
-
@param zipFile 准备解压的文件
-
@param dstFile 将文件解压到哪个路径
*/
public static void unZip(String zipFile,String dstFile){
// 定义文件输入流
InputStream is = null;
// 定义对象输入流
ObjectInputStream ois = null;
// 定义文件输出流
OutputStream os = null;
try {
// 创建文件输入流
is = new FileInputStream(zipFile);
// 创建一个和is关联的对象输入流
ois = new ObjectInputStream(is);
// 读取byte数组 huffmanBytes
byte[] huffmanBytes = (byte[])ois.readObject();
// 读取保存的赫夫曼编码表
Map<Byte,String> huffmanCodes = (Map<Byte, String>)ois.readObject();
// 解码
byte[] bytes = decode(huffmanCodes, huffmanBytes);
// 将bytes数组写入文件
os = new FileOutputStream(dstFile);
// 写数据到dstFile文件
os.write(bytes);
}catch (Exception e){
System.out.println(e.getMessage());
}finally {
try {
is.close();
ois.close();
os.close();
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
// 完成数据的解压
// 思路:
// 1. 将huffmanCodeBytes[-88, -65, -56, -65, -56, -65, -55, 77, -57, 6, -24, -14, -117, -4, -60, -90, 28]转成赫夫曼编码对应的二进制字符串
// 重写先转成赫夫曼编码对应的二进制的字符串"1010100010111…"
// 2. 将赫夫曼编码对应的二进制字符串转成原始字符串
/**
-
编写一个方法,完成对压缩数据的解码
-
@param huffmanCodes 赫夫曼编码表Map
-
@param huffmanBytes 赫夫曼编码得到的字节数组
-
@return 原来的字符串对应的数组
*/
public static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
// 1. 先得到huffmanBytes对应的二进制的字符串,形式1010100010111…
StringBuilder stringBuilder = new StringBuilder();
// 将byte数组转成二进制字符串
for (int i = 0; i < huffmanBytes.length; i++) {
// 判断是不是最后一个字节
boolean flag = (i == huffmanBytes.length - 1);
stringBuilder.append(byteToBitString(!flag, huffmanBytes[i]));
}
// System.out.println(stringBuilder.toString());
// 把字符串按照指定的赫夫曼编码进行解码
// 把赫夫曼编码表反向调换 a -> 100 => 100 -> a
Map<String, Byte> map = new HashMap<String, Byte>();
for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
map.put(entry.getValue(), entry.getKey());
}
// 创建一个集合,存放Byte
List list = new ArrayList<>();
// i 可以理解成就是索引,扫描stringBuilder
for (int i = 0; i < stringBuilder.length()😉 {
int count = 1; // 小的计数器
boolean flag = true;
Byte b = null;
while (flag) {
// 递增取出一个’1’ 或者 ‘0’
String key = stringBuilder.substring(i, i + count); // i不动,让count移动,直到匹配到一个字符
b = map.get(key);
if (b == null) { //没有匹配到
count++;
} else {
flag = false;
}
}
list.add(b);
i += count; // i直接的移动到count
}
// 当for循环结束后,list中存放了所有的字符
// 把list中数据放入一个byte[] 并返回
byte[] b = new byte[list.size()];
for (int i = 0; i < b.length; i++) {
b[i] = list.get(i);
}
return b;
}
/**
-
将一个byte转成一个二进制字符串
-
@param b 接收的byte
-
@param flag 标识是否需要补高位,如果是true,表示需要补高位,如果是false不补
-
如果是最后一个字节,无须补高位
-
@return b对应的二进制字符串(注意是按照补码返回)
*/
private static String byteToBitString(boolean flag, byte b) {
// 使用变量保存b
int temp = b;
// 如果是正数,需要补高位
if (flag) {
temp |= 256; // 按位与 256 1 0000 0000 | 0000 0001 => 1 0000 0001
}
String str = Integer.toBinaryString(temp); // 返回的是temp对应的二进制的补码
if (flag) {
str = str.substring(str.length() - 8);
}
return str;
}
/**
-
使用一个方法,将前面的方法封装起来,便于调用
-
@param bytes 原始的字符串对应的字节数组
-
@return 返回的是经过赫夫曼编码处理后(压缩后)的数组
*/
private static byte[] huffmanZip(byte[] bytes) {
List nodes = getNodes(bytes);
// 根据nodes创建赫夫曼树
Node huffmanTreeRoot = createHuffmanTree(nodes);
// 根据赫夫曼树创建对应的赫夫曼编码
Map<Byte, String> huffmanCodes = getCodes(huffmanTreeRoot);
// 根据赫夫曼编码对原始赫夫曼编码亚索
byte[] huffmanCodeBytes = zip(bytes, huffmanCodes);
return huffmanCodeBytes;
}
/**
-
编写一个方法,将字符串对应的byte[] 数组,通过生成的赫夫曼编码表,返回一个赫夫曼编码压缩后的byte[]
-
@param bytes 这是原始的字符串对应的byte[]
-
@param huffmanCodes 生成的赫夫曼编码map
-
@return 返回赫夫曼编码处理后的byte[]
-
举例:接收"i like like like java do you like a java"对应的byte[]数组 => byte[] contentBytes = str.getBytes()
-
返回的是1010100110111101111010011011110111101001101111011110100001100001110011001111000011001111000100100100110111101111011100100001100001110
-
该字符串对应的字节数组byte[] huffmanCodeBytes,即8位对应一个byte,放入huffmanCodeBytes
-
huffmanCodeBytes[0] = 10101000(补码) => byte[推导 10101000(补码) => 10101000 - 1 => 10100111(反码) => 11011000(原码) = -88]
-
即使 huffmanCodeByte[1] = -88
*/
public static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCodes) {
// 1. 先利用huffmanCodes 将 bytes 转成 赫夫曼编码对应的字符串
StringBuilder stringBuilder = new StringBuilder();
// 遍历bytes数组
for (byte b : bytes) {
stringBuilder.append(huffmanCodes.get(b));
}
// 将字符串转成byte[] 数组
// 统计返回的huffmanCodes长度
// 一句话 int len = (stringBuilder.length() + 7) / 8
int len;
if (stringBuilder.length() % 8 == 0) {
len = stringBuilder.length() / 8;
} else {
len = stringBuilder.length() / 8 + 1;
}
// 创建存储压缩后的byte数组
byte[] huffmanCondeBytes = new byte[len];
int index = 0; // 记录第几个byte
for (int i = 0; i < stringBuilder.length(); i += 8) { // 因为每八位对应一个byte,所以步长+8
String strByte;
if (i + 8 > stringBuilder.length()) { // 不够八位
strByte = stringBuilder.substring(i);
} else {
strByte = stringBuilder.substring(i, i + 8);
}
// 将strByte转成数组放到
huffmanCondeBytes[index] = (byte) Integer.parseInt(strByte, 2);
index++;
}
return huffmanCondeBytes;
}
// 为了调用方便,我们重载getNodes
private static Map<Byte, String> getCodes(Node root) {
if (root == null) {
return null;
} else {
// 处理root左子树
getCodes(root.left, “0”, stringBuilder);
getCodes(root.right, “1”, stringBuilder);
}
return huffmanCodes;
}
/**
-
@param bytes 接收一个字节数组
-
@return 返回一个List 形式[Node{date=97,weight=5},Node{date=32,weight=9}…]
*/
private static List getNodes(byte[] bytes) {
// 1. 创建一个ArrayList
ArrayList nodes = new ArrayList<>();
// 2. 遍历bytes,统计每个byte出现的次数 => map
Map<Byte, Integer> counts = new HashMap<>();
for (byte b : bytes) {
// Map仍然没有该字符数据
counts.merge(b, 1, Integer::sum);
}
// 把每一个键值对转换为Node对象,加入nodes集合
for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
nodes.add(new Node(entry.getKey(), entry.getValue()));
}
return nodes;
}
/**
-
通过List创建对应赫夫曼树
-
@param nodes 需要创建赫夫曼树的所有节点组成的List
-
@return 赫夫曼树根节点
*/
private static Node createHuffmanTree(List nodes) {
while (nodes.size() > 1) {
// 从小到大
Collections.sort(nodes);
// 出去前两个点
Node leftNode = nodes.get(0);
Node rightNode = nodes.get(1);
// 创建一棵新的二叉树,它的根节点没有data,只有权值
Node parent = new Node(null, leftNode.weight + rightNode.weight);
parent.left = leftNode;
parent.right = rightNode;
// 将已经处理的两棵二叉树从nodes移出
nodes.remove(leftNode);
nodes.remove(rightNode);
// 将新的二叉树加入到nodes
nodes.add(parent);
}
// nodes最后剩下的结点就是哈夫曼树的根节点
return nodes.get(0);
}
// 生成的赫夫曼树对应的赫夫曼编码
// 思路:
// 1. 将赫夫曼编码表存放到一个Map<Byte,String>中
// 2. 在生成赫夫曼编码表时,需要拼接路径,所以创建一个StringBuilder,存储某个叶子节点的路径
static Map<Byte, String> huffmanCodes = new HashMap<Byte, String>();
static StringBuilder stringBuilder = new StringBuilder();
/**
-
功能:将传入的node结点的所有叶子结点的赫夫曼编码得到,并放入到huffmanCodes中
-
@param node 传入结点,默认根节点
-
@param code 路径:左子节点为0;右子节点为1
-
@param stringBuilder 用于拼接路径
*/
private static void getCodes(Node node, String code, StringBuilder stringBuilder) {
StringBuilder stringBuilder1 = new StringBuilder(stringBuilder);
// 将传入的code加入到stringBuilder1
stringBuilder1.append(code);
if (node != null) { // 如果node==null 不处理
// 判断当前node是叶子节点还是非叶子节点
if (node.data == null) {// 非叶子节点
// 递归处理
// 向左递归
getCodes(node.left, “0”, stringBuilder1);
// 向右递归
getCodes(node.right, “1”, stringBuilder1);
} else { // 叶子节点
// 就表示找到某个叶子节点
huffmanCodes.put(node.data, stringBuilder1.toString());
}
}
}
// 前序遍历方法
private static void preOrder(Node root) {
if (root != null) {
root.preOrder();
} else {
System.out.println(“该树为空!”);
}
}
}
class Node implements Comparable {
Byte data; // 存放数据(字符)本身,比如’a’ => 97, ’ ’ => 32
int weight; // 权值,表示字符出现次数
Node left;
Node right;
public Node(Byte data, int weight) {
this.data = data;
this.weight = weight;
}
@Override
public String toString() {
return “Node{” +
“data=” + data +
“, weight=” + weight +
‘}’;
}
@Override
public int compareTo(Node o) {
// 从小到大排序
return this.weight - o.weight;
}
// 前序遍历
public void preOrder() {
System.out.print(this.data + “:” + this.weight + " ");
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
}
================================================================
给你一个数列 (7, 3, 10, 12, 5, 1, 9),要求能够高效的完成对数据的查询和添加。
- 使用数组
数组未排序, 优点:直接在数组尾添加,速度快。 缺点:查找速度慢.
数组排序,优点:可以使用二分查找,查找速度快,缺点:为了保证数组有序,在添加新数据时,找到插入位置后,后面的数据需整体移动,速度慢。
-
使用链式存储-链表不管链表是否有序,查找速度都慢,添加数据速度比数组快,不需要数据整体移动。
-
使用二叉排序树
二叉排序树:BST: (Binary Sort(Search) Tree), 对于二叉排序树的任何一个非叶子节点,要求左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
特别说明:如果有相同的值,可以将该节点放在左子节点或右子节点
比如针对前面的数据 (7, 3, 10, 12, 5, 1, 9) ,对应的二叉排序树为:
一个数组创建成对应的二叉排序树,并使用中序遍历二叉排序树,比如: 数组为 Array(7, 3, 10, 12, 5, 1, 9) , 创建成对应的二叉排序树为 :
代码
package binarysourtree;
public class BinarySortTreeDemo {
public static void main(String[] args) {
int[] arr = {7, 3, 10, 12, 5, 1, 9};
BinarySortTree binarySortTree = new BinarySortTree();
for(int a:arr){
binarySortTree.add(new Node(a));
}
binarySortTree.infixOrder();
}
}
// 创建二叉排序树
class BinarySortTree {
private Node root;
// 添加节点的方法
public void add(Node node) {
if (root == null) { // 如果root为空,直接把node加上
root = node;
} else {
root.add(node);
}
}
// 中序遍历
public void infixOrder() {
if (root != null) {
root.infixOrder();
} else {
System.out.println(“二叉排序树为空”);
}
}
}
// 创建节点
class Node {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
// 添加节点方法
// 递归形式,需要满足二叉排序树
public void add(Node node) {
if (node == null) {
return;
}
// 判断传入节点的值和当前子树根节点值的关系
if (node.value < this.value) {
// 如果当前结点左子节点为null
if (this.left == null) {
this.left = node;
} else {
// 递归向左子树添加
this.left.add(node);
}
} else { // 添加的节点的值大于等于当前结点的值
// 如果当前结点右子节点为null
if (this.right == null) {
this.right = node;
} else {
// 递归向右子树添加
this.right.add(node);
}
}
}
@Override
public String toString() {
return “Node{” +
“value=” + value +
‘}’;
}
// 中序遍历
public void infixOrder() {
if (this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.infixOrder();
}
}
}
二叉排序树的删除情况比较复杂,有下面三种情况需要考虑
-
删除叶子节点 (比如:2, 5, 9, 12)
-
删除只有一颗子树的节点 (比如:1)
-
删除有两颗子树的节点. (比如:7, 3,10 )
思路分析
代码实现
package binarysourtree;
public class BinarySortTreeDemo {
public static void main(String[] args) {
int[] arr = {7, 3, 10, 12, 5, 1, 9};
BinarySortTree binarySortTree = new BinarySortTree();
for (int a : arr) {
binarySortTree.add(new Node(a));
}
System.out.println(“中序遍历二叉树”);
binarySortTree.infixOrder();
// 测试删除叶子节点
// binarySortTree.delNode(2);
// System.out.println(“删除节点后···”);
// binarySortTree.infixOrder();
// 测试删除只有一个子树的结点
// binarySortTree.delNode(1);
// System.out.println(“删除节点后· ··”);
// binarySortTree.infixOrder();
// 测试删除有两个子树的结点
binarySortTree.delNode(7);
System.out.println(“删除节点后· ··”);
binarySortTree.infixOrder();
}
}
// 创建二叉排序树
class BinarySortTree {
private Node root;
// 查找要删除的结点
public Node search(int value) {
if (root == null) {
return null;
} else {
return root.search(value);
}
}
// 查找要删除的结点的父节点
public Node searchParent(int value) {
if (root == null) {
return null;
} else {
return root.searchParent(value);
}
}
// 编写方法
// 1. 返回
/**
-
- 返回以node为根节点的二叉排序树的最小结点的值
-
- 删除以node为根节点的二叉排序树的最小结点
-
@param node 传入的结点(当做二叉排序树的根节点)
-
@return 返回的以node为根节点的二叉排序树的最小结点值
*/
public int delRightTreeMin(Node node) {
Node target = node;
// 循环查找左节点,就会找到最小值
while (target.left != null) {
target = target.left;
}
// 这是,target指向了最小的结点
delNode(target.value); // 删除最小结点
return target.value;
}
// 删除节点
public void delNode(int value) {
if (root == null) {
return;
} else {
// 1. 先去找到要删除的结点,targetNode
Node targetNode = search(value);
if (targetNode == null) {
// 如果没有找到要删除的结点
return;
}
// 如果发现当前这棵二叉排序树只有一个节点
if (root.left == null && root.right == null) {
root = null; // root置空
return;
}
// 2.去找到targetNode的父节点
Node parentNode = searchParent(value);
if (targetNode.left == null && targetNode.right == null) {// Ⅰ. 如果删除的结点是叶子节点(没有子树)
// 判断targetNode是父节点的左子节点还是右子节点
if (parentNode.left != null && parentNode.left.value == value) { // 是左子节点
parentNode.left = null;
} else if (parentNode.right != null && parentNode.right.value == value) { // 是右子节点
parentNode.right = null;
}
} else if (targetNode.left != null && targetNode.right != null) {// Ⅱ. 删除有两棵子树的结点
int minVal = delRightTreeMin(targetNode.right);
targetNode.value = minVal;
// 该方法是从右子树中找最小的结点
// 也可以从左子树中找最大的结点
} else {// Ⅲ. 删除只有一棵子树的结点
if (targetNode.left != null) {// 如果要删除的结点有左子节点
// 如果targetNode是parentNode的左子节点
if (parentNode.left.value == value) {
parentNode.left = targetNode.left;
} else {
// 如果targetNode是parentNode的右子节点
parentNode.right = targetNode.left;
}
} else {// 如果要删除的结点有右子节点
if (parentNode.left.value == value) {
parentNode.left = targetNode.right;
} else {
parentNode.right = targetNode.right;
}
}
}
}
}
// 添加节点的方法
public void add(Node node) {
if (root == null) { // 如果root为空,直接把node加上
root = node;
} else {
root.add(node);
}
}
// 中序遍历
public void infixOrder() {
if (root != null) {
root.infixOrder();
} else {
System.out.println(“二叉排序树为空”);
}
}
}
// 创建节点
class Node {
int value;
Node left;
Node right;
/**
-
查找要删除结点的父节点
-
@param value 要查找的值
-
@return 找到返回要删除节点父节点,找不到返回null
*/
public Node searchParent(int value) {
// 如果当前结点就是要删除节点的父节点,就返回
if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
return this;
} else {
// 如果查找的值小于当前的结点,并且当前结点的左子节点不为空
if (value < this.value && this.left != null) {
return this.left.searchParent(value); // 向左子树递归查找
} else if (value >= this.value && this.right != null) {
return this.right.searchParent(value); // 向右子树递归查找
} else {
return null; // 没有找到父节点
}
}
}
/**
-
查找要删除的结点
-
@param value 希望删除的结点的值
-
@return 如果找到返回该节点,否则返回null
*/
public Node search(int value) {
if (this.value == value) {
// 找到就是该节点
return this;
} else if (value < this.value) {
if (this.left != null) {
return this.left.search(value);
} else {
return null;
}
} else {
if (this.right == null) {
return null;
} else {
return this.right.search(value);
}
}
}
public Node(int value) {
this.value = value;
}
// 添加节点方法
// 递归形式,需要满足二叉排序树
public void add(Node node) {
if (node == null) {
return;
}
// 判断传入节点的值和当前子树根节点值的关系
if (node.value < this.value) {
// 如果当前结点左子节点为null
if (this.left == null) {
this.left = node;
} else {
// 递归向左子树添加
this.left.add(node);
}
} else { // 添加的节点的值大于等于当前结点的值
// 如果当前结点右子节点为null
if (this.right == null) {
this.right = node;
} else {
// 递归向右子树添加
this.right.add(node);
}
}
}
@Override
public String toString() {
return “Node{” +
“value=” + value +
‘}’;
}
// 中序遍历
public void infixOrder() {
if (this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.infixOrder();
}
}
}
================================================================
给你一个数列{1,2,3,4,5,6},要求创建一颗二叉排序树(BST), 并分析问题所在.
上图BST 存在的问题分析:
-
左子树全部为空,从形式上看,更像一个单链表.
-
插入速度没有影响
-
查询速度明显降低(因为需要依次比较), 不能发挥BST的优势,因为每次还需要比较左子树,其查询速度比单链表还慢
-
解决方案-平衡二叉树(AVL)
-
平衡二叉树也叫平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树, 可以保证查询效率较高。
-
具有以下特点:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等。
-
举例说明, 看看下面哪些AVL树, 为什么?
要求: 给你一个数列,创建出对应的平衡二叉树.数列 {4,3,6,5,7,8}
思路分析(示意图)
代码实现
// 结点左旋转
private void leftRotate() {
// 1. 创建一个新的结点newNode,值等于当前根结点的值
Node newNode = new Node(value);
// 2. 把新结点的左子树设置成当前结点的左子树
newNode.left = left;
// 3. 把新结点的右子树设置成当前结点的右子树的左子树
newNode.right = right.left;
// 4. 把当前结点的值换成右子结点的值
value = right.value;
// 5. 把当前结点的右子树设置成当前结点右子树的右子树
right = right.right;
// 6. 把当前结点的左子树设置成最开始创建的新结点
left = newNode;
}
要求: 给你一个数列,创建出对应的平衡二叉树.数列 {10,12, 8, 9, 7, 6}
思路分析(示意图)
代码实现
// 结点右旋转
private void rightRotate() {
// 1. 创建一个新的结点newNode,值等于当前根结点的值
Node newNode = new Node(value);
// 2. 把新结点的右子树设置成当前结点的右子树
newNode.right = right;
// 3. 把新结点的左子树设置成当前结点的左子树的右子树
newNode.left = left.right;
// 4. 把当前结点的值换成左子结点的值
value = left.value;
// 5. 把当前结点的左子树设置成当前结点左子树的左子树
left = left.left;
// 6. 把当前结点的右子树设置成最开始创建的新结点
right = newNode;
}
前面的两个数列,进行单旋转(即一次旋转)就可以将非平衡二叉树转成平衡二叉树,但是在某些情况下,单旋转不能完成平衡二叉树的转换。比如数列
-
int[] arr = { 10, 11, 7, 6, 8, 9 }; 运行原来的代码可以看到,并没有转成 AVL树.
-
int[] arr = {2,1,6,5,7,3}; // 运行原来的代码可以看到,并没有转成 AVL树
双旋转分析
平衡二叉树完整代码
双旋转主要体现在每次添加节点的时候,根据左右子树判断如何旋转,在调整前,需要根据左右子树的左右子树高度情况判断是否需要对子树的子树进行旋转。即结点对象Node的add方法中后两个if语句中的if判断语句。
package avl;
import java.util.Map;
public class AVLTreeDemo {
public static void main(String[] args) {
// int[] arr = {4, 3, 6, 5, 7, 8};
// int[] arr = {10, 12, 8, 9, 7, 6};
int[] arr = {10, 11, 7, 6, 8, 9};
// 创建一个AVLTree对象
AVLTree avlTree = new AVLTree();
// 添加结点
for (int a : arr) {
avlTree.add(new Node(a));
}
// 遍历
System.out.println(“中序遍历···”);
avlTree.infixOrder();
System.out.println(“平衡处理后~”);
System.out.println(“树的高度:” + avlTree.getRoot().height());
System.out.println(“树的左子树高度:” + avlTree.getRoot().leftHeight());
System.out.println(“树的右子树高度:” + avlTree.getRoot().rightHeight());
}
}
// 创建AVLTree
class AVLTree {
private Node root;
public Node getRoot() {
return root;
}
// 查找要删除的结点
public Node search(int value) {
if (root == null) {
return null;
} else {
return root.search(value);
}
}
// 查找要删除的结点的父结点
public Node searchParent(int value) {
if (root == null) {
return null;
} else {
return root.searchParent(value);
}
}
// 编写方法
// 1. 返回
/**
-
- 返回以node为根结点的二叉排序树的最小结点的值
-
- 删除以node为根结点的二叉排序树的最小结点
-
@param node 传入的结点(当做二叉排序树的根结点)
-
@return 返回的以node为根结点的二叉排序树的最小结点值
*/
public int delRightTreeMin(Node node) {
Node target = node;
// 循环查找左结点,就会找到最小值
while (target.left != null) {
target = target.left;
}
// 这是,target指向了最小的结点
delNode(target.value); // 删除最小结点
return target.value;
}
// 删除结点
public void delNode(int value) {
if (root == null) {
return;
} else {
// 1. 先去找到要删除的结点,targetNode
Node targetNode = search(value);
if (targetNode == null) {
// 如果没有找到要删除的结点
return;
}
// 如果发现当前这棵二叉排序树只有一个结点
if (root.left == null && root.right == null) {
root = null; // root置空
return;
}
// 2.去找到targetNode的父结点
Node parentNode = searchParent(value);
if (targetNode.left == null && targetNode.right == null) {// Ⅰ. 如果删除的结点是叶子结点(没有子树)
// 判断targetNode是父结点的左子结点还是右子结点
if (parentNode.left != null && parentNode.left.value == value) { // 是左子结点
parentNode.left = null;
} else if (parentNode.right != null && parentNode.right.value == value) { // 是右子结点
parentNode.right = null;
}
} else if (targetNode.left != null && targetNode.right != null) {// Ⅱ. 删除有两棵子树的结点
int minVal = delRightTreeMin(targetNode.right);
targetNode.value = minVal;
// 该方法是从右子树中找最小的结点
// 也可以从左子树中找最大的结点
} else {// Ⅲ. 删除只有一棵子树的结点
if (targetNode.left != null) {// 如果要删除的结点有左子结点
if (parentNode != null) {
// 如果targetNode是parentNode的左子结点
if (parentNode.left.value == value) {
parentNode.left = targetNode.left;
} else {
// 如果targetNode是parentNode的右子结点
parentNode.right = targetNode.left;
}
} else {
root = targetNode.left;
}
} else {// 如果要删除的结点有右子结点
if (parentNode != null) {
if (parentNode.left.value == value) {
parentNode.left = targetNode.right;
} else {
parentNode.right = targetNode.right;
}
} else {
root = targetNode.right;
}
}
}
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
(targetNode == null) {
// 如果没有找到要删除的结点
return;
}
// 如果发现当前这棵二叉排序树只有一个结点
if (root.left == null && root.right == null) {
root = null; // root置空
return;
}
// 2.去找到targetNode的父结点
Node parentNode = searchParent(value);
if (targetNode.left == null && targetNode.right == null) {// Ⅰ. 如果删除的结点是叶子结点(没有子树)
// 判断targetNode是父结点的左子结点还是右子结点
if (parentNode.left != null && parentNode.left.value == value) { // 是左子结点
parentNode.left = null;
} else if (parentNode.right != null && parentNode.right.value == value) { // 是右子结点
parentNode.right = null;
}
} else if (targetNode.left != null && targetNode.right != null) {// Ⅱ. 删除有两棵子树的结点
int minVal = delRightTreeMin(targetNode.right);
targetNode.value = minVal;
// 该方法是从右子树中找最小的结点
// 也可以从左子树中找最大的结点
} else {// Ⅲ. 删除只有一棵子树的结点
if (targetNode.left != null) {// 如果要删除的结点有左子结点
if (parentNode != null) {
// 如果targetNode是parentNode的左子结点
if (parentNode.left.value == value) {
parentNode.left = targetNode.left;
} else {
// 如果targetNode是parentNode的右子结点
parentNode.right = targetNode.left;
}
} else {
root = targetNode.left;
}
} else {// 如果要删除的结点有右子结点
if (parentNode != null) {
if (parentNode.left.value == value) {
parentNode.left = targetNode.right;
} else {
parentNode.right = targetNode.right;
}
} else {
root = targetNode.right;
}
}
}
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-TnIlVXzH-1710827667320)]
[外链图片转存中…(img-7uKUIWYY-1710827667321)]
[外链图片转存中…(img-uemrkieq-1710827667321)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-c55bNYaZ-1710827667322)]