ハフマン符号長の効率的な求め方.

Slides:



Advertisements
Similar presentations
Lecture02-Binary Search Trees 通信情報システム専攻 岩間伊藤研究室 M1 前田圭介.
Advertisements

LZ符号化 森田 岳史.
プログラムのパタン演習 解説.
ヒープソートの演習 第13回.
アルゴリズムイントロダクション第2章 主にソートに関して
情報・知能工学系 山本一公 プログラミング演習Ⅱ 第4回 配列(2) 情報・知能工学系 山本一公
アルゴリズムとデータ構造 第8回 ソート(3).
近似アルゴリズム 第10章 終了時刻最小化スケジューリング
第11回 整列 ~ シェルソート,クイックソート ~
データ構造とアルゴリズム論 第5章 整列(ソート)のアルゴリズム
第11回 整列 ~ バケットソート,基数ソート,ヒープソート~
On the Enumeration of Colored Trees
ファーストイヤー・セミナーⅡ 第8回 データの入力.
Problem G : Entangled Tree
プログラミング演習Ⅱ 第12回 文字列とポインタ(1)
アルゴリズムイントロダクション第5章( ) 確率論的解析
動的ハフマン符号化の例 入力:ABCDEからなる文字列 出力:動的に作ったハフマン木.
岩井 儀雄 コンピュータ基礎演習  ー探索、整列ー 岩井 儀雄
第7章 データベース管理システム 7.1 データベース管理システムの概要 7.2 データベースの格納方式 7.3 問合せ処理.
エージェントを管理するボスの苦悩 問題 n人のエージェントを部下に持つボスがいる. エージェントは出張・帰還を頻繁に繰り返す.
ヒープソートの復習.
データ構造とアルゴリズム論 第2章 配列(構造)を使った処理
第11回 整列 ~ シェルソート,クイックソート ~
二分探索木によるサーチ.
7-3.高度な木 (平衡木) AVL木 平衡2分木。回転操作に基づくバランス回復機構により平衡を保つ。 B木
定兼邦彦 今井浩 東京大学理学系研究科 情報科学専攻
プログラミング入門 電卓を作ろう・パートIV!!.
情報工学概論 (アルゴリズムとデータ構造)
ソートアルゴリズムの種類 選択ソート (selection sort) バブルソート (bubble sort)
アルゴリズムとデータ構造 第3章 ヒープ 6月10日分
第9回 優先度つき待ち行列,ヒープ,二分探索木
25. Randomized Algorithms
P n ポインタの基礎 5 q m 5 7 int* p; int 型の変数を指すポインタ int* q; int 型の変数を指すポインタ int n=5, m=7; int 型の変数 int array[3]; int* pArray[3]; p = &n; ポインタにアドレスを代入しているのでOK.
データ構造と アルゴリズム 第六回 知能情報学部 新田直也.
基本情報技術概論(第6回) 埼玉大学 理工学研究科 堀山 貴史
Cプログラミング演習 第10回 二分探索木.
アルゴリズムとプログラミング (Algorithms and Programming)
データ構造とアルゴリズム論 第5章 整列(ソート)のアルゴリズム
プログラミング 4 整列アルゴリズム.
2009/10/23 整列アルゴリズム (1) 第4講: 平成21年10月23日 (金) 4限 E252教室 コンピュータアルゴリズム.
データ構造とアルゴリズム論 第5章 整列(ソート)のアルゴリズム
プログラミング 4 木構造とヒープ.
アルゴリズムとデータ構造1 2006年7月11日
15.cons と種々のデータ構造.
情報数理Ⅱ 第11章 データ構造 平成29年1月18日.
D: 壊れかけのヒープ 問題案: 稲葉.
第16章 動的計画法 アルゴリズムイントロダクション.
AVL木.
第9回 優先度つき待ち行列,ヒープ,二分探索木
全体ミーティング (5/23) 村田雅之.
アルゴリズムとデータ構造 2012年7月2日
ポインタとポインタを用いた関数定義.
アルゴリズムとプログラミング (Algorithms and Programming)
バブルソート,バケツソート,クイックソート
アルゴリズムとデータ構造 2011年6月28日
アルゴリズムとデータ構造 2011年6月16日
基本情報技術概論(第6回) 埼玉大学 理工学研究科 堀山 貴史
ヒープソートの復習 第12回.
Advanced Data Structure 第3回
アルゴリズムとデータ構造 2013年7月2日
ソートのプログラムの流れ 配列の中身を小さい順に並び替える a[1],a[2],…a[n]の値を順に出力する
平面走査法を使った 一般線分の 交点列挙アルゴリズム
4.プッシュダウンオートマトンと 文脈自由文法の等価性
ヒープソート.
ヒープソート.
アルゴリズムとデータ構造 2013年6月20日
参考:大きい要素の処理.
データ構造と アルゴリズムI 第三回 知能情報学部 新田直也.
プログラミング入門2 第5回 配列 変数宣言、初期化について
プログラミング入門2 第3回 条件分岐(2) 繰り返し文 篠埜 功.
Presentation transcript:

