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

來源:互聯網
上載者:User
這次給大家帶來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['v'] < $b['v'] ? -1 : 1;  });  $a = array_shift($huffmanTree);  $b = array_shift($huffmanTree);  $huffmanTree[] = [    'v' => $a['v'] + $b['v'],    'left' => $b,    'right' => $a,  ];}$root = current($huffmanTree);

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

根據Huffman樹產生編碼字典

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

function buildDict($elem, $code = '', &$dict) {  if (isset($elem['k'])) {    $dict[$elem['k']] = $code;  } else {    buildDict($elem['left'], $code.'0', $dict);    buildDict($elem['right'], $code.'1', $dict);  }}$dict = [];buildDict($root, '', $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('VV', strlen($dictString), strlen($input));fwrite($outFile, $header);// 寫入字典本身fwrite($outFile, $dictString);// 寫入編碼的內容$buffer = '';$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, '0'));  fwrite($outFile, chr($char));}fclose($outFile);

解碼

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

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

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

<?php$content = file_get_contents('a.out');// 讀出字典長度和編碼內容長度$header = unpack('VdictLen/VcontentLen', $content);$dict = unserialize(substr($content, 8, $header['dictLen']));$dict = array_flip($dict);$bin = substr($content, 8 + $header['dictLen']);$output = '';$key = '';$decodedLen = 0;$i = 0;while (isset($bin[$i]) && $decodedLen !== $header['contentLen']) {  $bits = decbin(ord($bin[$i]));  $bits = str_pad($bits, 8, '0', STR_PAD_LEFT);  for ($j = 0; $j !== 8; $j++) {    // 每拼接上 1-bit,就去與字典比對是否能解碼出字元    $key .= $bits[$j];    if (isset($dict[$key])) {      $output .= $dict[$key];      $key = '';      $decodedLen++;      if ($decodedLen === $header['contentLen']) {        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實現的迭代器步驟詳解

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.