Day 14

Java 树与二叉树(三)


学习来源: 日撸 Java 三百行(21-30天,树与二叉树)

一、Hanoi 塔问题

这是一个经典问题, 如果不知道请自行百度, 这里就不描述了.

  1. 在数据结构中, 一般把 Hanoi 塔放到栈这一章. 但我觉得, 将其展开更像是二叉树, 也有点三叉的味道.
  2. 虽然代码很短, 但参数复杂. 必须用有意义的变量名, 才不容易出错. 话说现实生活中一般不会遇到这样复杂的参数.

代码如下:

package datastructure.tree;

/**
 * 
 * @author Ling Lin E-mail:linling0.0@foxmail.com
 * 
 * @version 创建时间:2022年4月22日 下午5:19:27
 * 
 */
public class Hanoi {

	/**
	 * Move a number of plates.
	 * 
	 * @param paraSource
	 *            The source pole.
	 * @param paraIntermedium
	 *            The intermediary pole.
	 * @param paraDestination
	 *            The destination pole.
	 * @param paraNumber
	 *            The number of plates.
	 */
	public static void hanoi(char paraSource, char paraIntermedium, char paraDestination, int paraNumber) {
		if (paraNumber == 1) {
			System.out.println(paraSource + "->" + paraDestination + " ");
			return;
		} // Of if

		hanoi(paraSource, paraDestination, paraIntermedium, paraNumber - 1);
		System.out.println(paraSource + "->" + paraDestination + " ");
		hanoi(paraIntermedium, paraSource, paraDestination, paraNumber - 1);
	}// Of hanoi

	/**
	 * 
	 * The entrance of the program.
	 * 
	 * @param args
	 *            Not used now.
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		hanoi('a', 'b', 'c', 3);
	}// Of main

}// Of class Hanoi

运行结果:
在这里插入图片描述

二、Huffman 编码

1. 节点定义与文件读取

不要被代码量吓到了! 今天只需要写到 148 行, 以及 main 里面相应的代码. 三天的代码一起贴上来是为了保证完整性.

(1)定义了一个内嵌类. 如果是实际项目, 我就为其单独写一个文件了, 这里仅仅是为了方便.
(2)每个节点的内容包括: 字符 (仅对叶节点有效)、权重 (用的整数, 该字符的个数)、指向子节点父节点的引用. 这里指向父节点的引用是必须的.
(3)NUM_CHARS 是指 ASCII 字符集的字符个数. 为方便起见, 仅支持 ASCII.
(4)inputText 的引入只是想把程序尽可能细分成独立的模块, 这样便于学习和调拭.
(5)alphabet 仅存 inputText 出现过的字符.
(6)alphabetLength 完全可以用 alphabet.length() 代替, 但我就喜欢写成独立的变量.
(7)charCounts 要为所有的节点负责, 其元素对应于 HuffmanNode 里面的 weight. 为了节约, 可以把其中一个省掉.
(8)charMapping 是为了从 ASCII 里面的顺序映射到 alphabet 里面的顺序. 这也是我只采用 ASCII 字符集 (仅 256 字符) 的原因.
(9)huffmanCodes 将个字符映射为一个字符串, 其实应该是二进制串. 我这里不是想偷懒么.
(10)nodes 要先把所有的节点存储在一个数组里面, 然后再链接它们. 这是常用招数.
(11)构造方法仅初始化了 charMapping, 读入了文件.
(12)readText 采用了最简单粗暴的方式. 还可以有其它的逐行读入的方式.
(13)要自己弄个文本文件, 里面存放一个字符串 abcdedgsgs 之类, 或者几行英文文本.

2. 建树

今天写到 274 行.

(1)Arrays.fill(charMapping, -1); 这种初始化工作非常重要. 搞不好就会调拭很久才找到 bug.
(2)171 行将 char 强制转换为 int, 即其在 ASCII 字符集中的位置. 这是底层代码的特权. 学渣没资格用哈哈.
(3)变量多的时候你才能体会到用 temp, para 这些前缀来区分不同作用域变量的重要性. 没有特殊前缀的就是成员变量.
建树就是一个自底向上, 贪心选择的过程. 确定子节点、父节点的代码是核心.
(4)最后生成的节点就是根节点.
(5)手绘相应的 Huffman 树对照, 才能真正理解.

3. 编码与解码

(1)前序遍历代码的作用仅仅是调拭.
(2)双重循环有一点点难度. 好像也并没有比第 26 天的难.
(3)306 每次重新初始化很重要.
(4)336 行使用了 charMapping 和强制类型转换.
(5)编码是从叶节点到根节点, 解码就是反过来.
(6)解码获得原先的字符串, 就验证正确性了.

代码如下:

package datastructure.tree;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.stream.Collectors;

/**
 * 
 * @author Ling Lin E-mail:linling0.0@foxmail.com
 * 
 * @version 创建时间:2022年4月22日 下午7:13:56
 * 
 */
public class Huffman {

