SorryToPerson logo
返回
算法2026-04-21·7 分钟

算法知识库:压缩算法实现

JavaScript/TypeScript 实现压缩算法,如游程编码、霍夫曼编码等。

压缩算法实现

1. 游程编码 (Run-Length Encoding)

ts
function runLengthEncode(str: string): string {
  if (!str) return '';

  let result = '';
  let count = 1;

  for (let i = 1; i < str.length; i += 1) {
    if (str[i] === str[i - 1]) {
      count += 1;
    } else {
      result += count + str[i - 1];
      count = 1;
    }
  }

  result += count + str[str.length - 1];
  return result;
}

function runLengthDecode(encoded: string): string {
  let result = '';
  let i = 0;

  while (i < encoded.length) {
    let count = 0;
    while (i < encoded.length && /\d/.test(encoded[i])) {
      count = count * 10 + parseInt(encoded[i]);
      i += 1;
    }
    const char = encoded[i];
    result += char.repeat(count);
    i += 1;
  }

  return result;
}

2. 霍夫曼编码

ts
class HuffmanNode {
  char?: string;
  freq: number;
  left?: HuffmanNode;
  right?: HuffmanNode;

  constructor(char?: string, freq: number = 0) {
    this.char = char;
    this.freq = freq;
  }
}

function buildHuffmanTree(text: string): HuffmanNode {
  const freqMap = new Map<string, number>();
  for (const char of text) {
    freqMap.set(char, (freqMap.get(char) || 0) + 1);
  }

  const priorityQueue: HuffmanNode[] = [];
  for (const [char, freq] of freqMap) {
    priorityQueue.push(new HuffmanNode(char, freq));
  }

  priorityQueue.sort((a, b) => a.freq - b.freq);

  while (priorityQueue.length > 1) {
    const left = priorityQueue.shift()!;
    const right = priorityQueue.shift()!;
    const merged = new HuffmanNode(undefined, left.freq + right.freq);
    merged.left = left;
    merged.right = right;
    priorityQueue.push(merged);
    priorityQueue.sort((a, b) => a.freq - b.freq);
  }

  return priorityQueue[0];
}

function buildHuffmanCodes(node: HuffmanNode, currentCode: string, codes: Map<string, string>): void {
  if (!node) return;

  if (node.char) {
    codes.set(node.char, currentCode);
    return;
  }

  buildHuffmanCodes(node.left!, currentCode + '0', codes);
  buildHuffmanCodes(node.right!, currentCode + '1', codes);
}

function huffmanEncode(text: string): { encoded: string; codes: Map<string, string> } {
  if (!text) return { encoded: '', codes: new Map() };

  const root = buildHuffmanTree(text);
  const codes = new Map<string, string>();
  buildHuffmanCodes(root, '', codes);

  let encoded = '';
  for (const char of text) {
    encoded += codes.get(char);
  }

  return { encoded, codes };
}

function huffmanDecode(encoded: string, codes: Map<string, string>): string {
  const reverseCodes = new Map<string, string>();
  for (const [char, code] of codes) {
    reverseCodes.set(code, char);
  }

  let result = '';
  let currentCode = '';

  for (const bit of encoded) {
    currentCode += bit;
    if (reverseCodes.has(currentCode)) {
      result += reverseCodes.get(currentCode);
      currentCode = '';
    }
  }

  return result;
}

3. LZW 压缩

ts
function lzwCompress(input: string): number[] {
  const dictionary = new Map<string, number>();
  for (let i = 0; i < 256; i += 1) {
    dictionary.set(String.fromCharCode(i), i);
  }

  let result: number[] = [];
  let current = '';
  let nextCode = 256;

  for (const char of input) {
    const combined = current + char;
    if (dictionary.has(combined)) {
      current = combined;
    } else {
      result.push(dictionary.get(current)!);
      dictionary.set(combined, nextCode);
      nextCode += 1;
      current = char;
    }
  }

  if (current) {
    result.push(dictionary.get(current)!);
  }

  return result;
}

function lzwDecompress(compressed: number[]): string {
  const dictionary = new Map<number, string>();
  for (let i = 0; i < 256; i += 1) {
    dictionary.set(i, String.fromCharCode(i));
  }

  let result = '';
  let previous = String.fromCharCode(compressed[0]);
  result += previous;
  let nextCode = 256;

  for (let i = 1; i < compressed.length; i += 1) {
    let current: string;
    if (dictionary.has(compressed[i])) {
      current = dictionary.get(compressed[i])!;
    } else if (compressed[i] === nextCode) {
      current = previous + previous[0];
    } else {
      throw new Error('Invalid compressed data');
    }

    result += current;
    dictionary.set(nextCode, previous + current[0]);
    nextCode += 1;
    previous = current;
  }

  return result;
}

4. 实现要点

  • 游程编码适合重复字符多的数据。
  • 霍夫曼编码基于字符频率构建最优前缀码。
  • LZW 使用字典动态构建编码。
  • 压缩效果取决于数据特征。
算法压缩JavaScript