Taro-スタック(公開版).jtd

Similar documents
Taro-リストⅠ(公開版).jtd

Taro-リストⅢ(公開版).jtd

Taro-2分探索木Ⅱ(公開版).jtd

Taro-2分探索木Ⅰ(公開版).jtd

Taro-再帰関数Ⅱ(公開版).jtd

Taro-再帰関数Ⅲ(公開版).jtd

Taro-最大値探索法の開発(公開版

Taro-プログラミングの基礎Ⅱ(公

Taro-ポインタ変数Ⅰ(公開版).j

バイオプログラミング第 1 榊原康文 佐藤健吾 慶應義塾大学理工学部生命情報学科

プログラミングI第10回

memo

Microsoft PowerPoint - 06.pptx

始めに, 最下位共通先祖を求めるための関数 LcaDFS( int v ) の処理を記述する. この関数は値を返さない再帰的な void 関数で, 点 v を根とする木 T の部分木を深さ優先探索する. 整数の引数 v は, 木 T の点を示す点番号で, 配列 NodeSpace[ ] へのカーソル

memo

第2回

< F2D837C E95CF CF68A4A94C5816A2E6A>

PowerPoint プレゼンテーション

PowerPoint プレゼンテーション

Microsoft PowerPoint - 11.pptx

Microsoft PowerPoint - lec10.ppt

PowerPoint プレゼンテーション

kiso2-09.key

Microsoft PowerPoint - kougi9.ppt

Microsoft Word - no205.docx

文字数は1~6なので 同じ本数の枝を持つパスで生成される呪文の長さは最大で6 倍の差がある 例えば 上図のようなケースを考える 1サイクル終了した時点では スター節点のところに最強呪文として aaaaaac が求まる しかしながら サイクルを繰り返していくと やがてスター節点のところに aaaaaa

PowerPoint Template

memo

Microsoft Word - no15.docx

Microsoft Word - Cプログラミング演習(10)

Microsoft Word - Cプログラミング演習(11)

Microsoft PowerPoint - kougi10.ppt

問 2 ( 型変換 ) 次のプログラムを実行しても正しい結果が得られない 何が間違いかを指摘し 正しく修正せよ ただし int サイズが 2 バイト long サイズが 4 バイトの処理系での演算を仮定する #include <stdio.h> int main( void ) { int a =

プログラミング方法論 II 第 14,15 回 ( 担当 : 鈴木伸夫 ) 問題 17. x 座標と y 座標をメンバに持つ構造体 Point を作成せよ 但し座標 は double 型とする typedef struct{ (a) x; (b) y; } Point; 問題 18. 問題 17 の

Microsoft Word - no206.docx

Taro-ファイル処理(公開版).jtd

Taro-再帰関数Ⅰ(公開版).jtd

cp-7. 配列

Prog1_15th

[Problem F] 古い記憶 良さそうな方法は思いつかなかった アイディア募集中!!! かなり強引に解いている 各判定データに対して 30 秒程度かかる 元の文章と改ざん文章の関係を考える ウィルス感染の可能性は高々 2 回であり 各々の感染では 1 文字削除 1 文字追加 1 文字変更が行われ

gengo1-8

プログラミング基礎

memo

プログラム例 /* ACM-ICPC2009 国内予選 Problem C */ // // filename = pc1.c // コンパイル

CプログラミングI

Microsoft PowerPoint - C言語の復習(配布用).ppt [互換モード]

ポインタ変数

r07.dvi

1. 入力した正の整数を降順に並べ換えて出力するプログラムを作成せよ プログラムは個別にコンパイルし make コマンドで実行すること 入力データは 50 以下とし 以下の数が混在しているとする 16 進数 : 先頭 1 文字が x または X( エックスの小文字か大文字 ) 8 進数 : 先頭 1

Microsoft Word - 第5回 基本データ構造2(連結リスト).doc

ohp07.dvi

次に示す数値の並びを昇順にソートするものとする このソートでは配列の末尾側から操作を行っていく まず 末尾の数値 9 と 8 に着目する 昇順にソートするので この値を交換すると以下の数値の並びになる 次に末尾側から 2 番目と 3 番目の 1

Microsoft Word - 3new.doc

slide5.pptx

Microsoft Word - Cプログラミング演習(12)

2006年10月5日(木)実施

Microsoft PowerPoint - 6.pptx

II 3 yacc (2) 2005 : Yacc 0 ~nakai/ipp2 1 C main main 1 NULL NULL for 2 (a) Yacc 2 (b) 2 3 y

C プログラミング演習 1( 再 ) 2 講義では C プログラミングの基本を学び 演習では やや実践的なプログラミングを通して学ぶ

講習No.9

Microsoft PowerPoint - 05.pptx

Microsoft Word - no12.doc

Microsoft Word - Cプログラミング演習(9)

Microsoft PowerPoint - lec4.ppt

Taro-数値計算の基礎Ⅱ(公開版)

Microsoft Word - 13

Prog1_12th

PowerPoint Presentation

演算増幅器

memo

gengo1-11

Microsoft Word - Training10_プリプロセッサ.docx

演習課題No12

PowerPoint プレゼンテーション

I. Backus-Naur BNF : N N 0 N N N N N N 0, 1 BNF N N 0 11 (parse tree) 11 (1) (2) (3) (4) II. 0(0 101)* (

PowerPoint Presentation

Microsoft PowerPoint pptx

PowerPoint プレゼンテーション

Microsoft PowerPoint - CproNt02.ppt [互換モード]

Prog1_10th

:30 12:00 I. I VI II. III. IV. a d V. VI

Microsoft Word - Cプログラミング演習(3)

gengo1-12

memo

Microsoft Word - Cプログラミング演習(8)

Microsoft Word - Cプログラミング演習(7)

program7app.ppt

プログラミング基礎

gengo1-12

プログラミング基礎

Taro-cshプログラミングの応用.jt

char int float double の変数型はそれぞれ 文字あるいは小さな整数 整数 実数 より精度の高い ( 数値のより大きい より小さい ) 実数 を扱う時に用いる 備考 : 基本型の説明に示した 浮動小数点 とは数値を指数表現で表す方法である 例えば は指数表現で 3 書く

Taro-ビット処理(公開版).jtd

Microsoft PowerPoint - C4(反復for).ppt

memo

Microsoft PowerPoint - algo ppt [互換モード]

Microsoft PowerPoint - prog13.ppt

第2回

I. Backus-Naur BNF S + S S * S S x S +, *, x BNF S (parse tree) : * x + x x S * S x + S S S x x (1) * x x * x (2) * + x x x (3) + x * x + x x (4) * *

人工知能入門

Transcription:

0. 目次 1. 1. 1 配列によるの実現 1. 2 再帰的なデータ構造によるの実現 1. 3 地図情報処理 1. 4 問題 問題 1 グラフ探索問題 - 1 -

1. は データの出し入れが一カ所で行われ 操作は追加と削除ができるデータ構造をいう 出入口 追加 削除 操作 最初 111 追加 111 222 追加 111 222 333 追加 111 222 333 444 追加 111 222 333 は一杯で追加失敗 333 削除 111 222 222 削除 111-2 -

1. 1 配列によるの実現 配列での実現 : 変数 top でに保存されるデータの位置を示す 変数 top の初期値は 0 とする データを保存する場合 変数 top が指す位置にデータが保存され 変数 top の値を 1 増やす データを取り出す場合 変数 top の値を 1 減らし 変数 top の指す位置からデータが取り出される 変数 top の値は 要素の数でもある 最初 配列 0 1 2 top=0 0 1 2 111 追加配列 111 top=1 0 1 2 222 追加配列 111 222 top=2 0 1 2 333 追加 配列 111 222 333 top=3 0 1 2 444 追加 配列 111 222 333 は一杯で追加失敗 top=3 0 1 2 333 削除配列 111 222 top=2 0 1 2 222 削除配列 111 top=1-3 -

( 配列表現 ) へ要素の追加 追加前 0 1 top=2 stack 111 222 追加データ : 333 追加後 0 1 2 top=3 stack 111 222 333 stack[top] = x; top++; ( 配列表現 ) から要素の削除 削除前 0 1 2 top=3 111 222 333 削除データ : 333 削除後 0 1 top=2 111 222 top--; - 4 -

プログラム ( st111.c) の操作を行うプログラム 操作 1: 正整数を読み込みに追加 操作 0: からデータを取り出す 操作 -1: 終了 1 /* << st111.c >> */ 2 #include <stdio.h> 3 #define SMAX 2 /* に保存できるデータ数 */ 4 5 int main() { 6 int i, 7 op, /* 操作 */ 8 stack[smax], /* を実現する配列 */ 9 top, /* に保存されるデータの位置 */ 10 x; /* データ */ 11 12 /* の初期化 */ 13 top = 0; 14 15 /* 操作 */ 16 while( 1 ) { 17 /* の表示 */ 18 printf(" : "); 19 for( i=0; i<top; i++ ) { printf("%d ",stack[i]); } 20 printf("\n"); 21 22 /* 操作入力 */ 23 printf(" 操作 : "); scanf("%d",&op); 24 switch( op ) { 25 26 case 1: /* 追加データを読み込む */ 27 printf(" データ :"); scanf("%d",&x); 28 /* にデータを追加 */ 29 if( top < SMAX ) { 30 stack[top] = x; top++; 31 } else { 32 printf(" が一杯です \n"); 33 } 34 break; 35 36 case 0: /* からデータを削除 */ 37 if( top > 0 ) { 38 top--; 39 } else { 40 printf(" が空です \n"); 41 } 42 break; - 5 -

43 44 case -1: /* 終了 */ 45 exit(0); 46 } 47 } 48 } 実行結果 % cc st111.c %./a.out : 操作 : 1 データ :111 : 111 操作 : 1 データ :222 : 111 222 操作 : 1 データ :333 が一杯です : 111 222 操作 : 0 : 111 操作 : 0 : 操作 : 0 が空です : 操作 : -1-6 -

