二叉树,顺序存储二叉树,线索化二叉树,二叉排序树,AVL树,赫夫曼树(文件压缩,解压)

二叉树

二叉树:每个节点最多只用两个节点的树。

二叉树的三种遍历方式

前序遍历:先找到根节点,然后左节点,最后右节点。
在这里插入图片描述
中序遍历:先左节点,根节点,右节点。
在这里插入图片描述
后序遍历:先左节点,右节点,根节点。
在这里插入图片描述
JAVA代码实现:

// 创建节点
@Data
class Node {
	// 存储数据
	private int no;
	// 左右结点
	private Node left;
	private Node right;

	public Node(int no) {
		super();
		this.no = no;
	}

	// 前序遍历
	public void preOrder() {
		// 先输出根节点
		System.out.println(this.no);
		// 左节点不为null,一直递归左节点
		if (this.left != null) {
			this.left.preOrder();
		}
		// 右节点不为null,一直递归右节点
		if (this.right != null) {
			this.right.preOrder();
		}
	}

	// 中序
	public void infixOrder() {
		// 先递归到左节点结束
		if (this.left != null)
			this.left.infixOrder();
		// 在输出
		System.out.println(this.no);
		// 递归右结点
		if (this.right != null)
			this.right.infixOrder();
	}

	// 后序,最后输出节点
	public void postOrder() {
		if (this.left != null)
			this.left.postOrder();
		if (this.right != null)
			this.right.postOrder();
		System.out.println(this.no);
	}

	// 前序查找
	public Node preOrderSearch(int no) {
		// 如果当前结点等与查找节点.返回
		if (this.no == no) {
			return this;
		}
		// 定义节点接收结果
		Node resNode = null;
		// 向左递归
		if (this.left != null) {
			resNode = this.left.preOrderSearch(no);
		}
		// 如果结果结点不为null,证明已经找到,直接返回
		if (resNode != null) {
			return resNode;
		}
		// 向右递归
		if (this.right != null) {
			resNode = this.right.preOrderSearch(no);
		}
		// 返回结果
		return resNode;
	}

	// 中序查找
	public Node infixOrderSearch(int no) {
		// 返回结果
		Node resNode = null;
		// 向左递归
		if (this.left != null) {
			resNode = this.left.infixOrderSearch(no);
		}
		// 结果不为null,返回
		if (resNode != null) {
			return resNode;
		}
		// 判断是否找到
		if (this.no == no) {
			return this;
		}
		// 向右递归
		if (this.right != null) {
			resNode = this.right.infixOrderSearch(no);
		}
		return resNode;
	}

	// 后序查找
	public Node postOrderSearch(int no) {
		// 返回结果
		Node resNode = null;
		if (this.left != null) {
			resNode = this.left.postOrderSearch(no);
		}
		// 结果不为null,返回
		if (resNode != null) {
			return resNode;
		}
		if (this.right != null) {
			resNode = this.right.postOrderSearch(no);
		}
		// 结果不为null,返回
		if (resNode != null) {
			return resNode;
		}
		// 判断是否找到
		if (this.no == no) {
			return this;
		}
		return resNode;
	}

	// 删除节点,只是简单删除,删除结点下所有结点
	public void delNode(int no) {
		if (this.left != null && this.left.no == no) {
			this.left = null;
			return;
		}
		if (this.right != null && this.right.no == no) {
			this.right = null;
			return;
		}
		if (this.left != null) {
			this.left.delNode(no);
		}
		if (this.right != null) {
			this.right.delNode(no);
		}
	}
}
@Data
/**
 * 
 * 二叉树,定义一个root结点,调用方法就可以了
 *
 */
class BinaryTree {
	private Node root;

	// 前序
	public void preOrder() {
		if (root != null)
			root.preOrder();
	}

	// 中序
	public void infixOrder() {
		if (root != null)
			root.infixOrder();
	}

	// 后序
	public void postOrder() {
		if (root != null)
			root.postOrder();
	}

	// 前序查找
	public Node preOrderSearch(int no) {
		if (root == null)
			return null;
		return root.preOrderSearch(no);
	}

	// 中序查找
	public Node infixOrderSearch(int no) {
		if (root == null)
			return null;
		return root.infixOrderSearch(no);
	}

	// 后序查找
	public Node postOrderSearch(int no) {
		if (root == null)
			return null;
		return root.postOrderSearch(no);
	}

