refactor: Enhance docs, code, add tests in Huffman (#6646)

* refactor: Enhance docs, code, add tests in `Huffman`

* Fix lint

* Fix lint
This commit is contained in:
Hardik Pawar
2025-10-12 11:20:58 +05:30
committed by GitHub
parent 2a004a0141
commit b5246c3d86
2 changed files with 401 additions and 92 deletions

View File

@@ -1,125 +1,211 @@
package com.thealgorithms.others;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Scanner;
// node class is the basic structure
// of each node present in the Huffman - tree.
/**
* Node class representing a node in the Huffman tree.
* Each node contains a character, its frequency, and references to left and
* right children.
*/
class HuffmanNode {
int data;
char c;
HuffmanNode left;
HuffmanNode right;
}
// comparator class helps to compare the node
// on the basis of one of its attribute.
// Here we will be compared
// on the basis of data values of the nodes.
class MyComparator implements Comparator<HuffmanNode> {
/**
* Constructor for HuffmanNode.
*
* @param c the character stored in this node
* @param data the frequency of the character
*/
HuffmanNode(char c, int data) {
this.c = c;
this.data = data;
this.left = null;
this.right = null;
}
public int compare(HuffmanNode x, HuffmanNode y) {
return x.data - y.data;
/**
* Default constructor for HuffmanNode.
*/
HuffmanNode() {
this.left = null;
this.right = null;
}
}
/**
* Comparator class for comparing HuffmanNode objects based on their frequency
* data.
* Used to maintain min-heap property in the priority queue.
*/
class HuffmanComparator implements Comparator<HuffmanNode> {
@Override
public int compare(HuffmanNode x, HuffmanNode y) {
return Integer.compare(x.data, y.data);
}
}
/**
* Implementation of Huffman Coding algorithm for data compression.
* Huffman Coding is a greedy algorithm that assigns variable-length codes to
* characters
* based on their frequency of occurrence. Characters with higher frequency get
* shorter codes.
*
* <p>
* Time Complexity: O(n log n) where n is the number of unique characters
* Space Complexity: O(n)
*
* @see <a href="https://en.wikipedia.org/wiki/Huffman_coding">Huffman
* Coding</a>
*/
public final class Huffman {
private Huffman() {
}
// recursive function to print the
// huffman-code through the tree traversal.
// Here s is the huffman - code generated.
public static void printCode(HuffmanNode root, String s) {
// base case; if the left and right are null
// then its a leaf node and we print
// the code s generated by traversing the tree.
if (root.left == null && root.right == null && Character.isLetter(root.c)) {
// c is the character in the node
System.out.println(root.c + ":" + s);
/**
* Builds a Huffman tree from the given character array and their frequencies.
*
* @param charArray array of characters
* @param charFreq array of frequencies corresponding to the characters
* @return root node of the Huffman tree
* @throws IllegalArgumentException if arrays are null, empty, or have different
* lengths
*/
public static HuffmanNode buildHuffmanTree(char[] charArray, int[] charFreq) {
if (charArray == null || charFreq == null) {
throw new IllegalArgumentException("Character array and frequency array cannot be null");
}
if (charArray.length == 0 || charFreq.length == 0) {
throw new IllegalArgumentException("Character array and frequency array cannot be empty");
}
if (charArray.length != charFreq.length) {
throw new IllegalArgumentException("Character array and frequency array must have the same length");
}
int n = charArray.length;
PriorityQueue<HuffmanNode> priorityQueue = new PriorityQueue<>(n, new HuffmanComparator());
// Create leaf nodes and add to priority queue
for (int i = 0; i < n; i++) {
if (charFreq[i] < 0) {
throw new IllegalArgumentException("Frequencies must be non-negative");
}
HuffmanNode node = new HuffmanNode(charArray[i], charFreq[i]);
priorityQueue.add(node);
}
// Build the Huffman tree
while (priorityQueue.size() > 1) {
HuffmanNode left = priorityQueue.poll();
HuffmanNode right = priorityQueue.poll();
HuffmanNode parent = new HuffmanNode();
parent.data = left.data + right.data;
parent.c = '-';
parent.left = left;
parent.right = right;
priorityQueue.add(parent);
}
return priorityQueue.poll();
}
/**
* Generates Huffman codes for all characters in the tree.
*
* @param root root node of the Huffman tree
* @return map of characters to their Huffman codes
*/
public static Map<Character, String> generateCodes(HuffmanNode root) {
Map<Character, String> huffmanCodes = new HashMap<>();
if (root != null) {
generateCodesHelper(root, "", huffmanCodes);
}
return huffmanCodes;
}
/**
* Helper method to recursively generate Huffman codes by traversing the tree.
*
* @param node current node in the tree
* @param code current code being built
* @param huffmanCodes map to store character-to-code mappings
*/
private static void generateCodesHelper(HuffmanNode node, String code, Map<Character, String> huffmanCodes) {
if (node == null) {
return;
}
// if we go to left then add "0" to the code.
// if we go to the right add"1" to the code.
// recursive calls for left and
// right sub-tree of the generated tree.
printCode(root.left, s + "0");
printCode(root.right, s + "1");
// If it's a leaf node, store the code
if (node.left == null && node.right == null && Character.isLetter(node.c)) {
huffmanCodes.put(node.c, code.isEmpty() ? "0" : code);
return;
}
// Traverse left with '0' and right with '1'
if (node.left != null) {
generateCodesHelper(node.left, code + "0", huffmanCodes);
}
if (node.right != null) {
generateCodesHelper(node.right, code + "1", huffmanCodes);
}
}
// main function
/**
* Prints Huffman codes for all characters in the tree.
* This method is kept for backward compatibility and demonstration purposes.
*
* @param root root node of the Huffman tree
* @param code current code being built (initially empty string)
*/
public static void printCode(HuffmanNode root, String code) {
if (root == null) {
return;
}
// If it's a leaf node, print the code
if (root.left == null && root.right == null && Character.isLetter(root.c)) {
System.out.println(root.c + ":" + code);
return;
}
// Traverse left with '0' and right with '1'
if (root.left != null) {
printCode(root.left, code + "0");
}
if (root.right != null) {
printCode(root.right, code + "1");
}
}
/**
* Demonstrates the Huffman coding algorithm with sample data.
*
* @param args command line arguments (not used)
*/
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
// number of characters.
int n = 6;
// Sample characters and their frequencies
char[] charArray = {'a', 'b', 'c', 'd', 'e', 'f'};
int[] charfreq = {5, 9, 12, 13, 16, 45};
int[] charFreq = {5, 9, 12, 13, 16, 45};
// creating a priority queue q.
// makes a min-priority queue(min-heap).
PriorityQueue<HuffmanNode> q = new PriorityQueue<HuffmanNode>(n, new MyComparator());
System.out.println("Characters: a, b, c, d, e, f");
System.out.println("Frequencies: 5, 9, 12, 13, 16, 45");
System.out.println("\nHuffman Codes:");
for (int i = 0; i < n; i++) {
// creating a Huffman node object
// and add it to the priority queue.
HuffmanNode hn = new HuffmanNode();
// Build Huffman tree
HuffmanNode root = buildHuffmanTree(charArray, charFreq);
hn.c = charArray[i];
hn.data = charfreq[i];
hn.left = null;
hn.right = null;
// add functions adds
// the huffman node to the queue.
q.add(hn);
// Generate and print Huffman codes
Map<Character, String> codes = generateCodes(root);
for (Map.Entry<Character, String> entry : codes.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// create a root node
HuffmanNode root = null;
// Here we will extract the two minimum value
// from the heap each time until
// its size reduces to 1, extract until
// all the nodes are extracted.
while (q.size() > 1) {
// first min extract.
HuffmanNode x = q.peek();
q.poll();
// second min extarct.
HuffmanNode y = q.peek();
q.poll();
// new node f which is equal
HuffmanNode f = new HuffmanNode();
// to the sum of the frequency of the two nodes
// assigning values to the f node.
f.data = x.data + y.data;
f.c = '-';
// first extracted node as left child.
f.left = x;
// second extracted node as the right child.
f.right = y;
// marking the f node as the root node.
root = f;
// add this node to the priority-queue.
q.add(f);
}
// print the codes by traversing the tree
printCode(root, "");
s.close();
}
}