FGK符号


関数名
enFGK  FGK符号
deFGK  FGK符号からの復号
形式
FGK符号
    long enFGK(unsigned char *code, long clen,
               unsigned char *text, long tlen);

FGK符号からの復号
    long deFGK(unsigned char *text, long tlen,
               unsigned char *code, long clen);
引数
FGK符号関数において
    code  (入出力)FGK符号を格納する配列
    clen  (入力)  配列codeの長さ
    text  (入力)  テキストを格納する配列
    tlen  (入力)  配列textの長さ

FGK符号からの復号関数において
    text  (入出力)テキストを格納する配列
    tlen  (入力)  配列textの長さ
    code  (入力)  FGK符号を格納する配列
    clen  (入力)  配列codeの長さ
関数値
FGK符号関数について
    FGK符号の長さ。符号化に失敗したときは -1L。

FGK符号からの復号関数について
    テキストの長さ。復号に失敗したときは -1L。
注意事項

用例(fgk-test.c

プログラム(fgk.c
typedef struct {
    int  chr;            /* 文字 */
    long count;          /* 子ノードの出現頻度の和 */
    int  parent;         /* 親ノードへ */
    int  left;           /* 左側の子ノードへ */
    int  right;          /* 右側の子ノードへ */
} NODE;

#define ROOT         0                /* ルート */
#define EOF_NODE     1                /* EOFノード */
#define CHAR_SIZE    256
#define NODE_SIZE    (2*CHAR_SIZE+2)  /* ノードの最大個数 */

long enFGK(unsigned char *code, long clen,
           unsigned char *text, long tlen)
{
    int    i, j, k;
    long   c, len;
    int    curChar;
    int    zeroNode, thisNode, maxid;
    int    node2id[NODE_SIZE];         /* ノードからそのノード番号 */
    int    char2node[CHAR_SIZE];
    NODE   node[NODE_SIZE]; int freeNode;
    char   bit[NODE_SIZE];  char *bitp;
    static unsigned char bit0[8] = {~128,~64,~32,~16,~8,~4,~2,~1};
    static unsigned char bit1[8] = { 128, 64, 32, 16, 8, 4, 2, 1};

    if (tlen <= 0) return -1L;

    for (i = 0; i < CHAR_SIZE; i++) char2node[i] = 0;

    /* 初期ハフマン木 */
    zeroNode = 2;
    node[2].count = 0;
    node[2].parent = ROOT;

    node[EOF_NODE].count = 1;
    node[EOF_NODE].parent = ROOT;

    node[ROOT].count = 0x7FFFFFFFL;
    node[ROOT].parent = 0;
    node[ROOT].left = zeroNode;
    node[ROOT].right = EOF_NODE;

    node2id[zeroNode] = 0;
    node2id[EOF_NODE] = 1;
    node2id[ROOT]     = 2;

    freeNode = 3;

    /* 各文字を符号化 */
    for (i = 0, c = 0, len = 0, bitp = bit; ; c++) {
        /* 入力文字に対応するノード */
        if (c >= tlen) thisNode = EOF_NODE;
        else if ((thisNode = char2node[curChar = text[c]]) == 0) {
            thisNode = zeroNode;
            for (j = 7; j >= 0; j--) *bitp++ = curChar & bit1[j];
        }

        /* 符号語を得る */
        k = thisNode;
        do {
            int parent = node[k].parent;
            *bitp++ = (node[parent].left == k) ? 0 : 1;
            k = parent;
        } while (k != ROOT);

        /* 逆順に出力 */
        do {
            if (*--bitp) *code |= bit1[i];
            else         *code &= bit0[i];
            if (++i == 8) {
                code++;
                if (++len > clen) return -1L;
                i = 0;
            }
        } while (bitp != bit);

        if (c == tlen) break;    /* EOF */

        /* ハフマン木を更新 */
        while (thisNode != ROOT) {
            if (thisNode == zeroNode) {
                for (j = 0; j < freeNode; j++) node2id[j] += 2;
                node[zeroNode].left  = freeNode;
                node[freeNode].count = 0;
                node[freeNode].parent = zeroNode;
                node2id[freeNode] = 0;
                freeNode++;

                node[zeroNode].right = freeNode;
                node[freeNode].count = 1;
                node[freeNode].parent = zeroNode;
                node2id[freeNode] = 1;
                char2node[curChar] = freeNode;     /* 新文字 */
                freeNode++;

                thisNode = zeroNode;
                zeroNode = freeNode - 2;
            } else {
                if (node[thisNode].count == 1 &&
                    node[k = node[thisNode].parent].count == 1) {
                    /* 0-ノードの兄弟 */
                    for (j = 0, maxid = thisNode; j < freeNode; j++)
                        if (node[j].count == 1 && j != k &&
                            node2id[j] > node2id[maxid]) maxid = j;
                } else {
                    for (j = 0, maxid = thisNode; j < freeNode; j++)
                        if (node[thisNode].count == node[j].count &&
                            node2id[j] > node2id[maxid]) maxid = j;
                }
                if (maxid != thisNode) {
                    int maxidP = node[maxid].parent;
                    int thisP  = node[thisNode].parent;
                    int id = node2id[maxid];

                    if (node[maxidP].left == maxid)
                         node[maxidP].left  = thisNode;
                    else node[maxidP].right = thisNode;
                    if (node[thisP].left  == thisNode)
                         node[thisP].left  = maxid;
                    else node[thisP].right = maxid;
                    node[maxid].parent = thisP;
                    node[thisNode].parent = maxidP;
                    
                    node2id[maxid] = node2id[thisNode];
                    node2id[thisNode] = id;
                }
                node[thisNode].count++;
                thisNode = node[thisNode].parent;
            }
        }
    }
    if (i > 0) len++;
    return (len > clen) ? -1L : len;
}

long deFGK(unsigned char *text, long tlen,
           unsigned char *code, long clen)
{
    int    i, j, k;
    long   c, len;
    int    curChar;
    int    zeroNode, thisNode, maxid;
    int    node2id[NODE_SIZE];         /* ノードからそのノード番号 */
    NODE   node[NODE_SIZE]; int freeNode;
    static unsigned char bit1[8] = { 128, 64, 32, 16, 8, 4, 2, 1};

    if (clen <= 0) return -1L;

    /* 初期ハフマン木 */
    zeroNode = 2;
    node[2].chr = CHAR_SIZE+1;
    node[2].count = 0;
    node[2].parent = ROOT;

    node[EOF_NODE].chr = CHAR_SIZE;
    node[EOF_NODE].count = 1;
    node[EOF_NODE].parent = ROOT;

    node[ROOT].count = 0x7FFFFFFFL;
    node[ROOT].parent = 0;
    node[ROOT].left = zeroNode;
    node[ROOT].right = EOF_NODE;

    node2id[zeroNode] = 0;
    node2id[EOF_NODE] = 1;
    node2id[ROOT]     = 2;

    freeNode = 3;

    /* 復号化 */
    for (i = 0, c = 0, len = 0; ; ) {
        /* 符号語に対応する文字ノードを得る */
        thisNode = ROOT;
        do {
            thisNode = (*code & bit1[i]) ?
                 node[thisNode].right : node[thisNode].left;
            if (thisNode >= freeNode) return -1L;
            if (++i == 8) {
                code++;
                if (++c > clen) return -1L;
                i = 0;
            }
        } while (node[thisNode].chr < 0);

        if (thisNode == EOF_NODE) break;     /* EOF */

        if (thisNode == zeroNode) {          /* 新文字 */
            curChar = 0;
            for (j = 0; j < 8; j++) {
                if (*code & bit1[i]) curChar |= bit1[j];
                if (++i == 8) {
                    code++;
                    if (++c > clen) return -1L;
                    i = 0;
                }
            }
        } else curChar = node[thisNode].chr;

        if (++len > tlen) return -1L;
        *text++ = curChar;                  /* 文字の出力 */

        /* ハフマン木を更新 */
        while (thisNode != ROOT) {
            if (thisNode == zeroNode) {
                for (j = 0; j < freeNode; j++) node2id[j] += 2;
                node[zeroNode].left  = freeNode;
                node[freeNode].count = 0;
                node[freeNode].parent = zeroNode;
                node2id[freeNode] = 0;
                node[freeNode].chr = CHAR_SIZE + 1;
                freeNode++;

                node[zeroNode].right = freeNode;
                node[freeNode].count = 1;
                node[freeNode].parent = zeroNode;
                node2id[freeNode] = 1;
                node[freeNode].chr = curChar;
                freeNode++;

                thisNode = zeroNode;
                node[thisNode].chr = -1;
                zeroNode = freeNode - 2;
            } else {
                if (node[thisNode].count == 1 &&
                    node[k = node[thisNode].parent].count == 1) {
                    /* 0-ノードの兄弟 */
                    for (j = 0, maxid = thisNode; j < freeNode; j++)
                        if (node[j].count == 1 && j != k &&
                            node2id[j] > node2id[maxid]) maxid = j;
                } else {
                    for (j = 0, maxid = thisNode; j < freeNode; j++)
                        if (node[thisNode].count == node[j].count &&
                            node2id[j] > node2id[maxid]) maxid = j;
                }
                if (maxid != thisNode) {
                    int maxidP = node[maxid].parent;
                    int thisP  = node[thisNode].parent;
                    int id = node2id[maxid];

                    if (node[maxidP].left == maxid)
                         node[maxidP].left  = thisNode;
                    else node[maxidP].right = thisNode;
                    if (node[thisP].left  == thisNode)
                         node[thisP].left  = maxid;
                    else node[thisP].right = maxid;
                    node[maxid].parent = thisP;
                    node[thisNode].parent = maxidP;

                    node2id[maxid] = node2id[thisNode];
                    node2id[thisNode] = id;
                }
                node[thisNode].count++;
                thisNode = node[thisNode].parent;
            }
        }
    }
    return len;
}
説明
ハフマン符号には2つの問題点 がある。

  1. 各文字の出現頻度を求めるには、いったん入力テキストを走査しなければ ならない。ハフマン木を作成した後、再び入力テキストを走査し、各文字を実際 に符号化する。つまり、入力テキストを2度読み込む必要があるので、オンライ ンでの用途には不向きである。
  2. 復号できるように、各文字と符号語(またはハフマン木)との対応関係を 符号の先頭に付加しなければならない。つまり、符号そのもの以外にオーバヘッ ド部分がある。

これらの問題点を解決するために、入力テキストから1文字を読み込むごとに、 その時点までの各文字の出現頻度を再計算し、ハフマン木をつくり直し、文字を 符号化することが考案された。ハフマン木はその時点での出現頻度に対して最適 であり続けるように変化するので、適応型ハフマン符号という。またハフマン木 が文字ごとに変わるので、動的ハフマン符号ともいう。

しかし、実際にこのようにしてハフマン木を文字ごとに再構成することは、かな りの計算時間がかかる。実際の適応型ハフマン符号アルゴリズムでは、ハフマン 木の変形を最小限にするための工夫が必要である。

適応型ハフマン符号を少ない計算量で実現するアイデアは、最初 1970 年代に N. Faller と R. G. Gallager によって独立に考案された。1985年に Knuth が このアイデアに改良を加え、FGKアルゴリズムを開発した。

アルゴリズムの基礎は、Gallager によって定義された兄弟条件である。ある 木が兄弟条件を満たすとは、ルート以外の各ノードが1つの兄弟をも ち、しかも、ノードを兄弟同士が隣り合うように重みの非増加順に並べること ができる場合をいう。Gallager は、ある木がハフマン木であるのは、その木が 兄弟条件を満たす場合であり、かつその場合に限ることを示した。ここでいう 重みとはノードの文字出現頻度ののことで、内部ノードの文字出現頻度はその 子ノードのそれの和である。

FGK符号においても、初期ハフマン木はルートとその子ノード2つからなる。 2つの子ノードは、それぞれ「未出現文字(以下 0-ノードと呼ぶ)」とEOF (符号の終了を表す特別記号)に対応する。0-ノードの出現頻度を 0、EOF ノードの出現頻度を 1 とする。また、ノード番号は 0-ノードが 0、EOFノード が 1、ルートが 2 の順とする。

0-ノードは、現在までに出現していない文字が初めて入力テキスト中に現れた ときに、その文字を符号化するために使う。その場合、0-ノードに対する符号 語を出力した後に、文字そのものも出力する(たとえば ASCIIコード等による)。

ハフマン木を以下のように更新する。

  1. 入力文字に対応する葉ノードに注目する。もし、初めて現れた文字 であれば、0-ノードに注目する。
  2. 現在の注目しているノードが 0-ノードである場合
    0-ノードに対して、新たに2つの重み 0 の子ノードを付け加え、そのうちの 左側の子ノードを 0-ノードとし、右側の子ノードに初めて現れた文字を割り 当てる。左側のノード、右側のノード、今までの木のノード番号、という順で ノード番号をつけ直した後、右側の子ノードを注目する。
  3. 現在の注目しているノードが 0-ノードの兄弟の場合
    ノードの重みを1増やした後、その親ノードを注目する。
  4. 現在の注目しているノードが上記の 2. と 3. 以外の場合
    現在のノードと同じ重みをもつノードの中から、一番ノード番号の大きいノード を探し(自分自身でもよい)、そのノード(とその子孫)と現在のノードを入れ 替える。つぎに、現在のノードの重みを1増やした後、親のノードを注目する。
  5. 上記の 4. の処理をルートに到達するまで繰り返す。

関連関数
シャノン・ファノ符号算術符号ハフマン符号Vitter符号LZ77符号LZW符号