	// 删除节点
	public void delNode(int no) {
		if (root != null) {
			if (root.getNo() == no) {
				root = null;
			} else {
				root.delNode(no);
			}
		} else {
			System.out.println("不能删除 ");
		}
	}

}

// 手动创建树
BinaryTree binaryTree = new BinaryTree();
		Node node1 = new Node(1);
		Node node2 = new Node(2);
		Node node3 = new Node(3);
		Node node4 = new Node(4);
		Node node5 = new Node(5);
		node3.setLeft(node5);
		node1.setLeft(node2);
		node1.setRight(node3);
		node3.setRight(node4);
		binaryTree.setRoot(node1);

上面只是简单模拟了一下二叉树的遍历方式和查找。

顺序存储二叉树

顺序存储二叉树:用数组的方式来模拟二叉树。
左节点:2 * index + 1
右节点:2 * index + 2
这样就可以模拟二叉树了。
JAVA代码实现:

@Data
@AllArgsConstructor
class ArrBinaryTree {
	private int[] arr;

	// 模拟实现前序遍历
	public void preOrder() {
		this.preOrder(0);
	}

	/**
	 * 
	 * @param index 数组的下标
	 */
	private void preOrder(int index) {
		if (arr == null || arr.length == 0)
			return;
		System.out.println(arr[index]);
		// 向左递归
		if ((2 * index + 1) < arr.length)
			preOrder(2 * index + 1);
		// 向右递归
		if ((2 * index + 2) < arr.length)
			preOrder(2 * index + 2);
	}

}

线索化二叉树

在这里插入图片描述
如上图,二叉树会存在null节点没有利用到,为了有效利用这些节点,会根据选择遍历的方式,将left节点指向它的前驱节点,right节点指向后继节点。
遍历方式有前序,中序,后序,所以有了三种线索化二叉树。

如上图:前序遍历顺序为5 -> 3 -> 8,3的前驱节点为5,所以将它的左节点指向5,右节点指向8,8的左节点指向3。这些节点就是线索,这样就完成了前序线索化二叉树。
JAVA代码实现:

// 先定义Node节点
@Data
class Node {
	private int no;
	// 左节点
	private Node left;
	// 右节点
	private Node right;
	// 1. 如果leftType == 0 表示指向的是左子树, 如果 1 则表示指向前驱结点
	// 2. 如果rightType == 0 表示指向是右子树, 如果 1表示指向后继结点
	private int leftType;
	private int rightType;