ハフマン符号長の効率的な求め方

モチベーション Canonical Huffman code (以下CHC) では符号長さえ分かれば後の処理は比較的単純。符号長が知りたい 通常のハフマンアルゴリズムはポインタで木を構築してから traverse して符号語と符号長を求めるが、木を必要としない CHC で長さを計算するためだけにこの類の木を使うのは冗長 符号化したいシンボルの数 n に対して 2n 程度の領域だけで符号長を求めるアルゴリズム。空間的にも、ローカリティ的にも有利

シンボルの数 n に対してサイズ 2n の配列Aを用意する。この配列で左上の表のハフマン木を表現することを考える 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 例: シンボル d のシンボル番号 (文字コード相当) は 3 で出現回数が 13 回 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 シンボルの数 n に対してサイズ 2n の配列Aを用意する。この配列で左上の表のハフマン木を表現することを考える

0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 10 11 2 13 22 23 5 13 配列Aの右半分を出現回数で埋める。つまり、右半分の要素それぞれがハフマン木の葉に相当する。 シンボル番号を i、i の出現回数を ciとすると A[n + i] = ci 例えば、左上表からシンボル b は番号 1、c1 = 11 なので A[n + 1] = 11 (n = 8)

0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 8 9 10 11 12 13 14 15 10 11 2 13 22 23 5 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 左半分を、各シンボルの葉へのポインタにする。具体的には、左側の要素の添え字がシンボル番号、値がポイントすべき右側の要素の添え字になる 例えば左上表からシンボル番号 i = 0 の 'a' を考える。A[0] は右半分の要素の中で 'a' の出現頻度に相当している要素を指す。よって A[0] = 8 になる。A[A[0]] = 10 とすれば 'a' の出現頻度 10 が得られる 一般化すると A[A[i]] = Ci になるように配列の左半分を埋めるということ

これにより A[0] がヒープの根になる。つまり、A[0] は出現頻度最小の値をポイントすることになる 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 10 9 14 11 12 13 8 15 10 11 2 13 22 23 5 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 次に出現頻度の値を比較キーにして二分木ヒープ(※)を構成する。このとき右半分の出現頻度の配列、つまり葉で直接ヒープを構成するのではなく、左半分のポインタで間接的にヒープを構成する。右半分の値 (葉) は変更せず、左半分だけが必要に応じて入れ替わる。 これにより A[0] がヒープの根になる。つまり、A[0] は出現頻度最小の値をポイントすることになる ※配列で二分木ヒープを構成する場合、親の添え字を i とすると左の子は 2i + 1, 右の子は 2i + 2。「親の指す値が常に子のそれよりも小さい」という制約を満たすのがヒープの条件

0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 10 9 14 11 12 13 8 15 10 11 2 13 22 23 5 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 いよいよハフマン木の構築を始める。通常のハフマン木構築アルゴリズム同様、出現頻度が最も小さい二つの値を(greedy に)見つけたい。ひとつは、ヒープの根である A[0] がポイントする最小値である。

0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 10 m1 10 9 14 11 12 13 8 15 10 11 2 13 22 23 5 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 一時変数 m1 に A[0] を取り出し、

0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 m1 10 15 9 14 11 12 13 8 15 10 11 2 13 22 23 5 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 左半分の配列の末尾の値を先頭にコピー(上書き)し、

