2 部データ構造とアルゴリズムI レポート課題

本講義では 2 通のレポートで評価します。 レポートは A4 の紙を縦に使い、適宜表紙を付けて提出すること。 またプログラムは C 言語で作成しなさい。

変更履歴

2016/5/26
課題1初出
2016/5/27
課題1のテストプログラムを差し替え。解答には影響しない
2016/6/9
テストプログラムの記載ミスを訂正
2016/7/7
課題2初出
2016/7/28
課題2-4文言訂正

課題2

レポート締切日: 2016年7月29日(金)20:00
提出先: 2 号館 3F レポートボックス

なお、遅れレポートは原則受けとりません。

課題1に引き続き、分数を取り扱う。 分数の電卓を作成することを目指す。 なお構造体として、以下の二つを定義しておく。


typedef struct {
  int bunshi;
  int bunbo;
} BUNSU;
typedef struct stack {
  BUNSU bunsu;
  struct stack* next;
}STACK;

なお、bunsu.h や、他に必要な関数は後述する。

課題2-1

ユークリッドの互除法を利用して、BUNSUのポインタを与えて、その分数を約 分する関数 void yakubun(BUNSU* b) を作成しなさい。 ただし、ユークリッドの互除法とは、二つの整数 a>b≥0 より最大公 約数 gcd(a,b) を求める以下のアルゴリズムである。

  1. もし b=0 なら gcd(a,b)=a とする
  2. aをbで割った余りを a%b とすると gcd(a,b)=gcd(b,a%b) とする

なお、以下のプログラムと結合してテストを行い、実行結果を示しなさい。

テストプログラム


#include <stdio.h>
#include "bunsu.h"

int main(void){
  BUNSU b[] ={{3,4},{-6,3},{10,-2},{-2,-30},{0,-10},{0,11},{0,0}};
  BUNSU *p;
  for(p=b;p->bunbo!=0;p++){
    printbunsu(*p);
    printf(" -> ");
    yakubun(p);
    printbunsu(*p);
    printf("\n");
  }
  return 0;
}

実行例

3/4 -> 3/4
-6/3 -> -2/1
-10/2 -> -5/1
2/30 -> 1/15
0 -> 0
0 -> 0

課題2-2

二つの BUNSU 型の値 a,b を受け取って、和、積を求める関数 BUNSU add(BUNSU a, BUNSU b), BUNSU mul(BUNSU a, BUNSU b) を両方共作成しなさ い。

そして、以下のテストプログラムと結合して動作が正常であることを示しな さい。

テストプログラム


#include <stdio.h>
#include "bunsu.h"
int main(void){
  BUNSU b[] ={{3,4},{-6,3},{10,-2},{-2,-30},{0,0}};
  BUNSU *p;
  BUNSU wa={0,1};
  BUNSU seki = {1,1};
  for(p=b;p->bunbo!=0;p++){
    printbunsu(*p);
    printf("\n");
    wa = add(wa,*p);
    seki = mul(seki,*p);
  }  
  yakubun(&wa);
  yakubun(&seki);
  printf("wa: ");
  printbunsu(wa);
  printf("\tseki: ");
  printbunsu(seki);
  printf("\n");
  return 0;
}

実行例

3/4
-6/3
-10/2
2/30
wa: -371/60	seki: 1/2

課題2-3

構造体 STACK を利用して、BUNSU のスタックを作成しなさい。 具体的には int empty(void), void push(BUNSU b), BUNSU pop(void) の三 つの関数をすべて実装しなさい。 なお、メモリーが確保できなかったときは、その場で stdlib.h で定義され ている exit(1) を呼び出してプログラムを止めてよい。 そして、以下のテストプログラムと結合して、テスト結果を示しなさい。

テストプログラム