	public Node(int no) {
		this.no = no;
	}
	@Override
	public String toString() {
		return "Node [no=" + no + "]";
	}
}
	/**
	 * 中序线索化二叉树
	 * 
	 * @param node 就是当前需要线索化的结点
	 */
	public void threadedNodes(Node node) {
		// 如果node==null, 不能线索化
		if (node == null) {
			return;
		}
		// (一)先线索化左子树
		threadedNodes(node.getLeft());
		// 线索化当前结点,当它的left为null时,可以线索化,将pre结点设置为left
		if (node.getLeft() == null) {
			// 让当前结点的左指针指向前驱结点
			node.setLeft(pre);
			// 修改当前结点的左指针的类型,指向前驱结点
			node.setLeftType(1);
		}
		// 处理后继结点,当pre不为null,且right为null时,进行线索化
		// 举例:  5
		//     3   8 的树,5进来,获取3进来,3进来继续向左,为null,方法结束
		// 回到3的结点方法,left为null,设置left = pre,
		// pre == null,不满足,pre = 3,获取右节点,为null,返回到5结点的方法,
		// 上面的都不满足,pre当前为3结点,right结点为null node结点为5,于是pre设置
		// 右节点就是3设置后继节点,也就是5,然后pre又等于5,8进来,设置left
		if (pre != null && pre.getRight() == null) {
			// 让前驱结点的右指针指向当前结点
			pre.setRight(node);
			// 修改前驱结点的右指针类型
			pre.setRightType(1);
		}
		// 每处理一个结点后,让当前结点是下一个结点的前驱结点
		pre = node;
		// (三)在线索化右子树
		threadedNodes(node.getRight());
	}

	// 遍历中序线索化二叉树的方法
	public void threadedList() {
		// 定义一个变量,存储当前遍历的结点,从root开始
		Node node = root;
		while (node != null) {
			// 循环的找到leftType == 1的结点,就得到最后的左节点
			while (node.getLeftType() == 0) {
				node = node.getLeft();
			}
			// 打印当前这个结点
			System.out.println(node);
			// 如果当前结点的右指针指向的是后继结点,就一直输出
			while (node.getRightType() == 1) {
				// 获取到当前结点的后继结点
				node = node.getRight();
				System.out.println(node);
			}
			// 后继节点遍历完后,回到了正常的节点,重新赋值
			node = node.getRight();

		}
	}
	// 前序后序都跟中序差不多
	// 后序线索化
	public void postThreadNodes() {
		postThreadNodes(root);
	}

	private void postThreadNodes(Node node) {
		if (node == null) return;
			postThreadNodes(node.getLeft());
			postThreadNodes(node.getRight());
		if (node.getLeft() == null) {
			node.setLeft(pre);
			node.setLeftType(1);
		}
		if (pre != null && pre.getRight() == null) {
			pre.setRight(node);
			pre.setRightType(1);
		}
		pre = node;
	}

	// 后序线索化遍历
	public void postThreadList() {
		Node temp = root.getLeft();
		while (temp != root) {
			while (temp.getLeft() != null && temp.getLeftType() == 0) {
				temp = temp.getLeft();
			}
			while (temp.getRightType() == 1) {
				System.out.println(temp);
				temp = temp.getRight();
			}
			System.out.println(temp);
			if (temp != root)
				temp = root.getRight();
		}

	}

	// 前序线索化
	public void preThreadedNodes() {
		this.preThreadedNodes(root);
	}
	private void preThreadedNodes(Node node) {
		if (node == null)
			return;
		if (node.getLeft() == null) {
			node.setLeft(pre);
			node.setLeftType(1);
		}
		if (pre != null && pre.getRight() == null) {
			pre.setRight(node);
			pre.setRightType(1);
		}
		pre = node;
		// 先执行的设置结点,需要进行一下判断,不然会死循环
		if (node.getLeftType() != 1) {
			preThreadedNodes(node.getLeft());
		}
		if (node.getRightType() != 1) {
			preThreadedNodes(node.getRight());
		}
	}

	// 前序线索化遍历
	public void preThreadList() {
		Node temp = root;
		while (temp.getLeftType() != 1) {
			System.out.println(temp);
			temp = temp.getLeft();
		}
		while (temp != null) {
			System.out.println(temp);
			temp = temp.getRight();
		}
	}
}

二叉排序树

二叉排序树:按照左节点<根节点<右节点进行排序的树。
添加:判断value与当前结点的大小,找到添加位置左右结点为null的地方进行添加。
重点删除的方法,删除会有三种情况:
1.删除结点左右结点为null,直接找到它的父节点,判断是它父节点的左节点还是右节点,找到后直接赋为null。根节点的话直接将根节点删除即可。
2.如果删除结点有左节点和右节点,两种方式处理,第一种找到它右节点的最大左节点,删除这个节点,并将这个节点的值赋值给需要删除的节点的值。第二种找它左节点的最大右节点,操作同上。
在这里插入图片描述
3.删除节点只有一个左节点或者右节点,先判断它的父节点是否为null,如果为null,直接将根节点赋值为存在的那个节点。判断删除节点是有左节点还是右节点,然后在判断是父节点的左节点还是右节点,然后就将对应的父节点的左或右节点重新赋值为删除节点的左或右节点。

// 结点
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 {
			// 大于走右节点
			if (this.right == null) {
				this.right = node;
			} else {
				this.right.add(node);
			}
		}
	}

	// 查找要删除结点的父节点
	public Node searchParent(int value) {
		// 判断是不是当前结点的左右结点
		if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
			return this;
		} else {
			// 查找的值小于当前值,左节点不为null,向左递归查找
			if (value < this.value && this.left != null) {
				return this.left.searchParent(value);
			} else if (this.right != null && value >= this.value) {
				// 大于找右节点
				return this.right.searchParent(value);
			}
		}
		return null;
	}

	// 查找要删除的结点
	public Node search(int value) {
		// 当前结点是否等于查找的结点
		if (value == this.value)
			return this;
		// 向左向右递归查找
		if (value < this.value) {
			if (this.left != null)
				return this.left.search(value);
		} else {
			if (this.right != null)
				return this.right.search(value);
		}
		return null;
	}

	// 中序
	public void infixOrder() {
		if (this.left != null) {
			this.left.infixOrder();
		}
		System.out.println(this);
		if (this.right != null) {
			this.right.infixOrder();
		}
	}

	@Override
	public String toString() {
		return "Node [value=" + value + "]";
	}

}