0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 m1 10 14 9 8 11 12 13 15 15 10 11 2 13 22 23 5 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [0, n -1] だった左半分の配列の範囲を一つ減らして [0, n - 2] の範囲でヒープを構成しなおす。 これで最初の最小値である 2 (を指しているポインタ) をヒープから取り出して、ヒープを再構成したことになる。

0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 m1 10 14 9 8 11 12 13 15 15 10 11 2 13 22 23 5 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 次の最小値は構成しなおしたヒープの根 A[0] にある。よって次の最小値は A[A[0]] = A[14] = 5

0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 m1 10 m2 14 14 9 8 11 12 13 15 15 10 11 2 13 22 23 5 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 二つ目の最小値を一次変数 m2 に取り出す。(今回はヒープの再構成はまだ走らせないことに注意)

ヒープから要素を一つ取り出して空いた領域 A[7] があるので、そこに二値の和 A[m1] + A[m2] = 7 の値を入れる 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 m1 10 m2 14 14 9 8 11 12 13 15 7 10 11 2 13 22 23 5 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ここまでで最小の出現頻度の値二つが分かった。通常のハフマンアルゴリズムではここでこの二値の和を取ってできた節をハフマン木の新しい内部節 = 親とするわけだが、それと同等のことを行う。 ヒープから要素を一つ取り出して空いた領域 A[7] があるので、そこに二値の和 A[m1] + A[m2] = 7 の値を入れる

A[0] の値は既に m2 にコピーしてあり要らないので、そこから新しい要素をポイントする。 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 m1 m2 10 14 7 9 8 11 12 13 15 7 10 11 2 13 22 23 5 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A[0] の値は既に m2 にコピーしてあり要らないので、そこから新しい要素をポイントする。

これで葉 (もしくは内部節)から親を参照したことになる 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 m1 m2 10 14 7 9 8 11 12 13 15 7 10 11 7 13 22 23 7 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 最小値として扱った要素 A[m1] と A[m2] も用済み。A[m1]、A[m2] からも新しい要素(つまり親) をポイントする。すなわち A[m1] = A[m2] = A[0] これで葉 (もしくは内部節)から親を参照したことになる

新しく追加した要素を加味して、左側のヒープを構成しなおす。(この場合、新しく追加した要素 A[7] = 7 は最小なのでヒープに変更はなし) 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 7 9 8 11 12 13 15 7 10 11 7 13 22 23 7 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 新しく追加した要素を加味して、左側のヒープを構成しなおす。(この場合、新しく追加した要素 A[7] = 7 は最小なのでヒープに変更はなし) ここまでで、ハフマン木構築処理のうち、最小の二つの要素を取って和を取り新しい要素にし、ヒープを次の繰り返しに備えて再構築する、という手順が一回完了した。

0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 7 m1 7 9 8 11 12 13 15 7 10 11 7 13 22 23 7 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 以下、ここまでの手順を繰り返す 根で最小値を指す A[0] を取り出し、

左半分の配列の末尾の値を先頭にコピー(上書き)し、 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 7 m1 15 9 8 11 12 13 15 7 10 11 7 13 22 23 7 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 左半分の配列の末尾の値を先頭にコピー(上書き)し、

[0, n -2] だった左半分の配列の範囲を一つ減らして [0, n - 3] の範囲でヒープを構成しなおす 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 7 m1 8 9 15 11 12 13 15 7 10 11 7 13 22 23 7 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [0, n -2] だった左半分の配列の範囲を一つ減らして [0, n - 3] の範囲でヒープを構成しなおす

二つ目の最小値を指すポインタを m2 に取り出し、 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 7 m1 m2 8 8 9 15 11 12 13 15 7 10 11 7 13 22 23 7 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 二つ目の最小値を指すポインタを m2 に取り出し、

A[m1] + A[m2] = 17 を、ヒープから要素を取り出したことで空いていた要素に入れる 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 7 m1 m2 8 8 9 15 11 12 13 17 7 10 11 7 13 22 23 7 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A[m1] + A[m2] = 17 を、ヒープから要素を取り出したことで空いていた要素に入れる

用済みの要素である先頭、今回扱った二つの最小値要素から、今できた新しい要素をポイントする 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 7 m1 m2 8 6 9 15 11 12 13 17 6 6 11 7 13 22 23 7 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 用済みの要素である先頭、今回扱った二つの最小値要素から、今できた新しい要素をポイントする