#include <stdio.h>
#include "bunsu.h"
int main(void){
  int i,j;
  BUNSU work;
  
  for(i=1; i<10; i++){
    for(j=1; j<10; j++){
      work.bunshi=i;
      work.bunbo=j;
      yakubun(&work);
      printbunsu(work);
      printf("\t");
      push(work);
    }
    printf("\n");
  }
  printf("----------\n");
  i=0;
  while(!empty()){
    printbunsu(pop());
    printf((++i%9)?"\t":"\n");
  }
  return 0;
}

実行例

1/1	1/2	1/3	1/4	1/5	1/6	1/7	1/8	1/9	
2/1	1/1	2/3	1/2	2/5	1/3	2/7	1/4	2/9	
3/1	3/2	1/1	3/4	3/5	1/2	3/7	3/8	1/3	
4/1	2/1	4/3	1/1	4/5	2/3	4/7	1/2	4/9	
5/1	5/2	5/3	5/4	1/1	5/6	5/7	5/8	5/9	
6/1	3/1	2/1	3/2	6/5	1/1	6/7	3/4	2/3	
7/1	7/2	7/3	7/4	7/5	7/6	1/1	7/8	7/9	
8/1	4/1	8/3	2/1	8/5	4/3	8/7	1/1	8/9	
9/1	9/2	3/1	9/4	9/5	3/2	9/7	9/8	1/1	
----------
1/1	9/8	9/7	3/2	9/5	9/4	3/1	9/2	9/1
8/9	1/1	8/7	4/3	8/5	2/1	8/3	4/1	8/1
7/9	7/8	1/1	7/6	7/5	7/4	7/3	7/2	7/1
2/3	3/4	6/7	1/1	6/5	3/2	2/1	3/1	6/1
5/9	5/8	5/7	5/6	1/1	5/4	5/3	5/2	5/1
4/9	1/2	4/7	2/3	4/5	1/1	4/3	2/1	4/1
1/3	3/8	3/7	1/2	3/5	3/4	1/1	3/2	3/1
2/9	1/4	2/7	1/3	2/5	1/2	2/3	1/1	2/1
1/9	1/8	1/7	1/6	1/5	1/4	1/3	1/2	1/1

課題2-4

演算子を最後に置く数式の記法を逆ポーランド記法と言う。 例えば、 ((1 * 2) + 3) は ((1 2 *) 3 +) などと書く。 なお、演算子を最後に置いた式では、最初から読んでいくと、演算子を置い た場所でカッコが閉じられるので、そこで必ず計算を実施してよい。 つまり、これはカッコを全て省略できる。 上記の式は 1 * 2 + 3 = 1 2 * 3 + と書ける(但し、真ん中に演算子を置 く記法と異なり、数と数とを切り離すための空白などの区切り記号が必要)。 一方、 1*(2+3) = 1 2 3 + * と書ける。

この逆ポーランド記法の式を計算するのは、スタックを使うと容易にできる。 これは次の様にすれば良い

  1. もし、数を読んだらスタックに数を積む
  2. もし、演算子を読んだら、スタックから二つ値を取り出し、その演算子 に対応した計算を行い、スタックに結果を積む
  3. 式が終了したら、スタックから値を一つ取り出し、式の値として返 す

さて、文字列のポインター変数のポインターから記述されている分数を BUNSU として取り出し、ポインターを進める BUNSU toBunsu(char** p), 空白を読み飛ばす関数 voidint skipSpace(char** p), 演算子記号かどうかを判定する int isEnzanshi(char c) を下記の通りとす るとき、分数、足し算記号、掛け算記号だけからなる文字列中の逆ポーラン ド記法の式から値を求める関数 BUNSU calc(char *p) を作成しなさい。 そして、以下のテストプログラムと結合して、テスト結果を示しなさい。


#include <stdio.h>
#include <ctype.h>
#include "bunsu.h"