1. 2 再帰的なデータ構造によるの実現 構造体を用いた再帰的なデータ構造 ( 構造体のメンバに他の構造体を指すポインタを含む ) で実現する head は の先頭を指す NULL はリストの最後を意味する head リストヘッド NULL 操作 最初 head NULL 111 追加 head 111 NULL 222 追加 head 222 111 NULL 333 追加 head 333 222 111 NULL 333 削除 ( 注意 ) 構造体が確保できる間 追加操作が可能 head 222 111 NULL 222 削除 head 111 NULL - 7 -

( 再帰的表現 ) へ要素の追加 追加前 head a b NULL 追加後 追加データ : x head x a b NULL p p = (struct NODE *)malloc(sizeof(struct NODE)); p->info = x; p->next = head->next; head->next = p; ( 再帰的表現 ) から要素の削除 削除前 head a b c NULL q 削除後 head b c NULL q = head->next; head->next = q->next; - 8 -

プログラム ( st121.c) の操作を行うプログラム操作 1: 正整数を読み込みに追加 操作 0: からデータを取り出す 操作 -1: 終了 1 /* << st121.c >> */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 struct NODE { 5 int info; 6 struct NODE *next; 7 }; 8 9 int main() { 10 int op, /* 操作 */ 11 x; /* データ */ 12 struct NODE *head, /* の先頭を指すポインタ変数 */ 13 *p, /* 作業用ポインタ変数 */ 14 *q; /* 作業用ポインタ変数 */ 15 16 /* リストヘッドの作成 */ 17 head = (struct NODE *)malloc(sizeof(struct NODE)); 18 head->next = NULL; 19 20 while( 1 ) { 21 /* の表示 */ 22 printf(" : "); 23 p = head->next; 24 while( p!= NULL ) { printf("%d ",p->info); p = p->next; } 25 printf("\n"); 26 27 /* 操作入力 */ 28 printf(" 操作 : "); scanf("%d",&op); 29 switch( op ) { 30 31 case 1: /* 追加データを読み込む */ 32 printf(" データ :"); scanf("%d",&x); 33 /* にデータを追加 */ 34 p = (struct NODE *)malloc(sizeof(struct NODE)); 35 p->info = x; 36 p->next = head->next; 37 head->next = p; 38 break; 39 40 case 0: /* からデータを削除 */ 41 if( head->next!= NULL ) { 42 q = head->next; 43 head->next = q->next; - 9 -

44 } else { 45 printf(" は空です \n"); 46 } 47 break; 48 49 case -1: /* 終了 */ 50 exit(0); 51 } 52 } 53 } 実行結果 % cc st121.c %./a.out : 操作 : 1 データ :111 : 111 操作 : 1 データ :222 : 222 111 操作 : 1 データ :333 : 333 222 111 操作 : 0 : 222 111 操作 : 0 : 111 操作 : 0 : 操作 : 0 は空です : 操作 : -1-10 -