// 二叉排序树
class BinarySortTree {
	// 根节点
	private Node root;

	// 添加节点的方法
	public void add(Node node) {
		if (root == null) {
			root = node;
		} else {
			root.add(node);
		}
	}

	// 查找要删除的结点
	public Node search(int value) {
		if (root == null)
			return null;
		return root.search(value);
	}

	// 查找父结点
	public Node searchParent(int value) {
		if (root == null)
			return null;
		return root.searchParent(value);
	}

	// 删除结点
	public void delNode(int value) {
		if (root == null)
			return;
		// 找到要删除的结点
		Node searchNode = search(value);
		if (searchNode == null)
			return;
		// 如果根节点没有左右结点,直接把根节点删除
		if (root.left == null && root.right == null) {
			root = null;
			return;
		}
		// 在去查找删除节点的父节点
		Node parent = searchParent(value);
		// 判断删除节点是不是叶子结点
		if (searchNode.left == null && searchNode.right == null) {
			// 判断是父节点的左子节点还是右子结点
			if (parent.left != null && parent.left.value == value) {
				// 是父节点的左子结点,直接删除
				parent.left = null;
			} else if (parent.right != null && parent.right.value == value) {
				// 父节点的右子结点
				parent.right = null;
			}
		} else if (searchNode.left != null && searchNode.right != null) {
			// 判断有没有左右子结点
			// 有的话调用删除当前删除节点的右节点的最小左节点的方法,该方法返回删除节点的值
			int minValue = delRightTreeMin(searchNode.right);
			// 将值赋值给删除节点
			searchNode.value = minValue;
		} else {
			// 只用一个节点的情况,父节点为null,证明是根节点,将根节点赋值为有值的那个节点
			if (parent == null) {
				root = root.left == null ? root.right : root.left;
				return;
			}
			// 删除的结点有左子节点
			if (searchNode.left != null) {
				// 是父节点的左节点,把父节点的左节点赋值为删除节点的左节点
				if (parent.left != null && parent.left.value == value) {
					parent.left = searchNode.left;
				} else {
					// 父节点的右节点
					parent.right = searchNode.left;
				}
			} else {
				// 删除节点有右子结点
				if (parent.left != null && parent.left.value == value) {
					parent.left = searchNode.right;
				} else {
					parent.right = searchNode.right;
				}
			}
		}
	}

	// 删除右节点的最小左节点的方法
	public int delRightTreeMin(Node node) {
		Node temp = node;
		// 循环查找左结点 找到最小值
		while (temp.left != null) {
			temp = temp.left;
		}
		// 然后调用方法删除该节点
		delNode(temp.value);
		// 返回删除的节点值
		return temp.value;
	}

	public void infixOrder() {
		if (root != null) {
			root.infixOrder();
		} else {
			System.out.println("null");
		}
	}
}

AVL树

AVL树:平衡二叉树,上面的排序二叉树,在某些极端的情况下,树会退化成链表结构,这样使得查询效率大大下降,所以有了AVL树。左子树和右子树的高度差不超过1的树。
通过树的左旋或者右旋,来实现树的平衡。
左旋:
黑色部分为原来的二叉树
右旋跟左旋差不多,只是找左节点的右节点.
JAVA代码实现:

// avl树
class AVLTree {
	private Node root;

	public Node getRoot() {
		return this.root;
	}

	public void add(Node node) {
		if (root == null) {
			root = node;
		} else {
			root.add(node);
		}
	}

	public void infixOrder() {
		if (root != null) {
			root.infixOrder();
		} else {
			System.out.println("null");
		}
	}
}

// 节点
class Node {
	int value;
	Node left;
	Node right;

	public Node(int value) {
		this.value = value;
	}

	// 是否左旋或者右旋的判断条件是高度差是否超过1,所以需要知道树的高度
	public int height() {
		// 向左向右递归+1取最大值就是树的高度
		return Math.max(left == null ? 0 : left.height(), 
				right == null ? 0 : right.height()) + 1;
	}

	// 返回左子树的高度
	public int leftHeight() {
		if (left == null) {
			return 0;
		}
		return left.height();
	}

	// 右子树高度
	public int rightHeight() {
		if (right == null)
			return 0;
		return right.height();
	}