	/**
	 * An inner class for Huffman nodes.
	 */
	class HuffmanNode {
		// The char. Only valid for leaf nodes.
		char character;

		// Weight. It can also be double.
		int weight;

		// The left child.
		HuffmanNode leftChild;

		// The right child.
		HuffmanNode rightChild;

		// The parent. It helps constructing the Huffman code of each character.
		HuffmanNode parent;

		/**
		 * The first constructor
		 */
		public HuffmanNode(char paraCharacter, int paraWeight, HuffmanNode paraLeftChild, HuffmanNode paraRightChild,
				HuffmanNode paraParent) {
			character = paraCharacter;
			weight = paraWeight;
			leftChild = paraLeftChild;
			rightChild = paraRightChild;
			parent = paraParent;
		}// Of HuffmanNode

		/**
		 * To string
		 */
		@Override
		public String toString() {
			String resultString = "(" + character + "," + weight + ")";

			return resultString;
		}// Of toString

	}// Of class HuffmanNode

	// The number of characters. 256 for ASCII.
	public static final int NUM_CHARS = 256;

	// The input text. It is stored in a string for simplicity.
	String inputText;

	// The lenght of the alphabet, also the number of leaves.
	int alphabetLength;

	// The alphabet
	char[] alphabet;

	/**
	 * The count of chars. The lenght is 2 * alphabetLenght - 1 to include
	 * non-leaf nodes.
	 * 
	 */
	int[] charCounts;

	// The mapping of chars to the indices in the alphabet.
	int[] charMapping;

	String[] huffmanCodes;

	// All nodes. The last node is the root.
	HuffmanNode[] nodes;

	/**
	 * The first constructor.
	 * 
	 * @param paraFilename
	 *            The text filename.
	 */
	public Huffman(String paraFilename) {
		charMapping = new int[NUM_CHARS];

		readText(paraFilename);
	}// Of the first constructor

	/**
	 * Read text.
	 * 
	 * @param paraFilename
	 *            The text filename.
	 */
	public void readText(String paraFilename) {
		try {
			inputText = Files.newBufferedReader(Paths.get(paraFilename), StandardCharsets.UTF_8).lines()
					.collect(Collectors.joining("\n"));
		} catch (Exception ee) {
			System.out.println(ee);
			System.exit(0);
		} // Of try

		System.out.println("The text is:\r\n" + inputText);
	}// Of readText

	public void constructAlphabet() {
		// Initialize.
		Arrays.fill(charMapping, -1);

		// The count for each char. At most NUM_CHARS chars.
		int[] tempCharCounts = new int[NUM_CHARS];

		// The index of the char in the ASCII charset.
		int tempCharIndex;

		// Step1. Scan the string to obtain the counts.
		char tempChar;
		for (int i = 0; i < inputText.length(); i++) {
			tempChar = inputText.charAt(i);
			tempCharIndex = tempChar;

			System.out.print("" + tempCharIndex + " ");

			tempCharCounts[tempCharIndex]++;
		} // Of for i

		// Step 2. Scan to determine the size of the alphabet.
		alphabetLength = 0;
		for (int i = 0; i < 255; i++) {
			if (tempCharCounts[i] > 0) {
				alphabetLength++;
			} // Of if
		} // Of for i

		// Step 3. Compress to the alphabet
		alphabet = new char[alphabetLength];
		charCounts = new int[2 * alphabetLength - 1];

		int tempCounter = 0;
		for (int i = 0; i < NUM_CHARS; i++) {
			if (tempCharCounts[i] > 0) {
				alphabet[tempCounter] = (char) i;
				charCounts[tempCounter] = tempCharCounts[i];
				charMapping[i] = tempCounter;
				tempCounter++;
			} // Of if
		} // Of for i

		System.out.println("The alphabet is: " + Arrays.toString(alphabet));
		System.out.println("Their counts are: " + Arrays.toString(charCounts));
		System.out.println("The char mappings are: " + Arrays.toString(charMapping));

	}// Of constructAlphabet