BUNSU toBunsu(char** p){
  BUNSU result={0,1};
  int sign=1;
  
  if(**p=='-'){
    sign*=-1;
    (*p)++;
  }
  while((isdigit(**p)))
    result.bunshi=result.bunshi*10+ *((*p)++) -'0';
  if(**p=='/'){
    result.bunbo=0;
    (*p)++;
    if(**p=='-'){
      sign*=-1;
      (*p)++;
    }
    while((isdigit(**p)))
      result.bunbo=result.bunbo*10+ *((*p)++) -'0';
  }
  result.bunshi*=sign;
  return result;
}
int skipSpace(char **p){
  while(**p==' ')(*p)++;
  return **p!='\0';
}
int isEnzanshi(char c){
  return (c=='+')||(c=='*');
}

テストプログラム


#include <stdio.h>
#include <stdlib.h>
#include "bunsu.h"
int main(void){
  char* input[]={"1/2 1/3 + 3 *",
		 "-1/3 5/4 2/7 * +",
		 "-2/3 -4/5 -6/7 * *",
		 "3/4 5/6 + 7/8 +",
		 NULL};
  char** p;
  for(p=input; *p!=NULL; p++){
    printf("%s = ",*p);
    printbunsu(calc(*p));
    printf("\n");
  }
  return 0;
}

実行例

1/2 1/3 + 3 * = 5/2
-1/3 5/4 2/7 * + = 1/42
-2/3 -4/5 -6/7 * * = -16/35
3/4 5/6 + 7/8 + = 59/24

おまけ toBunsu と skipSpace のテストプログラム


#include <stdio.h>
#include "bunsu.h"

int main(void){
  char input[] = "1/-3 -4/5   -2/-5   -6 5/6";
  char *p;
  p=input;
  while(skipSpace(&p)){
    printbunsu(toBunsu(&p));
    printf("\n");
  }
  return 0;
}

bunsu.h


typedef struct {
  int bunshi;
  int bunbo;
} BUNSU;
typedef struct stack {
  BUNSU bunsu;
  struct stack* next;
}STACK;
double bkazu(BUNSU b);
int bsign(BUNSU b);
BUNSU babs(BUNSU b);
void printbunsu(BUNSU b);
void yakubun(BUNSU* b);
BUNSU add(BUNSU a, BUNSU b);
BUNSU mul(BUNSU a, BUNSU b);
int empty(void);
void push(BUNSU b);
BUNSU pop(void);
BUNSU toBunsu(char** p);
int skipSpace(char **p);
int isEnzanshi(char c);
BUNSU calc(char* p);

レポート作成上の注意

レポートには自ら作成した関数の説明をし、テストプログラムと結合して実行 した結果を載せること。


課題1

レポート締切日: 2016年6月15日(水)20:00
提出先: 2 号館 3F レポートボックス

解答法として、分割コンパイルによる別ファイルにプログラムを書く方 法と、下記のテストプログラムに関数を追記する方法の二通りがあるがどちら でも良い。 プログラムの解説は、自分で作成した関数について行うこと。 こちらで提供したテストプログラムの説明は基本的には不要である。 但し、自分で作成した関数を説明するのに必要な内容には言及すること。

なお、遅れレポートは教員に直接提出してください。 遅れた方が有利にならない範囲内で採点します。

問題

分数を取り扱う構造体として次を用意した。


typedef struct {
  int bunshi;
  int bunbo;
} BUNSU;

なお、bunsu.h は後述する。

課題1-1

BUNSU 型を受け取り、分数の値を double 型として返す関数 double bkazu(BUNSU) を 作りなさい。

そして、テストプログラム1と結合して正しく動作していることを確認しなさい。

テストプログラム1


#include <stdio.h>
#include "bunsu.h"
int main(void){
  BUNSU b[] ={{3,4},{-5,3},{1,-2},{-2,-3},{0,-1},{0,1},{0,0}};
  BUNSU *p;
  for(p=b;p->bunbo!=0;p++){
    printf("%f\n",bkazu(*p));
  }
  return 0;
}
出力例1
0.750000
-1.666667
-0.500000
0.666667
-0.000000
0.000000

課題 1-2