	// 左旋
	private void leftRotate() {
		int currentValue = value;
        Node tempRight = right;
        value = right.value;
        right = right.right;
        tempRight.value = currentValue;
        tempRight.left = left;
        tempRight.right = tempRight.left;
        left = tempRight;
	}

	// 右旋
	private void rightRotate() {
		int currentValue = value;
        Node tempLeft = left;
        value = left.value;
        left = left.left;
        tempLeft.value = currentValue;
        tempLeft.left = tempLeft.right;
        tempLeft.right = right;
        right = tempLeft;
	}

	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 (rightHeight() - leftHeight() > 1) {
			// 如果当前节点的右子树的左子树大于当前节点的右子树的右子树高度,需要先进行右旋
			// 因为左旋的时候是将当前结点的右子树的左节点移到左边
			// 如果高度大于了右节点右子树的高度,移动过去的话又会变成不平衡了
			if (right.leftHeight() > right.rightHeight()) {
				right.rightRotate();
			}
			// 左旋
			leftRotate();
			return;
		}
		// 右旋
		if (leftHeight() - rightHeight() > 1) {
			// 如果当前节点的左子树的右子树大于当前节点的左子树的左子树高度,需要先进行左旋
			if (left.rightHeight() > left.leftHeight()) {
				// 先对左子树进行左旋
				left.leftRotate();
			}
			rightRotate();
		}
	}

	// 中序
	public void infixOrder() {
		if (this.left != null) {
			this.left.infixOrder();
		}
		System.out.println(this);
		if (this.right != null) {
			this.right.infixOrder();
		}
	}

	@Override
	public String toString() {
		return "Node [value=" + value + "]";
	}

}

赫夫曼树

赫夫曼树:将权值重的节点放在离根节点进的地方,权值低的节点放在里根节点远的位置。
将一个数组构建成赫夫曼树步骤:
在这里插入图片描述
JAVA代码实现

/**
 * 
 * 用list集合,需要实现排序
 *
 */
class Node implements Comparable<Node> {
	// 数据
	int value;
	Node left;
	Node right;

	// 前序遍历
	public void preOrder() {
		System.out.println(this);
		if (this.left != null) {
			this.left.preOrder();
		}
		if (this.right != null) {
			this.right.preOrder();
		}
	}

	public Node(int value) {
		this.value = value;
	}

	@Override
	public String toString() {
		return "Node [value=" + value + "]";
	}

	@Override
	public int compareTo(Node o) {
		// 比较大小的方式
		return this.value - o.value;
	}

}
// 创建赫夫曼树的方法
	public static Node createHuffmanTree(int[] arr) {
		// 结点集合
		List<Node> nodes = new ArrayList<>();
		// 将传入的数组转为node节点
		for (int val : arr) {
			nodes.add(new Node(val));
		}
		// 当集合的大小大于1,一直循环
		while (nodes.size() > 1) {
			// 排序
			Collections.sort(nodes);
			// 取出权值最小的两颗二叉树
			Node left = nodes.get(0);
			Node right = nodes.get(1);
			// 创建父节点
			Node parent = new Node(left.value + right.value);
			// 父节点的左右结点分别是上面的左右节点
			parent.left = left;
			parent.right = right;
			// 集合中删除这两个节点
			nodes.remove(left);
			nodes.remove(right);
			// 把父节点加入进去
			nodes.add(parent);
		}
		return nodes.get(0);
	}
	// 前序遍历
	public static void preOrder(Node root) {
		if (root != null) {
			root.preOrder();
		}
	}
}

赫夫曼树实践——赫夫曼编码,实现文件压缩解压

赫夫曼编码:统计字符出现的次数,构建赫夫曼树,规定赫夫曼树向左编码0,向右编码1,从根节点到叶子节点经过的路径,即为赫夫曼编码。以上面的赫夫曼树为例:
在这里插入图片描述
JAVA代码实现:

编码

第一步:先构建一个带权节点

// 有数据 权值 使用集合的排序方法,实现一个内部比较器
class Node implements Comparable<Node> {
	Byte data;
	int weight;
	Node left;
	Node right;

	public Node(Byte data, int weight) {
		super();
		this.data = data;
		this.weight = weight;
	}

	@Override
	public int compareTo(Node o) {
		return this.weight - o.weight;
	}