1. 3 地図情報処理 0 と 1 で与えられた地図情報から島 (1 が上下左右に隣接する領域 ) の個数を求める問題を考察する なお 0 は海を表し 1 は陸を表す 地図情報 000001100000 000111111000 000110000000 011000111100 000000001100 011100000000 000100000000 この場合 島の個数は 4 となる 考え方 1 地図のすべての点について 2 を行う 2 点の値が 1 の場合 隣接する点で値が 1 となる点をひとつの島として登録する 登録した島の点は 再度登録されないように値を変更しておく 動作 ( 1 ) 地図情報 ( 2 ) 地図情報 ( 3 ) 地図情報 000001100000 000002200000 000002200000 000111111000 000222222000 000222222000 000110000000 000220000000 000220000000 011000111100 011000111100 033000111100 000000001100 000000001100 000000001100 011100000000 011100000000 011100000000 000100000000 000100000000 000100000000 ( 4 ) 地図情報 ( 5 ) 地図情報 000002200000 000002200000 000222222000 000222222000 000220000000 000220000000 033000444400 033000444400 000000004400 000000004400 011100000000 055500000000 000100000000 000500000000-11 -

プログラム ( st131.c) 1 /* << st131.c >> */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #define M 30 /* 行の最大値 */ 5 #define N 30 /* 列の最大値 */ 6 7 int main() { 8 int a[m+2][n+2], /* 地図情報を保存する配列 */ 9 i,j,k,l, 10 m, /* 行数 */ 11 n, /* 列数 */ 12 num, /* 島の番号 */ 13 ti,tj; 14 int si[m*n], /* 関連の配列 */ 15 sj[m*n], /* 関連の配列 */ 16 top; /* 関連の変数 */ 17 18 /* 地図情報 ( m: 行数 n: 列数 ) の読み込み */ 19 scanf("%d%d",&m,&n); 20 if( (m <= 0) (m > M) 21 (n <= 0) (n > N) ) { exit; } 22 for( i=1; i<=m; i++ ) { 23 for( j=1; j<=n; j++ ) { scanf("%1d",&a[i][j]); } 24 } 25 26 /* 与えられた地図情報の周囲を海としておくと 処理が簡潔になる */ 27 for( j=0; j<=n+1; j++ ) { 28 a[0][j] = 0; a[m+1][j] = 0; 29 } 30 for( i=1; i<=m; i++ ) { 31 a[i][0] = 0; a[i][n+1] = 0; 32 } 33 34 /* 地図情報の表示 */ 35 printf(" 探索前 \n"); 36 for( i=1; i<=m; i++ ) { 37 for( j=1; j<=n; j++ ) { printf("%d",a[i][j]); } 38 printf("\n"); 39 } 40 41 /* 初期設定 */ 42 num = 1; 43 44 /* 地図情報のすべての点について 探索する */ 45 for( i=1; i<=m; i++ ) { 46 for( j=1; j<=n; j++ ) { 47 /* 点 (i,j) が 0 すなわち 海の場合 スキップ */ 48 if( a[i][j] == 0 ) { continue; } - 12 -