	/**
	 * Construct the tree.
	 */
	public void constructTree() {
		// Step 1. Allocate space.
		nodes = new HuffmanNode[alphabetLength * 2 - 1];
		boolean[] tempProcessed = new boolean[alphabetLength * 2 - 1];

		// Step 2. Initialize leaves.
		for (int i = 0; i < alphabetLength; i++) {
			nodes[i] = new HuffmanNode(alphabet[i], charCounts[i], null, null, null);
		} // Of for i

		// Step 3. Construct the tree.
		int tempLeft, tempRight, tempMinimal;
		for (int i = alphabetLength; i < 2 * alphabetLength - 1; i++) {
			// Step 3.1 Select the first minimal as the left child.
			tempLeft = -1;
			tempMinimal = Integer.MAX_VALUE;
			for (int j = 0; j < i; j++) {
				if (tempProcessed[j]) {
					continue;
				} // Of if

				if (tempMinimal > charCounts[j]) {
					tempMinimal = charCounts[j];
					tempLeft = j;
				} // Of if
			} // Of for j
			tempProcessed[tempLeft] = true;

			// Step 3.2 Select the second minimal as the right child.
			tempRight = -1;
			tempMinimal = Integer.MAX_VALUE;
			for (int j = 0; j < i; j++) {
				if (tempProcessed[j]) {
					continue;
				} // Of if

				if (tempMinimal > charCounts[j]) {
					tempMinimal = charCounts[j];
					tempRight = j;
				} // Of if
			} // Of for j
			tempProcessed[tempRight] = true;
			System.out.println("Selecting " + tempLeft + " and " + tempRight);

			// Step 3.3 Construct the new node.
			charCounts[i] = charCounts[tempLeft] + charCounts[tempRight];
			nodes[i] = new HuffmanNode('*', charCounts[i], nodes[tempLeft], nodes[tempRight], null);

			// Step 3.4 Link with children.
			nodes[tempLeft].parent = nodes[i];
			nodes[tempRight].parent = nodes[i];
			System.out.println("The children of " + i + " are " + tempLeft + " and " + tempRight);
		} // Of for i
	}// Of constructTree

	/**
	 * Get the root of the binary tree.
	 * 
	 * @return The root.
	 */
	public HuffmanNode getRoot() {
		return nodes[nodes.length - 1];
	}// Of getRoot

	/**
	 * Pre-order visit.
	 */
	public void preOrderVisit(HuffmanNode paraNode) {
		System.out.print("(" + paraNode.character + ", " + paraNode.weight + ") ");

		if (paraNode.leftChild != null) {
			preOrderVisit(paraNode.leftChild);
		} // Of if

		if (paraNode.rightChild != null) {
			preOrderVisit(paraNode.rightChild);
		} // Of if
	}// Of preOrderVisit

	/**
	 * Generate codes for each character in the alphabet.
	 */
	public void generateCodes() {
		huffmanCodes = new String[alphabetLength];
		HuffmanNode tempNode;
		for (int i = 0; i < alphabetLength; i++) {
			tempNode = nodes[i];

			// Use tempCharCode instead of tempCode such that it is unlike
			// tempNode.
			// This is an advantage of long names.
			String tempCharCode = "";
			while (tempNode.parent != null) {
				if (tempNode == tempNode.parent.leftChild) {
					tempCharCode = "0" + tempCharCode;
				} else {
					tempCharCode = "1" + tempCharCode;
				} // Of if

				tempNode = tempNode.parent;
			} // Of while

			huffmanCodes[i] = tempCharCode;
			System.out.println("The code of " + alphabet[i] + " is " + tempCharCode);
		} // Of for i
	}// Of generateCodes

	/**
	 * Encode the given string.
	 * 
	 * @param paraString
	 *            The given string.
	 */
	public String coding(String paraString) {
		String resultCodeString = "";

		int tempIndex;
		for (int i = 0; i < paraString.length(); i++) {
			// From the original char to the location in the alphabet.
			tempIndex = charMapping[paraString.charAt(i)];

			// From the location in the alphabet to the code.
			resultCodeString += huffmanCodes[tempIndex];
		} // Of for i
		return resultCodeString;
	}// Of coding

	/**
	 * Decode the given string.
	 * 
	 * @param paraString
	 *            The given string.
	 */
	public String decoding(String paraString) {
		String resultCodeString = "";

		HuffmanNode tempNode = getRoot();

		for (int i = 0; i < paraString.length(); i++) {
			if (paraString.charAt(i) == '0') {
				tempNode = tempNode.leftChild;
				System.out.println(tempNode);
			} else {
				tempNode = tempNode.rightChild;
				System.out.println(tempNode);
			} // Of if

			if (tempNode.leftChild == null) {
				System.out.println("Decode one:" + tempNode);
				// Decode one char.
				resultCodeString += tempNode.character;

				// Return to the root.
				tempNode = getRoot();
			} // Of if
		} // Of for i

		return resultCodeString;
	}// Of decoding

	/**
	 * The entrance of the program.
	 * 
	 * @param args
	 *            Not used now.
	 */
	public static void main(String args[]) {
		Huffman tempHuffman = new Huffman("C:/Users/lenovo/Desktop/huffmantext-small.txt");
		tempHuffman.constructAlphabet();

		tempHuffman.constructTree();

		HuffmanNode tempRoot = tempHuffman.getRoot();
		System.out.println("The root is: " + tempRoot);
		System.out.println("Preorder visit:");
		tempHuffman.preOrderVisit(tempHuffman.getRoot());
		System.out.println();

		tempHuffman.generateCodes();

		String tempCoded = tempHuffman.coding("abcdb");
		System.out.println("Coded: " + tempCoded);

		String tempDecoded = tempHuffman.decoding(tempCoded);
		System.out.println("Decoded: " + tempDecoded);
	}// Of main

}// Of class Huffman

运行结果:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值