	@Override
	public String toString() {
		return "Node [data=" + data + ", weight=" + weight + "]";
	}

}

第二步:统计数组中相同元素出现的次数,获取node集合

/**
	 * 统计统计数组中相同元素出现的次数,获取node集合
	 * @param bytes 需要进行统计的数组
	 * @return node集合
	 */
	private static List<Node> getNodes(byte[] bytes) {
		List<Node> list = new ArrayList<>();
		// 使用map来统计相同的元素,第一次出现put进去,第二次+1
		Map<Byte, Integer> counts = new HashMap<>();
		for (byte b : bytes) {
			Integer count = counts.get(b);
			if (count == null) {
				counts.put(b, 1);
			} else {
				counts.put(b, count + 1);
			}
		}
		// 遍历map集合,创建节点
		for (Map.Entry<Byte, Integer> entry : counts.entrySet()) {
			list.add(new Node(entry.getKey(), entry.getValue()));
		}
		return list;
	}

第三步:创建赫夫曼树

/**
	 * 创建赫夫曼树
	 * 
	 * @param nodes node集合
	 * @return 根节点
	 */
	private static Node createHuffmanTree(List<Node> nodes) {
		while (nodes.size() > 1) {
			Collections.sort(nodes);
			Node left = nodes.get(0);
			Node right = nodes.get(1);
			Node parent = new Node(null, left.weight + right.weight);
			parent.left = left;
			parent.right = right;
			nodes.remove(left);
			nodes.remove(right);
			nodes.add(parent);
		}
		return nodes.get(0);
	}

第四步:创建一个map,生成赫夫曼编码

// 生成霍夫曼编码
	static Map<Byte, String> huffmanCode = new HashMap<>();

	/**
	 * 将传入的node结点的所有叶子结点的赫夫曼编码,并放入map集合
	 * 
	 * @param node 传入结点
	 * @param code 左子结点是0 右子结点是1
	 * @param sb 编码
	 */
	private static void getCodes(Node node, String code, StringBuilder sb) {
		StringBuilder sb2 = new StringBuilder(sb);
		sb2.append(code);
		if (node != null) {
			// 节点的数据为null,一直向下递归到叶子节点,传入编码0或1,和当前编码
			if (node.data == null) {
				getCodes(node.left, "0", sb2);
				getCodes(node.right, "1", sb2);
			} else {
				// 到叶子节点,把他放入map中
				huffmanCode.put(node.data, sb2.toString());
			}
		}
	}

第5步:压缩数组

/**
	 * 将对应的byte数组 通过生成赫夫曼编码,返回一个压缩后的byte
	 * 
	 * @param bytes 需要进行压缩的数组
	 * @param huffmanCode 编码表
	 * @return 压缩后的数组
	 */
	private static byte[] zip(byte[] bytes, Map<Byte, String> huffmanCode) {
		// 利用赫夫曼编码表将bytes数组转成对应的字符串
		StringBuilder sb = new StringBuilder();
		for (byte b : bytes) {
			sb.append(huffmanCode.get(b));
		}
		// 统计返回的数组长度
		int len = (sb.length() + 7) / 8;
		// 创建存储压缩后的byte数组,长度不一定刚好八位,所以多存储1位来存最后一位的长度
		byte[] by = new byte[len + 1];
		for (int i = 0, j = 0; i < sb.length(); i += 8, j++) {
			// 截取8位长度,最后一位截取到最后长度
			String string = sb.substring(i, i + 8 > sb.length() ? sb.length() : i + 8);
			// 以2进制转换成byte
			by[j] = (byte) Integer.parseInt(string, 2);
		}
		// 数组最后存一下截取长度
		by[by.length - 1] = (byte) (sb.length() % 8);
		return by;
	}

第6步:对上面5步的方法进行封装

/**
	 * 封装前面的方法
	 * 
	 * @param bytes 待压缩的数组
	 * @return 压缩完成后的数组
	 */
	public static byte[] huffmanZip(byte[] bytes) {
		// 获取node结点
		List<Node> nodes = getNodes(bytes);
		// 创建赫夫曼树
		Node huffmanTree = createHuffmanTree(nodes);
		// 第一个编码
		String st = "";
		// 如果树只有根节点,第一个编码直接0
		if (huffmanTree.left == null && huffmanTree.right == null) {
			st = "0";
		}
		// 获取编码表
		getCodes(huffmanTree, st, new StringBuilder());
		// 返回压缩后的字节数组
		return zip(bytes, huffmanCode);
	}