49 50 /* 点 (i,j) が 1 以外 すなわち すでに探索済みの場合 スキップ */ 51 if( a[i][j] > 1 ) { continue; } 52 53 /* 点 (i,j) が 1 すなわち未探索の場合 島を確定する */ 54 num++; 55 56 /* の初期化 */ 57 si[0] = i; sj[0] = j; top = 1; 58 59 /* (si[*],sj[*]) を使って 点 (ti,tj) に隣接する陸の 60 点をすべて同じ島の番号に設定する */ 61 while( top > 0 ) { 62 ti = si[top-1]; tj = sj[top-1]; top--; 63 a[ti][tj] = num; 64 65 if( a[ti-1][tj] == 1 ) { 66 si[top]=ti-1; sj[top]=tj; top++; 67 } 68 if( a[ti+1][tj] == 1 ) { 69 si[top]=ti+1; sj[top]=tj; top++; 70 } 71 if( a[ti][tj-1] == 1 ) { 72 si[top]=ti; sj[top]=tj-1; top++; 73 } 74 if( a[ti][tj+1] == 1 ) { 75 si[top]=ti; sj[top]=tj+1; top++; 76 } 77 } 78 79 /* 途中結果の表示 */ 80 printf(" 途中結果 \n"); 81 for( k=1; k<=m; k++ ) { 82 for( l=1; l<=n; l++ ) { printf("%1d",a[k][l]); } 83 printf("\n"); 84 } 85 } 86 } 87 88 /* 最終結果の表示 */ 89 printf(" 探索後 \n"); 90 for( i=1; i<=m; i++ ) { 91 for( j=1; j<=n; j++ ) { printf("%1d",a[i][j]); } 92 printf("\n"); 93 } 94 printf(" 島の個数 : %d\n",num-1); 95 } - 13 -