0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 9 11 15 6 12 13 17 6 6 11 7 13 22 23 7 13 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ヒープを構成しなおす。 これでハフマン木構築の繰り返し2回目が完了。このまま繰り返していくと、ループのたびにヒープ(青の要素)とオリジナルの出現頻度値を保持していた要素(橙色の要素)が少なくなっていき、親をポイントしている要素(緑の要素)が増えていくことが直感的にわかる

A[1] が木の根が持つ値。すべてのシンボルの出現回数の合計になる 赤枠で囲っているのは、元々出現確率の値を持っていた要素、つまり葉 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 1 99 1 1 2 2 4 6 6 5 7 5 3 3 7 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 繰り返しの末、最終的には上図になる A[0] が木の根へのポインタ A[1] が木の根が持つ値。すべてのシンボルの出現回数の合計になる 赤枠で囲っているのは、元々出現確率の値を持っていた要素、つまり葉 これらの要素はヒープの構成時に、場所を移動してない つまり、A[n + i] は変わらずハフマン木の葉に相当している よって、葉 i から根まで上るには A[n + i] から始めてその値でポインタをたぐり寄せていけばよい

A[10] → A[7] → A[6] → A[4] → A[2] → A[1] と葉から始まり親を辿って根に到達 0. a 10 4. e 22 1. b 11 5. f 23 2. c 2 6. g 5 3. d 13 7. h 13 1 99 1 1 2 2 4 6 6 5 7 5 3 3 7 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 例えばシンボル c の符号長は、 c の番号は 2 なので n + i = 8 + 2 = A[10] A[10] → A[7] → A[6] → A[4] → A[2] → A[1] と葉から始まり親を辿って根に到達 5回辿ったので、符号長は 5

1 99 1 1 2 2 4 6 6 5 7 5 3 3 7 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ここで 子 → 親はかならず右から左へ向かっている。ということは、逆に左から右、つまり先頭から末尾に向かうのは親から子に向かう動きになる。 この特徴を利用すると、左から順番に A[i] = A[A[i]] + 1 していくだけで、A[n + i] にシンボル i の符号長が入る。これを理解するために動きを追ってみる。 まず根は A[1] = 0 とする 1 1 1 2 2 4 6 6 5 7 5 3 3 7 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

1 1 1 2 2 4 6 6 5 7 5 3 3 7 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 次の要素 A[2] は 1 で、この 1 は根である A[1] の添え字を指さしている。 さて、添え字を 1 → 2 と移動してきたのは、左から右へ移動をしたということで、これはハフマン木の親から子を辿っていることに等しい。 ハフマン木では幹を一回辿るたびに符号長が +1 されるわけだから、(親が今辿った道までの符号長を記録していると考えると) A[2] までの符号長は親である A[1] の符号長 +1 である。 先に A[1] = 0 としたのは、根は当然まだ幹を一度も辿ってないので、そこまでの符号長が 0 ということ。よって A[2] = A[A[1]] + 1 = 0 + 1 1 1 1 2 2 4 6 6 5 7 5 3 3 7 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 同じように、A[3] = 1 → A[3] = A[A[1]] + 1 = 0 + 1

1 1 1 2 2 4 6 6 5 7 5 3 3 7 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 A[4] = 2 ということは、A[4] へは親である A[2] から辿ってくるということ。従って A[4] での符号長は A[4] = A[A[2]] + 1 このように、左から順番に A[i] = A[A[i]] + 1 するということは、親からひとつ子を辿ったら、親が持っていた符号の長さに +1 したことに等しい 2n まで処理すると最終的に以下のようになる 1 1 1 2 2 3 4 4 3 5 3 2 2 5 3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 先にも述べたように、シンボル i の葉に相当するのは A[n + i]。赤枠で囲った右半分の配列の中身が、各シンボルの符号長である。例えば、シンボル c (番号2) の符号長は A[n + i] = A[8 + 2] = 5 おしまい

参考文献 Ian H. Witten, Alistair Moffat, Timothy C. Bell 『Managing Gigabytes: Compressing and Indexing Documents and Images』, Morgan Kaufmann Pub, 1999