首頁  >  文章  >  後端開發  >  PHP實作Huffman編碼/解碼步驟詳解

PHP實作Huffman編碼/解碼步驟詳解

php中世界最好的语言
php中世界最好的语言原創
2018-05-16 15:15:581940瀏覽

這次帶給大家PHP實作Huffman編碼/解碼步驟詳解,PHP實作Huffman編碼/解碼的注意事項有哪些,下面就是實戰案例,一起來看一下。

本文就來用 PHP 來實作 Huffman 程式碼和解碼。

1. 編碼

字數統計

#Huffman編碼的第一步就是要統計文件中每個字元出現的次數,PHP的內建函數 count_chars() 就可以做到:

$input = file_get_contents('input.txt');
$stat = count_chars($input, 1);

建構Huffman樹

接下來根據統計結果構造Huffman樹,構造方法在Wikipedia 有詳細的描述。這裡用PHP寫了一個簡易版的:

$huffmanTree = [];
foreach ($stat as $char => $count) {
  $huffmanTree[] = [
    'k' => chr($char),
    'v' => $count,
    'left' => null,
    'right' => null,
  ];
}
// 构造树的层级关系,思想见wiki:https://zh.wikipedia.org/wiki/%E9%9C%8D%E5%A4%AB%E6%9B%BC%E7%BC%96%E7%A0%81
$size = count($huffmanTree);
for ($i = 0; $i !== $size - 1; $i++) {
  uasort($huffmanTree, function ($a, $b) {
    if ($a['v'] === $b['v']) {
      return 0;
    }
    return $a[&#39;v&#39;] < $b[&#39;v&#39;] ? -1 : 1;
  });
  $a = array_shift($huffmanTree);
  $b = array_shift($huffmanTree);
  $huffmanTree[] = [
    &#39;v&#39; => $a[&#39;v&#39;] + $b[&#39;v&#39;],
    &#39;left&#39; => $b,
    &#39;right&#39; => $a,
  ];
}
$root = current($huffmanTree);

經過計算之後,$root 就會指向Huffman 樹的根節點

##根據Huffman樹產生編碼字典

有了Huffman 樹,就可以產生用於編碼的字典:

function buildDict($elem, $code = &#39;&#39;, &$dict) {
  if (isset($elem[&#39;k&#39;])) {
    $dict[$elem[&#39;k&#39;]] = $code;
  } else {
    buildDict($elem[&#39;left&#39;], $code.&#39;0&#39;, $dict);
    buildDict($elem[&#39;right&#39;], $code.&#39;1&#39;, $dict);
  }
}
$dict = [];
buildDict($root, &#39;&#39;, $dict);

#寫檔案

運用字典將檔案內容編碼,並寫入文件。將Huffman編碼寫入檔案的有幾個注意的地方:

將編碼字典和編碼內容一起寫入檔案後,就沒法區分他們的邊界了,因此需要在檔案開始寫入他們各自佔用的位元組數

PHP提供的fwrite() 函數一次能寫入8-bit(一個位元組)或是8的整數倍個bit。但Huffman編碼中,一個字元可能只用 1-bit 表示,PHP不支援只往檔案中寫入 1-bit 這種操作。所以需要我們自行對編碼進行拼接,每湊齊 8-bit 才會寫入檔案。

每湊齊8-bit才寫入

與第二條類似,最終形成的檔案大小一定是 8-bit 的整數倍。所以如果整個編碼的大小是8001-bit的話,還要在末尾補上7個0

$dictString = serialize($dict);
// 写入字典和编码各自占用的字节数
$header = pack(&#39;VV&#39;, strlen($dictString), strlen($input));
fwrite($outFile, $header);
// 写入字典本身
fwrite($outFile, $dictString);
// 写入编码的内容
$buffer = &#39;&#39;;
$i = 0;
while (isset($input[$i])) {
  $buffer .= $dict[$input[$i]];
  while (isset($buffer[7])) {
    $char = bindec(substr($buffer, 0, 8));
    fwrite($outFile, chr($char));
    $buffer = substr($buffer, 8);
  }
  $i++;
}
// 末尾的内容如果没有凑齐 8-bit,需要自行补齐
if (!empty($buffer)) {
  $char = bindec(str_pad($buffer, 8, &#39;0&#39;));
  fwrite($outFile, chr($char));
}
fclose($outFile);

解碼

Huffman編碼的解碼相對簡單:先讀取編碼字典,然後根據字典解碼出原始字元。

解碼過程有個問題要注意:由於我們在編碼過程中,在檔案結尾補齊了幾個0-bit,如果這些0-bit 在字典中剛好是某個字元的編碼時,就會造成錯誤的解碼。

所以解碼過程中,當已解碼的字元數達到文件長度時,就要停止解碼。

<?php
$content = file_get_contents(&#39;a.out&#39;);
// 读出字典长度和编码内容长度
$header = unpack(&#39;VdictLen/VcontentLen&#39;, $content);
$dict = unserialize(substr($content, 8, $header[&#39;dictLen&#39;]));
$dict = array_flip($dict);
$bin = substr($content, 8 + $header[&#39;dictLen&#39;]);
$output = &#39;&#39;;
$key = &#39;&#39;;
$decodedLen = 0;
$i = 0;
while (isset($bin[$i]) && $decodedLen !== $header[&#39;contentLen&#39;]) {
  $bits = decbin(ord($bin[$i]));
  $bits = str_pad($bits, 8, &#39;0&#39;, STR_PAD_LEFT);
  for ($j = 0; $j !== 8; $j++) {
    // 每拼接上 1-bit,就去与字典比对是否能解码出字符
    $key .= $bits[$j];
    if (isset($dict[$key])) {
      $output .= $dict[$key];
      $key = &#39;&#39;;
      $decodedLen++;
      if ($decodedLen === $header[&#39;contentLen&#39;]) {
        break;
      }
    }
  }
  $i++;
}
echo $output;

試驗

我們將Huffman編碼Wiki頁的HTML代碼儲存到本地,進行Huffman編碼測試,試驗結果:

#編碼前: 418,504 位元組

編碼後: 280,127 位元組

空間節省了33%,如果原文的重複內容較多,Huffman編碼節省的空間可以達到50%以上.

除了文字內容,我們再嘗試將一個二進位檔案進行Huffman編碼,例如f.lux的安裝程序,試驗結果如下:

編碼前: 770,384 位元組

編碼後: 773,076 位元組

編碼後反而佔用了更大的空間,一方面是由於我們儲存字典時,並沒有做額外的處理,佔用了不少空間。另一方面,在二進位檔案中,各個字元出現的機率相對比較平均,無法發揮Huffman編碼的優勢。

相信看了本文案例你已經掌握了方法,更多精彩請關注php中文網其它相關文章!

推薦閱讀:

PHP快速排序演算法使用步驟詳解

#PHP基於SPL實作的迭代器步驟詳解

以上是PHP實作Huffman編碼/解碼步驟詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn