第7回 基本データ構造4 木構造 と その操作2、再帰プログラム

明星大学 情報学科 3年後期 「情報技術Ⅱ」
第7回 Page 1
第7回
基本データ構造4
木構造 と その操作2、再帰プログラム
7-1.木構造の応用
7-1-1.四則演算式の解析
コンパイラや簡易言語等で、計算機が数式を構文解析する際、演算子の優先順位に従って
数式の構成を解析するときに、木構造が用いられる。
例)(4+2)×(3-1) という数式の場合
×
+
4
-
2
3
1
このように 2 分木に展開する。
展開のルールは、
・葉の部分には、数値を置く。それ以外の節は、演算子を置く。
・演算子の優先順位が高いものほど、深い階層(葉に近いほう)にする。
2 分木に展開した後、「(左部分木)(演算子)(右部分木)」という計算を進めればよい。
×
×
+
4
-
2
3
×
6
1
-
3
6
1
計算を進めるにあたり、各節に入っているものを1つずつ読み出して処理をすることに
なるのだが、次のような順番で読み出して処理をすることにより、プログラム的な処理を
簡素化できる。
×
+
4
-
②
2
3
1
①
[1/6]
明星大学 情報学科 3年後期 「情報技術Ⅱ」
第7回 Page 2
この読み出し順に従って記述すると、次のようになる。
このように演算子を、計算する2つの項の後ろに配置する記法を、
逆ポーランド記法(後置記法)という。
この記法の特徴は、演算子の優先順位を考えることなく、括弧を必要としないで、読み出した
順番どおりに計算処理を進められることにある。
実際に、スタックを用いてこの計算をすると、次のようになる。
計算処理をする際のルール
・数値の項を読み出したら・・・
・演算子を読み出したら・・・・・
1
2
3
3
2
6
4
4
6
6
6
4を積む
2を積む
+の計算をし
結果を積む
3を積む
1を積む
例)4+2×3-1 という数式の場合
-
+
4
1
×
2
3
[2/6]
12
-の計算をし ×の計算をし
結果を積む
結果を積む
明星大学 情報学科 3年後期 「情報技術Ⅱ」
第7回 Page 3
7-1-2.データファイルの圧縮
コンピュータで扱う電子データは、ネットワーク上で送信したり、パッケージとして配布したり
する場合、ファイルサイズが小さいほうがよい。
そのために、元のデータを構成している符号を効率よく置き換えることにより、全体の符号長
(ビット数)を小さくする。このことを「圧縮(compress)」という。
その符号置換の方法の一つに「ハフマン法」というものがあり、置換符号の生成に、木構造が
用いられる。
ハフマン法
文字などの固定長の符号で構成されているデータについて、出現回数が多いものには短い
ビット列、少ないものには長いビット列に置き換えることにより、1 つあたりの平均ビット長
を最小にする符号化方式。
例)文字列データの圧縮
'a' , 'b' , 'c' , 'd'の 4 文字からなるデータで、それぞれの出現回数が
50% , 30% , 10% , 10% で、ある場合。
木への展開の手順は、
① 葉の部分に文字を置き、出現回数の多い順に並べる。
② 最小の 2 つを選んで、それらを子にもつ部分木を作る。
③ その子の重みの和を、部分木の重みとする。
④ ②、③を 1 つの木になるまで繰り返す。
⑤ 作成された木の左右の枝に0と1を割り振っていく。
⑥ 根から葉までをたどり、枝に割り振った 0/1 を並べた
ビット列を、葉の要素に割り当てる。
0
1
50%
a
0
1
50%
20%
b
0
1
30%
c
d
10%
10%
圧縮前の均等の長さに符号が割り振られているものとの比較をすると、
文字
a
b
c
d
固定長符号
00
01
10
11
出現回数
50%
30%
10%
10%
ハフマン符号
0
10
110
111
この表から、圧縮前の固定長では 1 文字あたりのビット長は 2bit だが、
圧縮後の 1 文字あたりの平均ビット長は、
1bit×0.5 + 2bit×0.3 + 3bit×0.1 + 3bit×0.1 = 0.5+0.6+0.3+0.3 = 1.7bit
となり、仮に 100 文字分のデータの場合、
圧縮前
2 bit×100 = 200 bit が
圧縮後 1.7 bit×100 = 170 bit で、
30bit(15 文字分)少なくなる。
[3/6]
明星大学 情報学科 3年後期 「情報技術Ⅱ」
第7回 Page 4
7-2.再帰プログラム
7-2-1.木構造と再帰関数
木構造は、親と部分木から成り立っており、部分木をみても、その中身は(新たな)親と部分木と
なっている。
その性質に着目すると、節をたどっていくときに、「部分木の中身をたどる」という処理の中で
さらに「部分木の中身をたどる」という処理が出てくる。
このように、ある処理をする関数の中で、その関数自身を呼び出す仕組みを「再帰(recursive)」
という。
また、そのように自分のなかで自分自身の関数を呼び出すことを「再帰的に呼び出す」ともいう。
例)2 分木の一番深い階層レベルを調べる。
関数 kaisou
引数:その節の階層レベル、その節へのポインタ
ある
左部分木はあるか?
ない
左のレベルを1つ増やして、
左のレベルは、その階層で終わりなので、 左部分木について関数 kaisou を呼ぶ
それが左部分木の階層レベルになる
戻ってきた値が左部分木の深さなので、
それが左部分木の階層レベルになる
ある
右部分木はあるか?
ない
右のレベルを1つ増やして、
右のレベルは、その階層で終わりなので、 右部分木について関数 kaisou を呼ぶ
それが右部分木の階層レベルになる
戻ってきた値が右部分木の深さなので、
それが右部分木の階層レベルになる
左と右の階層レベルのうち、
より深いほうが、その節以下の深さなので、
それを戻り値とする
関数 kaisou の処理が終了なので、戻る
[4/6]
明星大学 情報学科 3年後期 「情報技術Ⅱ」
第7回 Page 5
struct binary_tree {
/* 二分木の節の構造体 */
int
value;
/* データ */
struct binary_tree
*left; /* 左の子へのポインタ */
struct binary_tree
*right; /* 右の子へのポインタ */
};
struct binary_tree *root;
/* 根のポインタ */
int kaisou( int level , struct binary_tree *sub_tree )
{
int left_level , right_level;
/* 部分木の階層を調べる */
if ( sub_tree->left == NULL )
{
/* 左部分木の階層を調べる */
left_level = level;
} else {
left_level = kaisou( level + 1 , sub_tree->left );
}
if ( sub_tree->right == NULL ) {
/* 右部分木の階層を調べる */
right_level = level;
} else {
right_level = kaisou( level + 1 , sub_tree->right );
}
if ( left_level > right_level ) {
return ( left_level );
} else {
return ( right_level );
}
/* 深いほうを戻り値にする */
}
int main( void )
{
tree_entry();
子がない場合は、
その枝のポインタは NULL として
データを作っている
/* 二分木のデータを作る(関数の中身は省略) */
printf( "deepest level = %d\n" , kaisou( 1 , root ) );
/* 根からたどって深さを調べる */
return ( 0 );
}
呼ぶ
1
+1
呼ぶ
+1
2
4
+1
3
3で戻る
2
4
3
+1
4
2
+1
4
+1
4
4
根の左部分木は4で
右部分木は2なので、
全体としては、最終的に
深いほうの4になる。
この部分木は4
(左が3、右が4なので
深いほうの4になる)
この部分木は4
[5/6]
明星大学 情報学科 3年後期 「情報技術Ⅱ」
第7回 Page 6
7-2-2.再帰的に呼び出す関数を作る際の注意
自分の関数内で、自分自身を呼び出しているので、終了条件をきちんと設定していないと、
無限に呼び続けることになり、終わらなくなってしまう。
よって、必ず「もうこれ以上は呼ばない」という条件を設定し、適切に処理を戻す必要がある。
7-2-3.再帰プログラムの例題
ある自然数nまでの総和(1+2+3+・・・+n)を求めるプログラムを、再帰的に呼び出す関数
を用いて作成せよ。
#include <stdio.h>
int sigma( int value )
{
if ( value ==
return (
)
{
);
} else {
return (
+ sigma(
) );
}
}
int main( void )
{
int
n;
printf( "n = " );
scanf( "%d" , &n );
if ( n <= 0 )
{
printf( "Error! Input value is illegal!\n" );
return ( 1 );
}
printf( "sigma = %d\n" , sigma( n ) );
return ( 0 );
}
※この例では、エラーチェックが充分でないので、n が 250 くらいまでしか正常に動作しない。
[6/6]