BUNSU 型を受け取り、分数の符号が負ならば -1, 0 ならば 0, 正ならば 1 を 返す関数 int bsign(BUNSU) を作りなさい。

そして、下記のテストプログラム2と結合して、正しく動作することを確かめなさい。

テストプログラム2


#include <stdio.h>
#include "bunsu.h"
int main(void){
  BUNSU b[] ={{3,4},{-5,3},{1,-2},{-2,-3},{0,-1},{0,1},{0,0}};
  BUNSU *p;
  for(p=b;p->bunbo!=0;p++){
    printf("%d\n",bsign(*p));
  }
  return 0;
}
出力例2
1
-1
-1
1
0
0

課題 1-3

BUNSU 型を受け取り、分子、分母とも正にした絶対値を返す関数 BUNSU babs(BUNSU) を作りなさい。 なお、stdlib.h に定義されている int 型の絶対値を返す関数 int abs(int) を使用してよい。

そして、下記のテストプログラム3と結合して、正しく動作することを確かめなさい。

テストプログラム3


#include <stdio.h>
#include "bunsu.h"
int main(void){
  BUNSU a;
  BUNSU b[] ={{3,4},{-5,3},{1,-2},{-2,-3},{0,-1},{0,1},{0,0}};
  BUNSU *p;
  for(p=b;p->bunbo!=0;p++){
    a= babs(*p);
    printf("%d %d\n",a.bunshi, a.bunbo);
  }
  return 0;
}
出力例3
3 4
5 3
1 2
2 3
0 1
0 1

課題 1-4

BUNSU 型を受け取り、値が 0 ならば 0、そうでなければ、負の場合はマイナ ス記号"-"を先頭につけ、分子/分母 という形式で出力する 関数 void printbunsu(BUNSU) を作りなさい。 なお、課題1-1 から 1-3 で定義した関数を呼び出してよい。

そして、下記のテストプログラム4と結合して、正しく動作することを確かめなさい。

テストプログラム4


#include <stdio.h>
#include "bunsu.h"
int main(void){
  BUNSU b[] ={{3,4},{-5,3},{1,-2},{-2,-3},{0,-1},{0,1},{0,0}};
  BUNSU *p;
  for(p=b;p->bunbo!=0;p++){
    printbunsu(*p);
    printf("\n");
  }
  return 0;
}
出力例4
3/4
-5/3
-1/2
2/3
0
0

bunsu.hについて

bunsu.h は次のように上記の構造体と作るべき関数のすべてのプロトタイプ 宣言が入っているファイルとする。


typedef struct {
  int bunshi;
  int bunbo;
} BUNSU;
double bkazu(BUNSU b);
int bsign(BUNSU b);
BUNSU babs(BUNSU b);
void printbunsu(BUNSU b);

レポート作成上の注意

  1. レポートはグラフの表現など特殊な事情がない限り、必ず白黒で作成する こと。
  2. プログラミングのレポートでは必ずプログラムの説明をすること。その時 に、一行一行を日本語に直訳するのではなく、プログラムを機能毎に区分し、 機能の実現方法を説明すること。 プログラムに一行ずつコメントを入れてもプログラムの説明とは見なしません。
  3. 「問題を解きなさい」という問に対して「解きました。合ってました」は 正解ではありません。 「プログラムを作りなさい」という問題については、作成の手順の説明をする こと。 「テストしなさい」という問題についてはテストの手法、合否判定の基準、結 果の検討などを説明しなさい。
  4. プログラムが短くて説明がしづらい場合は、ポインタなどの関係を図示す るなどして工夫してください。
  5. 出力例は個々に異なりますので、手計算であらかじめ正しい値の求め方を 示し、値を提示してテストの合否を判定しなさい。
  6. レポートは手書きでも良いですが、プログラムの実行結果だけは必ずコン ピュータの出力の印刷にして下さい。
  7. 考察は必ず書いて下さい。ネタがない人は以下を参考にして下さい。

坂本直志 <[email protected]>
東京電機大学工学部情報通信工学科