解码

第一步:将byte转换成二进制字符串

/**
	 * 转换成字节字符串
	 * @param b 需要转换的数字
	 * @param len 截取的长度
	 * @return 二进制字符串
	 */
	private static String byteToBitString(byte b, byte len) {
		int temp = b;
		// 使用的Integer中转成二进制字符串的方法,对于高位0是截取掉的
		// 而编码中会有0开头编码出现的情况,所以会造成编码缺失
		// 所以将第9位置为1,这样字符串出来就不会出现问题了
		temp |= 256;
		String binaryString = Integer.toBinaryString(temp);
		// 返回截取的字符串长度,除了最后一个都是截取8,最后一个不一定是8位
		return binaryString.substring(binaryString.length() - len);
	}

第二步:进行解码

	/**
	 * 解码
	 * 
	 * @param huffmanCodes 编码表
	 * @param huffmanBytes 压缩的数组
	 * @return
	 */
	private static byte[] decode(Map<Byte, String> huffmanCodes, byte[] huffmanBytes) {
		// 先得到对应的二进制的字符串
		StringBuilder sb = new StringBuilder();
		// 将数组转换成编码字符串
		for (int i = 0; i < huffmanBytes.length - 1; i++) {
			// 调用转换二进制字符串的方法,传入截取长度
			sb.append(byteToBitString(huffmanBytes[i],
					// 到数组下标的倒数第二个的时候,使用数组最后一位进行截取,其余用8
					i == huffmanBytes.length - 2 ? huffmanBytes[huffmanBytes.length - 1] : (byte) 8));
		}
		// 将赫夫曼编码进行反转,key存编码,value存字节
		Map<String, Byte> map = new HashMap<>();
		for (Map.Entry<Byte, String> entry : huffmanCodes.entrySet()) {
			map.put(entry.getValue(), entry.getKey());
		}
		// 创建集合 存放byte
		List<Byte> list = new ArrayList<>();
		// 循环遍历字符串
		for (int i = 0; i < sb.length();) {
			// 计下标
			int count = 1;
			// 循环开关
			boolean flag = true;
			// 获取到的编码
			Byte b = null;
			while (flag) {
				// 从map中一直获取,当b一直为null的时候,计算下标++,一直获取到编码对应key
				String key = sb.substring(i, i + count);
				b = map.get(key);
				if (b == null) {
					count++;
				} else {
					flag = false;
				}
			}
			// 加入集合中
			list.add(b);
			// 数组下标后移count位
			i += count;
		}
		// 将集合中的字节放入自己数组中
		byte[] b = new byte[list.size()];
		for (int i = 0; i < b.length; i++) {
			b[i] = list.get(i);
		}
		return b;
	}

压缩解码就完成了。

文件压缩

是对字节数组进行压缩的,文件也可以转成字节数组,所以可以进行压缩。
JAVA代码实现:

/**
	 * 
	 * @param srcFile 需要压缩的文件的路径
	 * @param dstFile 我们压缩后的文件放的位置
	 */
	public static void zipFile(String srcFile, String dstFile) {
		try {
			FileInputStream fis = new FileInputStream(srcFile);
			// 一次性将文件读取到字节数组中
			byte[] b = new byte[fis.available()];
			fis.read(b);
			fis.close();
			// 压缩
			byte[] huffmanZip = huffmanZip(b);
			FileOutputStream fos = new FileOutputStream(dstFile);
			// 用对象流将压缩的数组,和编码表存进文件中
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(huffmanZip);
			oos.writeObject(huffmanCode);
			fos.close();
			oos.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

文件解压

/**
	 * 
	 * @param zipFile 压缩文件
	 * @param dstFile 解压存储位置
	 */
	public static void unzipFile(String zipFile, String dstFile) {
		try {
			InputStream is = new FileInputStream(zipFile);
			ObjectInputStream ois = new ObjectInputStream(is);
			FileOutputStream fos = new FileOutputStream(dstFile);
			// 对象读取
			byte[] huffmanBytes = (byte[]) ois.readObject();
			Map<Byte, String> codes = (Map<Byte, String>) ois.readObject();
			// 解码
			byte[] decode = decode(codes, huffmanBytes);
			// 写入文件
			fos.write(decode);
			ois.close();
			is.close();
			fos.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dichotomy_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值