スポンサードリンク
こんにちは、ももやまです。
C言語の練習問題をまとめてみたので何問か練習してみましょう。
練習2と練習3が1問5点、それ以外が1問10点で全部で150点満点となっています。
目次
スポンサードリンク
1.プログラムの動きの確認
練習1
つぎのC言語で書かれたプログラムがある。Thank you for using my brog
と出力される変数 a
の入力値をすべて答えなさい。
ただし、入力値が1つも存在しない場合は「存在しない」、無限に出力される場合は無限に出力されることを明記しなさい。
例えば、5以上の整数が無限個出力される場合は「5以上の整数が無限個出力される」と明記すること。
#include<stdio.h>
int main() {
int a,b,c;
printf("a = ? ");
scanf("%d",&a); // aの値入力
b = a + 1;
c = b + 2;
if(a > 6 || a <= -3) {
printf("Hey Siri!!\n");
}
else {
if(-1 < a && a <= 4) {
printf("OK Google\n");
}
else {
printf("Thank you for using My Brog!!\n");
}
}
return 0;
}
練習2
つぎのプログラムの実行結果を答えなさい(余白などは考えなくてよい)。
#include<stdio.h>
int main() {
int i,s;
s = 0;
i = 1;
while(i < 11) {
i += 2;
s += i;
}
printf("s = %3d\n",s);
return 0;
}
練習3
つぎのプログラムの実行結果を答えなさい(余白などは考えなくてよい)。
#include<stdio.h>
int main() {
int i,s;
s = 0;
i = 1;
while(i < 4 || s < 30) {
s += i * i;
i += 1;
}
printf("i = %3d\n",i);
printf("s = %3d\n",s);
return 0;
}
スポンサードリンク
2.配列とfor文
どの問題でも共通:長さがNのint型の配列を入力とする。
配列の要素数は1以上と仮定してよい (N > 1)。
練習4
配列中の3でも5でも割り切れない数の平均を返す関数 ave3_5
を作成しなさい。
ただし、3でも5でも割り切れない数が配列中に少なくとも1つは含まれているとしてよい。
例:配列のデータが3,6,4,8,10,7の場合
→3でも5でも割り切れない数は4,8,7
→6.33…が返される。
練習5
配列中の平均値よりも大きい数の中で最小となる数を返す関数 ave_min
を作成しなさい。
例:配列内のデータが 1,5,8,2,4,6 の場合、平均が4.33…なので4.33より大きい中で一番最小の値は5、よって5を返す。
条件
- 配列中のデータの数の範囲は1以上1,000以下
- 必ず1つは平均よりも大きい数があるとしてOK
- 最小値は必ず整数!!
練習6
配列中の偶数の要素の最大値を奇数の要素の最小値で割った値を返す関数 mmratio
を作成しなさい。
例:配列内のデータが 2,7,5,6,9 の場合、偶数の要素が2,6(最大値は6)、奇数の要素が7,5,9(最小値は5)なので、6 ÷ 5 = 1.2 が返される。
条件
- データの値の範囲は1以上1,000以下
- それぞれ1つ以上偶数の要素、奇数の要素はあると仮定してOK
練習7
うさぎ塾で解析の試験を行うこととする。全員の試験の採点結果を0点~100点で配列中に格納する。
この試験で、平均点の2/3以上の点数を取れた人を合格とするとき、不合格の人数を返す関数 numfail
を作成しなさい。
練習8
先程の解析の試験の結果の中央値を求めたい。
中央値とは数(点数)のデータを大きさの順に並べてちょうど中央に来る値のことである。
(例:23,45,66,88,90の場合の中央値は66である。)
中央値を求める関数 find_med
を作成しなさい。なお、受験者の人数は奇数であると仮定してよい。
練習9
さらに解析の試験の結果の標準偏差を求めたい。
標準偏差 \( s \) とは、平均点 \( \bar{x} \) を用いて以下の式で求められるような値のことである。\[ s^{2} = \sum_{i=0}^{n-1} \left( x_{i} - \bar{x} \right)^{2} \\ s = \sqrt{s^{2}} \]このとき、 受験者の標準偏差を求める関数 find_stdevp
を作成しなさい。ただし、受験者は1人以上いると仮定してよい。
練習10
double型の配列中のそれぞれの要素の差の絶対値の最小を求める関数 mindif
を求めなさい。
(例えば5,7だと差の絶対値は2、9,3だと差の絶対値は6となる。配列の要素が 1,5,3,10,6,9,15 だとすると、差が一番小さいのは 10,9 の1となる。)
必要ならばdouble型の値の絶対値を求める関数 fabs()
を用いてもよい。
例えば、 fabs(a)
の場合はa
の絶対値を得ることができる。
スポンサードリンク
5.複数の配列を用いた処理
練習11
N人の学生の離散数学・線形代数の得点(0点〜100点で採点)をそれぞれint型の配列 risan
, senkei
で表すとする。
このとき、離散数学で最高点の半分以上を得た学生の中での線形代数の最低点を返す関数 min_alb
を作成しなさい。
(例: (risan,senkei) = (90,45), (50,60), (70,55), (40,20) とすると離散数学の最高得点は90、半分は45 なので離散数学が45点以上の中の線形代数の最小値、つまり45が返される。)
練習12
N人の学生のうさぎ塾内で行われる線形代数と解析の得点(0点〜100点で採点)をそれぞれint型の配列 senkei
, kaiseki
で表すとする。
このとき、線形代数の得点の上位2/3に入る人の中の解析の平均点を求める関数 avs_sk
を作成しなさい。
練習13
\( xy \) 上平面上の原点 \( (0,0) \) と2点 \( (x_{i},y_{i}) \), \( (x_{j},y_{j}) \) の三角形の面積 \( S \) は、\[ S = \left| \frac{1}{2} \left| \begin{array}{cc} x_{i} & x_{j} \\ y_{i} & x_{j} \end{array} \right| \right| = \frac{1}{2} \left| x_{i} y_{j} - x_{j} y_{i} \right| \] で求められる。
\( xy \) 平面上のN点(N > 2 を仮定してよい)の座標値をdouble型の配列x,y で表し、i番目をそれぞれ x[i]
, y[i]
とするとき、原点と任意のN点から異なる2つを組み合わせてできる三角形の面積の平均を求めるプログラムを作成しなさい。
6.2次元配列と行列
C言語では、行列の中に行列を入れる2次元行列を使うことができます。
練習14
M行N列行列をint型の2次元配列 a
として実装する(それぞれの成分は整数)。
行列のそれぞれの行ごとの最大の要素の和を返す関数 matrix_max_sum
を作成しなさい。
ただし要素の範囲は-1,000〜1,000と仮定してよい。
例
(M,N) = (4,4) とし(つまり4行4列行列)、次の行列を入力とする。
\[ \left( \begin{array}{cc} 2 & 4 & 6 & 9 \\ 1 & 3 & 2 & -1 \\ 1 & 1 & 5 & 6 \\ 3 & 1 & 4 & 5 \end{array} \right) \]
この行列の0行目の最大値は9、1行目は3、2行目は6、3行目は5なので最大値の合計は23となり、23が返される。
練習15
M行N列行列をint型の2次元配列 a
として実装する(それぞれの成分は整数)。
行列のそれぞれの行の中で、0列が最大である行の数を返す関数 count_max_col0
を作りなさい。
例
(M,N) = (4,5) とし(つまり4行5列行列)、次の行列を入力とする。
\[ \left( \begin{array}{cc} 3 & 1 & 5 & 9 & 4 \\ -2 & 5 & -3 & 0 & 4 \\ 10 & 9 & 3 & -1 & 7 \\ 2 & -2 & -1 & 0 & 1 \end{array} \right) \]
この行列の0列が最大なのは2行目(0列目は10)と3行目(0列目は2)なので、2が返される。
練習16
M行N列行列をint型の2次元配列 a
として実装する(それぞれの成分は整数)。
行列それぞれの行のすべての成分の和がすべて0以上なら1を、1つ以上の行の和が負の行があれば0を返す関数 isAll_row_pos
を作成しなさい。
例1
(M,N) = (3,4) とし(つまり3行4列行列)、次の行列を入力とする。
\[ \left( \begin{array}{cc} 1 & -3 & 3 & 4 \\ -3 & 0 & 1 & 2 \\ 1 & 9 & 1 & 9 \end{array} \right) \]
0行目の和は5、1行目の和は0、2行目の和は20と、すべての行の和は0以上なので1を返す。
例2
同じく(M,N) = (3,4) とし(つまり3行4列行列)、次の行列を入力とする。
\[ \left( \begin{array}{cc} 0 & 2 & -1 & -3 \\ 3 & 3 & -4 & 0 \\ 2 & -5 & 2 & 5 \end{array} \right) \]
0行目の和は-2、1行目の和は2、2行目の和は4と、0行目の和が-2と負のため、0を返す。
7.解答
解答1
b = a + 1
, c = b + 2
の部分は全くのダミーであることに注意してください[1]この部分が b = c + 1
, a = b + 2
になったら出力はどう変わるかとかを考えてみると面白いかもしれません。。
最初のelseを通るようなaの値は -2,-1,0,1,2,3,4,5,6 となる。
その中でつぎのif文で真となって OK Google
を出力してしまうaは0,1,2,3,4である。
これらを除くと -2,-1,5,6 が答えとなる。
採点基準(10点満点)
値が1個抜けてるにつれて3点減点、5個以上書いた人はオーバーしたもの1個につき2点減点
解答2
プログラムの動きをよく考えましょう。
while文を1回抜けるごとに2つの変数がどうなるかを示してみます。
条件の判定は、while文の中に入る前(ここで条件を満たしていなければ1回も実行されない)、カッコの中身を一通り実行し終わった段階でそれぞれ実行され、条件を満たしている限りはずっと実行されます。
i = 3, s = 3 i = 5, s = 8 i = 7, s = 15 i = 9, s = 24 i = 11, s = 35 // カッコに入る前に条件を満たしているので実行される!!
となり、出力は s = 35
となる( s =
を忘れないように!!)
採点基準 (5点満点)
s = 忘れ:-2点 余計な出力(小数とか) -2点
解答3
while文を1回抜けるごとに2つの変数がどうなるかを示してみます。
i = 2, s = 1 i = 3, s = 5 i = 4, s = 14 i = 5, s = 30
となり、出力は
i = 5 s = 30
となる( s =
や i =
を忘れないように!!)
採点基準 (5点満点)
iの値に2点、sの値に3点
s = や i = 忘れ:各-1点 余計な出力:-1点
ここからは関数を作る、つまり実際にプログラムする問題となる。解説をする前に配列の引数について説明しておこう。
配列を引数にするときは、次の3つの書き方をすることができる。
配列の引数の書き方
int f1(int a[]) // 1つ目:配列aをa[] と書く書き方、一番一般的 int f1(int a[N]) // 2つ目:配列aの要素数を明示した書き方 int f1(int *a) // 3つ目:ポインタとして見る書き方。配列とポインタは同じなので。
解答4
3でも5でも割り切れない数
→3で割ったあまりが0でない and 5で割ったあまりが0でないに落とし込むのがポイント。
条件式の書き方
a == b // aがbと等しい a != b // aがbと等しくない a < b // aはbよりも小さい(bを含まない) a <= b // aはb以下(bを含む) a > b // aはbよりも大きい(bを含まない) a >= b // aはb以上(bを含む)
複数の条件 A,B があるときの書き方
A && B // A and B A || B // A or B
整数型同士の割り算をすると結果が整数になり、正しいあまりが出ないため、double型にするために1.0を掛けるかキャストをしてあげる必要がある。
1. 0に初期化した変数を2つ(合計数える用sum・データ加算した回数count)
2. 条件があればfor文の中にif文で平均の対象を設定(Ex. 3の倍数の平均点)
3. それぞれのデータの値をsumに加算、加算するたびにcount変数を1増やす
4. sum ÷ count で平均が出せる。sumがint型の場合は sum * 1.0 / count もしくは (double) sum / count で整数型同士の除算を避けること。
double ave3_5(int a[]){
int count = 0,sum = 0;
for(int i = 0; i < N; i += 1) {
if(a[i] % 3 != 0 && a[i] % 5 != 0) {
sum += a[i];
count += 1;
}
}
return sum * 1.0 / count; // int ÷ int 型の割り算はだめ!!
// OK: return (double)sum / count;
}
解答5
流れとしては
- 平均を求める
- 平均値より大きい中での最小値を出す
の2ステップである。
1. 初期値は最大値の場合は想定される最小の値-1、最小値の場合は想定される最大の値+1にすること。
2. 最大値の場合は、現在値の最大値より大きい値が来たらその値を最大値に、最小値の場合は現在値の最小値よりも小さい値がきたらその値を最小値にする。
注意:最大値・最小値の更新条件には等号はあってもなくてもOK。しかし、最大値・最小値そのものではなく、最大値・最小値の配列の番号(添字)を返す場合は注意が必要。
該当するデータが複数あるとき、最初のデータ(より番号が小さい添字)を返すのであれば等号は不要、最後のデータ(より番号が大きい添字)を返す場合は等号が必要。
if文、for文の中身が1行のときはカッコ { } を省略することができるのですが、僕はカッコは絶対に省略しないことをおすすめします。
理由としては、中身の行にかかわらずカッコを書く癖をつける(1行のときの省略を覚えるとカッコを忘れることがある)のと、もともとif文、for文の中身を1行から複数行に変えるときにバグの原因となるからです。
int ave_max(int a[]) {
int count = 0, sum = 0,min = 0; // 初期化もお忘れなく
double average; // 平均はdouble
// まずは平均を求める
for(int i = 0; i < N; i += 1) {
sum += a[i];
count += 1; // count使わずにNで代用可
}
average = sum * 1.0 / count; // キャスト忘れずに
// OK: average = (double) sum / N;
// OK: average = (double) sum / count;
// つぎに最小値
for(int i = 0; i < N; i += 1) {
if(a[i] < min && a[i] > average) { // 最小値よりも小さいデータが来たら更新
min = a[i]; // 1行でも必ずカッコでくくること!!
}
}
// averageより大きいか(if文)の中にさらにa[i]はmin以下かのif文を書くのもOK
return min;
}
解答6
奇数は2で割った余りが1、偶数は2で割ったあまりが0というのをすぐに連想できるようにしましょう。
double mmratio(int a[N]) {
int evenMax = -1,oddMin = 10001;
double ans;
for(int i = 0; i < N; i += 1) {
if (a[i] % 2 == 0 && evenMax < a[i]) {
evenMax = a[i];
}
else if (a[i] % 2 == 1 && oddMin > a[i]) {
oddMin = a[i];
}
}
ans = evenMax * 1.0 / oddMin;
// OK: ans = (double)evenMax / oddMin;
return ans;
}
解答7
流れとしては、
- 平均点を求める
- 平均の2/3未満(平均の2/3以上ではない生徒)の人数をカウントする
となる。
int numfail(int a[]) {
int sum = 0, countave = 0, under23 = 0;
double ave23;
for(int i = 0; i < N; i += 1) {
sum += a[i];
countave += 1;
}
ave23 = sum * 2.0 / countave / 3;
// OK: ave23 = sum * 2.0 / 3 / N;
for(int i = 0; i < N; i += 1) {
if(a[i] < ave23) { // OK: if(! a[i] >= ave23)
under23 += 1;
}
}
return under23;
}
ソートを使った問題。
tmpを使った簡単なソートは何も見ずにプログラムを書けるようにしましょう。
解答8
int型の除算は切り捨てられるのがポイント。
例えば
N = 5の場合:0,1,2,3,4 中央値は2→ int 5 / 2 = 2
N = 7の場合:0,1,2,3,4,5,6 中央値は3→ int 7 / 2 = 3
と少し試せば、ソート後の配列の要素を2で除算すれば中央値になることがわかるだろう。
int find_med(int a[]) {
int tmp;
for(int i = 0; i < N - 1; i += 1) {
for(int j = i + 1; j < N; j += 1) {
// ソート部分(昇順)
if(a[i] > a[j]) { // OK: if(x[i] < x[j])
tmp = a[i]; // まず片方をtmpに入れて
a[i] = a[j]; // 入れた方にデータをコピー
a[j] = tmp; // もう1方のデータはtmp
}
}
}
return a[N / 2]; // ソート後は(データ数÷2)番目が中央値
}
おまけ
もしNが偶数の場合は……?
(Nが偶数のときの中央値は真ん中2つの平均)
例えばN=4:0,1,2,3 → 中央値は1と2の平均
例えばN=6:0,1,2,3,4,5 → 中央値は2と3の平均
と試せば (a[N/2-1] + a[N/2]) / 2.0
とすれば中央値が求められるだろう。なお、この場合は入力データがint型でもdouble型にする必要があるのに注意。
解答9
複雑な数式であっても落ち着いて少しずつ求めていきましょう。
今回の場合は、
- 平均を求める
- 分散の1/nの部分以外を求める
- nで割る
- ルートを取る
で求められます。
double find_stdevp(int a[]) {
int count = 0,sum = 0;
double ave,var = 0;
for(int i = 0; i < N; i += 1) {
sum += a[i];
count += 1;
}
ave = sum * 1.0 / count;
for(int i = 0; i < N; i += 1) {
var += (a[i] - ave) * (a[i] - ave);
// OK: (count_ave - a[i]) * (count_ave - a[i]);
// OK: pow(a[i] - count_ave,2);
}
var /= count; // 分散 OK: var = var / count;
return sqrt(var);
}
解答10
1つの配列の要素を、重複なく2つ選んでいく処理が必要です。
double mindif(double a[]) {
double dest,min = 10001;
for(int i = 0; i < N - 1; i += 1) {
for(int j = i + 1; j < N; j += 1) {
dest = fabs(a[i] - a[j]);
if(dest < min) {
min = dest;
}
}
}
return min;
}
別解
ソートをすると、自分より1個前(もしくは後)を見るだけで値がどれだけ離れているかを確認できる。(2,2,3,4,4,5みたいにソートされてるので1つ前と後以外はみなくてよい。)
なので、ソート後の長さチェックのfor文は1重でよい。
double mindif(double a[]) {
double dest,min = 10001,tmp;
for(int i = 0; i < N - 1; i += 1) {
for(int j = i + 1; j < N; j += 1) {
// ソート部分(昇順)
if(a[i] > a[j]) { // OK: if(x[i] < x[j])
tmp = a[i]; // まず片方をtmpに入れて
a[i] = a[j]; // 入れた方にデータをコピー
a[j] = tmp; // もう1方のデータはtmp
}
}
}
for(int i = 0; i < N - 1; i += 1) {
dest = a[i] - a[i+1];
if(dest < min) {
min = dest;
}
}
return min;
}
解答11
最小値の条件となる最高点を最初のfor文で調べる。
次に最小値を調べるステップを書くだけ。
離散数学の点が最高点の半分以上 → 離散数学の点を2倍にしたものが最高点以上 に書き換えることでキャストなど複雑なことを考えずに済む
int min_alb(int risan[],int senkei[]) {
int max_risan = -1, min = 101;
for(int i = 0; i < N; i += 1) {
if (max_risan < risan[i]) {
max_risan = risan[i];
}
}
for(int i = 0; i < N; i += 1) {
// OK: risan[i] * 2 >= max_risan && senkei[i] < min
if (risan[i] >= max_risan / 2.0 && min > senkei[i]) {
min = senkei[i];
}
}
return min;
}
解答12
上位2/3 → 降順ソートして配列の要素の前半2/3を調べる(もしくは下位1/3と見て昇順ソートして配列の前半1/3を調べる)に落とし込めたかがポイント
ポイントは、2つの点数を同時にソート出来たかどうか。
また、for文の条件を書くときに N * (2/3) と書くと、2/3 でint ÷ intの割り算が発生し、N * 0の計算が行われfor文が一切回らないことに要注意。
double avsmt(int senkei[],int kaiseki[]) {
int sum = 0,count = 0,tmp;
for(int i = 0; i < N; i += 1) {
for(int j = i + 1; j < N; j += 1) {
if(senkei[i] < senkei[j]) {
tmp = senkei[i];
senkei[i] = senkei[j];
senkei[j] = tmp;
tmp = kaiseki[i];
kaiseki[i] = kaiseki[j];
kaiseki[j] = tmp;
}
}
}
for(int i = 0; i < N * 2 / 3; i += 1) { // NG: i < N * (2/3)
sum += kaiseki[i];
count += 1;
}
return sum * 1.0 / count;
}
解答13
練習9 + 練習10 のパターンに2つの配列が出てきたもの。
double ave_triangle(double x[],double y[]) {
double sum = 0,area;
int count = 0;
for(int i = 0; i < N - 1; i += 1) {
for(int j = i + 1; j < N; j += 1) {
area = fabs(x[i] * y[j] - x[j] - y[i]) / 2; // fabsはdouble型なのでキャスト不要
sum += area;
count += 1;
}
}
return sum / count; // sumはもともとdoubleなのでキャスト不要
}
解答14
2次元配列を引数に書く際に、int a[][]
と書くことができないことに注意。
int matrix_max_sum(int a[M][N]) {
int max,sum = 0;
for(int i = 0; i < M; i += 1) {
max = -1001;
for(int j = 0; j < N; j += 1) {
if(max < a[i][j]) {
max = a[i][j];
}
}
sum += max;
}
return sum;
}
初期化するmaxを決めない方法もあり。
int matrix_max_sum(int a[M][N]) {
int max,sum = 0;
for(int i = 0; i < M; i += 1) {
max = a[i][0];
for(int j = 1; j < N; j += 1) {
if(max < a[i][j]) {
max = a[i][j];
}
}
sum += max;
}
return sum;
}
解答15
真偽判定の書き方は大切。
1つでも該当するものがあれば真の場合
初期値0(偽)の真偽判定用の変数を用意、1つでも真のものがあれば変数を1(真)に書き換える。
1つでも該当するものがあれば偽の場合
初期値1(真)の真偽判定用の変数を用意、1つでも偽のものがあれば変数を0(偽)に書き換える。
また、練習14と同じく、どこのfor文の前後にどの処理を書くかも大切。
if文で真偽を判定する場合、 == 1
や == 0
はなくてもOK。
例えば if(a)
と書いた場合、aが0以外なら真となりif文の中身が実行され、aが0なら偽となり、if文の中身が実行されない。
int count_max_col0(int a[M][N]) {
int col0,isMax,count = 0;
for(int i = 0; i < M; i += 1) {
col0 = a[i][0];
isMax = 1;
for(int j = 1; j < N; j += 1) {
if(col0 < a[i][j]) {
isMax = 0;
}
}
if(isMax) { // == 1 はなくてもOK
count += 1;
}
}
return count;
}
また、こんな書き方をすることもできるので紹介をしておく。
int count_max_col0(int a[M][N]) {
int col0,isMax,count = 0;
for(int i = 0; i < M; i += 1) {
col0 = a[i][0];
isMax = 1;
for(int j = 1; j < N; j += 1) {
if(col0 < a[i][j]) {
isMax = 0;
}
}
count += isMax; // 真なら1足され、偽ならそのまま
}
return count;
}
練習16
真偽を判定する変数を1つ用意(スタートは1)。
それぞれの行ごとの合計を計算(計算前に初期化!)し、和を計算し終わったタイミングで0以上か確認し、1つでも0のものがあれば真にはなりえないので変数を0に書き換える。
int isAll_row_pos(int a[M][N]) {
int sum, isZero = 1;
for(int i = 0; i < M; i += 1) {
sum = 0; // 合計の初期化のタイミングはここ!
for(int j = 0; j < N; j += 1) {
sum += a[i][j];
}
if(sum < 0) {
isZero = 0; // 1つでも負があれば偽(0)
}
}
return isZero;
}
別解
関数内にreturnは2つ以上あってもOK。
むしろこちらのプログラムのほうが実行時間は早くなるので、こちらのプログラムのほうがおすすめである。
int isAll_row_pos(int a[M][N]) {
int sum;
for(int i = 0; i < M; i += 1) {
sum = 0;
for(int j = 0; j < N; j += 1) {
sum += a[i][j];
}
if(sum < 0) {
return 0; // 1つでも0未満なら0確定→強制終了
}
}
return 1; // 全部正なら真(1)
}
8.さいごに
今回は、プログラミング(C言語)の基礎知識の確認と、配列とfor文を用いた関数作成の練習問題をまとめてみました。
プログラミングがあまり得意じゃないよ、という人は本やネットなどを見ていいので他の人に聞かずにプログラムが思いつくようになればいいかなと思います。逆に得意だぜ、という人はいかにすばやく関数を作れるか、もしくはいかに効率よい(無駄がない)関数を作れるかなどを意識するのがいいでしょう。
注釈
↑1 | この部分が b = c + 1 , a = b + 2 になったら出力はどう変わるかとかを考えてみると面白いかもしれません。 |
---|
関連広告・スポンサードリンク