実行結果 % cat st131.dat 7 12 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 % cc st131.c %./a.out < st131.dat 探索前 000001100000 000111111000 000110000000 011000111100 000000001100 011100000000 000100000000 途中結果 000002200000 000222222000 000220000000 011000111100 000000001100 011100000000 000100000000 途中結果 000002200000 000222222000 000220000000 033000111100 000000001100 011100000000 000100000000 途中結果 000002200000 000222222000 000220000000 033000444400 000000004400 011100000000 000100000000 途中結果 000002200000 000222222000 000220000000 033000444400-14 -

000000004400 055500000000 000500000000 探索後 000002200000 000222222000 000220000000 033000444400 000000004400 055500000000 000500000000 島の個数 : 4-15 -

1. 4 問題 問題 1 グラフ探索問題 与えられたグラフにおいて 開始節点から到達できる節点をすべて求める問題を考察する 1 3 5 2 4 考え方 節点に隣接する節点を隣接節点とよぶ ( 手順 a) 始点を訪問済節点とし に格納する 他の節点は未訪問節点とする ( 手順 b) から節点をひとつ取り出す この節点の隣接節点が未訪問の場合 訪問済節点とした後 に格納する 訪問済の場合 何もしない ( 手順 c) 手順 b をが空になるまで繰り返す ( 手順 d) 訪問済節点を表示する 動作開始節点を節点 1 とする ( 1 ) 手順 a 節点 1 3 1 訪問済 2 5 3 4 2 4 1 5 未訪問節点は空欄とする ( 2 ) 手順 b 節点 1 3 1 訪問済 2 訪問済 5 3 訪問済 3 4 2 4 2 5 未訪問節点は空欄とする - 16 -

( 3 ) 手順 b 節点 1 3 1 訪問済 2 訪問済 5 5 3 訪問済 4 4 訪問済 2 4 2 5 訪問済 未訪問節点は空欄とする ( 4 ) 手順 b 節点 1 3 1 訪問済 2 訪問済 5 3 訪問済 4 4 訪問済 2 4 2 5 訪問済 未訪問節点は空欄とする ( 5 ) 手順 b 節点 1 3 1 訪問済 2 訪問済 5 3 訪問済 4 訪問済 2 4 2 5 訪問済 未訪問節点は空欄とする ( 6 ) 手順 b 終了 節点 1 3 1 訪問済 2 訪問済 5 3 訪問済 4 訪問済 2 4 5 訪問済 未訪問節点は空欄とする プログラム ( st141.c) 1 /* << st141.c >> */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #define N 99 /* 節点の最大数 */ 5 struct NODE { 6 int info; 7 struct NODE *next; /* nextはポインタ変数 */ 8 }; 9 10 int main() { 11 struct NODE *head[n+1], /* 節点 iに隣接する節点のリスト */ 12 *p, /* 作業用ポインタ変数 */ - 17 -

13 *q; /* 作業用ポインタ変数 */ 14 int d0,d1, /* (d0,d1) は辺を意味する */ 15 i, 16 n, /* 節点の個数 */ 17 s, /* 開始節点 */ 18 v[n+1]; /* v[i]=0 節点 iが未訪問 19 v[i]=1 節点 iが訪問済を意味する */ 20 int stack[99], /* 関係の配列 */ 21 top; /* 関係の変数 */ 22 23 /* 節点の個数 nを入力 */ 24 scanf("%d",&n); 25 if( (n <= 0) (n > N) ) { exit; } 26 27 /* 各節点のリストヘッドの作成 */ 28 for( i=1; i<=n; i++ ) { 29 head[i] = (struct NODE *)malloc(sizeof(struct NODE)); 30 head[i]->info = i; 31 head[i]->next = NULL; 32 } 33 34 /* グラフの作成 */ 35 while( 1 ) { 36 /* 辺の入力 (d0,d1) は辺を意味する */ 37 scanf("%d%d",&d0,&d1); 38 if( (d0 <= 0) (d0 > n) (d1 <= 0) (d1 > n) ) { break; } 39 40 q = (struct NODE *)malloc(sizeof(struct NODE)); 41 q->info = d1; 42 q->next = NULL; 43 44 /* 入力した辺 (d0,d1) をリストの先頭に挿入する */ 45 p = head[d0]->next; 46 head[d0]->next = 1 ; 47 q->next = 2 ; 48 } 49 50 /* グラフの表示 */ 51 printf(" グラフ \n"); 52 for( i=1; i<=n; i++ ) { 53 printf("%d: ",i); 54 p = head[i]->next; 55 while( p!= NULL ) { 56 printf("%d ",p->info); 57 p = p->next; 58 } 59 printf("\n"); 60 } 61 62 /* 到達節点の探索 */ - 18 -

63 64 while( 1 ) { 65 /* 開始節点の入力 */ 66 scanf("%d",&s); 67 if( s == 0 ) { break; } 68 69 /* 初期設定 */ 70 for( i=1; i<=n; i++ ) { /* すべての節点を未訪問に設定 */ 71 v[i] = 0; 72 } 73 stack[0] = s; top = 1; /* topは 次に格納する位置を意味する 74 格納されている節点数でもある */ 75 v[s] = 1; /* 開始節点 sを訪問済にする */ 76 77 /* 操作 */ 78 while( top > 0 ) { 79 /* 節点 v[top-1] に隣接する節点で 未訪問であればに格納 */ 80 p = head[stack[top-1]]->next; top--; 81 while( p!= NULL ) { 82 /* 節点 p->infoが未訪問の場合 */ 83 if( v[p->info] == 3 ) { 84 stack[top] = 4 ; 85 v[stack[top]] = 5 ; 86 top++; 87 } 88 p = p->next; 89 } 90 } 91 92 /* 到達節点の出力 */ 93 printf(" 開始節点 : %d ",s); 94 printf(" 到達節点 : "); 95 for( i=1; i<=n; i++ ) { 96 if( v[i] == 1 ) { printf(" %d",i); } 97 } 98 printf("\n"); 99 } 100 } - 19 -

グラフ 1 1 3 5 2 4 実行結果 % cat g1.dat 5 1 2 1 3 2 1 2 4 3 1 3 4 3 5 4 2 4 3 4 5 5 3 5 4 0 0 1 5 0 % cc st141.c %./a.out < g1.dat グラフ 1: 3 2 2: 4 1 3: 5 4 1 4: 5 3 2 5: 4 3 開始節点 : 1 到達節点 : 1 2 3 4 5 開始節点 : 5 到達節点 : 1 2 3 4 5-20 -

グラフ 2 1 3 7 5 6 2 4 8 実行結果 % cat g2.dat 8 1 2 1 3 2 1 2 4 3 1 3 4 3 5 4 2 4 3 4 5 5 3 5 4 6 7 6 8 7 6 7 8 8 6 8 7 0 0 1 8 0 %./a.out < g2.dat グラフ 1: 3 2 2: 4 1 3: 5 4 1 4: 5 3 2 5: 4 3 6: 8 7 7: 8 6 8: 7 6 開始節点 : 1 到達節点 : 1 2 3 4 5 開始節点 : 8 到達節点 : 6 7 8-21 -