応用プログラミング
高瀬 裕(たかせ ゆたか)[email protected] http://iui.ci.seikei.ac.jp/~takase/
応用プログラミング<6> 第十三回 2014/12/17
授業日程・内容回数 日程 内容 テキスト第1回 9/24 基本データ型, 制御構造 1章第2回 10/1 一次配列,string クラス 2章 3章第3回 10/8 関数定義とスコープ (Vector型) 4章(5章)第4回 10/15 ファイル操作 6章第5回 10/22 テキスト入力処理 7章第6回 10/29 ポインタの基本 8章第7回 11/5 配列とポインタ 9章第8回 11/12 中間試験第9回 11/19 多次元配列 10章第10回 11/26 構造体 11章第11回 12/3 関数宣言, 関数名の多重定義, デフォルト引数, 関数テンプレート 12章第12回 12/10 再帰呼び出し 13章第13回 12/17 アルゴリズム 14章第14回 1/14 配列を用いた間接参照 15章第15回 1/21 期末試験
前回の復習
再帰呼び出し
再帰呼び出しとは関数の中で自身を呼ぶことです
関数が自分自身を呼び出すこと
再帰呼び出し(recursive)
void func(){
~~ 処理 ~~
func();
~~ 処理 ~~ };
func() 関数が その処理の中で func() 関数(自身)を呼び出す
適切に使うと記述が簡単になります
無限ループ単純に考えると無限ループしますね
int recursive(){
cout << “出力” << endl; recursive();
return 0; };
int main(){
recursive(); }
自分自身を呼び出す。 呼び出し続けて終わらない。 (実際には関数の呼び出しでメモリを 消費していくのでメモリを使い果たして 異常終了するはずです。)
上手に止めてあげれば 役に立ちます
再帰呼び出しは止め方を最初に考えます
階乗の計算(p117)再帰呼び出し
int rec_fact(int n){
if( n == 0 ) return 1; return n * rec_fact(n-1);
};
n=0 で ストップ
n* rec_fect(n-1)main関数
int main (){ rec_fact(5);
}
実行結果 120
確かに結果は正しいです。 どのような処理手順か 想像つきますか?
再帰呼び出しの処理順序一度最後まで辿り、戻ってくるn * rec_fact(4)
return 5 * 24
n*rec_fact(3)
return 4 * 6
n*rec_fact(2)
return 3 * 2
n*rec_fact(1)
return 2 * 1
n*rec_fact(0)
rec_fact(5)
rec_fact(4)
rec_fact(3)
rec_fact(2)
rec_fact(1)
n = 0 なので再帰終了!! return 1
rec_fact(0)
return 1return 1 * 1
return 2 * 1return 3 * 2
return 4 * 6return 5 * 24
start!!
中間地点
goal
次々に処理の途中で呼び出す
終了条件に合致したら 順番に処理が終了していく
再帰呼び出しは自己ループではないwhile文のようなループとは異なります
while(true) cout << “出力” << endl; };
int recursive(){
cout << “出力” << endl; recursive();
return 0; };
whileループは この文の先頭に戻ります
再帰呼び出しは 自分で自分自身(と 全く同じ関数)呼び出す呼び出した関数が終われば 自分も終わります。
次々に関数を呼んでいき、最後に呼ばれたものから終了していく。(メモリが必要な理由がここにあります。)
再帰呼び出しと局所変数(p119)新たな関数が呼び出され、最後から順に終了していきます
void recur( int n){ int x = n*n;
cout << n << “:” << x << “(before)” << endl;
if( n < 0 ) return; // これが終了条件
recur(n -1);
cout << n << “:” << x << “(after)” << endl; }
int main(){recur(3); return 0;}
3:9(before) 2:4(before) 1:1(before) 0:0(before) -1:1(before) 0:0(after) 1:1(after) 2:4(after) 3:9(after)
after の出力は再帰呼び出しが最後まで到達してから始まる
再帰呼び出しと局所変数呼び出しの順番
n =3; x= 9; before(3)文の出力
after(3)文の出力
n =2;x= 4; before(2)文の出力
after(2)文の出力
n =1;x= 2; before(1)文の出力
after(1)文の出力
n =0;x= 0; before(0)文の出力
after(0)文の出力
n =-1;x= 1; before(-1)文の出力 ここで再帰終了!!
次々に引数や局所変数の 異なる関数が呼び出される
nが負だと after文は 呼ばれない
start!!
中間地点
Goal
最初の n や x が保持されていますが 処理の途中で関数を呼んでるので当然です
r e c u r (3)
r e c u r (2)
r e c u r (2)
r e c u r (1)
recur(-1)
入力と逆順の出力(p120)複数の文字列を入力し、逆から出力
void recur_print( ){ string s; if( cin >> s){ // これが終了条件 recur_print();
} cout << s << endl;
}
int main(){ recur_print();
return 0; }
if( cin >> s)入力が成功する : true 失敗(EOF等) : false終了条件として成立します
cout がいつ実行されるか もうわかりますよね
次々に関数を呼んで入力を要求し続け、 入力が失敗したら最後から出力されるはずです
練習問題出力を書いて下さい
実験の復習です。 出力を書いてみましょう
rec(6)
rec(4)
rec(2)
rec(0)return;
cout << 2;
rec(0)return;
return;
・ ・ ・ ・
最初の方は→
rec(6)
rec(4)
rec(2)
rec(0)return; (rec0)
cout << 2;
return; (rec2)
cout << 4;
rec(0)return; (rec0)
rec(2)
rec(0)return; (rec0)
cout << 2;
return; (rec2)
rec(0)return; (rec0)
return; (rec4)
cout << 6;
rec(4)
rec(2)
rec(0)return; (rec0)
cout << 2;
return; (rec2)
cout << 4;
rec(0)return; (rec0)
rec(2)
rec(0)return; (rec0)
cout << 2;
return; (rec2)
rec(0)return; (rec0)
return; (rec4)
return;(rec6)
アルゴリズム
アルゴリズムアルゴリズムって何だ
ある処理を行うための手続きや手段,手順のこと
バブルソートのアルゴリズムってのをやりました?
今日は有名なアルゴリズムをお伝えします。 (手順を覚えておけばプログラムにできますよ)
探索法配列から目的のものを手早く探す
A B C D E F G H I J例えば、上記配列から G のカードの位置を求めたいなら
逐次探索法
2分探索法
端から 1 枚ずつ見ていきます。
もう少し効率的に見ていきます。
逐次探索法アルゴリズム端から見ていく
A B C D E F G H I J★ ★ ★ ★ ★ ★ ★ ★ ★ ★
裏返したとして
これをプログラムできればいいわけです。
0
A B C D E F G H I J★ ★ ★ ★ ★ ★ ★ ★ ★1A B C D E F G H I J★ ★ ★ ★ ★ ★ ★ ★2A B C D E F G H I J★ ★ ★ ★ ★ ★ ★3A B C D E F G H I J★ ★ ★ ★ ★ ★4
A B C D E F G H I J★ ★ ★ ★ ★5A B C D E F G H I J★ ★ ★ ★6A B C D E F G H I J★ ★ ★7
ここで終了
逐次探索法の実装端から見ていく
int main(){ const int size = 10000; int a[size] = {}; for(int i =0; i < size; i++){ a[i] = i;
}
const int x = 5000;
for(int i =0; i < size; i++){ if( a[i] == x) break;
}
cout << i << endl; return 0; }
10個や20個ではすぐ終わるので 10000個の配列を作ります
5000を見つけてストップする
処理時間の計測時間を計測できると便利です。
#include<ctime>時間計測のためにインクルード
clock_t start = clock(); 計測したい処理
clock_t end = clock(); cout << (double)(end-start)/CLOCKS_PER_SEC<<endl;
計測したい処理に掛かった時間が表示できる。
#include<iostream> #include<ctime>
using namespace std;
int main(){ const int size = 10000; int a[size] = {}; for(int i =0; i < size; i++){ a[i] = i;
}
const int x = 5000; clock_t start = clock(); for(int i =0; i < size; i++){ if( a[i] == x) break;
} clock_t end = clock(); cout << i << endl; cout << (double)(start - end)/CLOCLS_PRE_SEC << endl; return 0; }
2分探索法アルゴリズム楽になる気がしますよ
アルファベット順に並んでいる仮定 :
特定のカードがどこにいるかわからないが 少なくとも並び順が決まっているなら
2分探索アルゴリズムで手早く済ませられます
C G I K L O P S U W X★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★アルファベット順に並んだカードから P を探してみます
2分探索法アルゴリズム探索領域を半分にしていく
1:中央のカードをめくる
C G I K L O P S U W X★ ★ ★ ★ ★ ★ ★ ★ ★ ★
2:条件判定
C G I K L O P S U W X★ ★ ★ ★ ★ ★ ★ ★ ★ ★
P > O なのでこのカードより左側には存在しない。 見る必要のないカード
めくる = Pかどうか調べる探索開始位置 探索終了位置
中央
3:残りの中央をめくる
C G I K L O P S U W X★ ★ ★ ★ ★ ★ ★ ★ ★
2分探索法アルゴリズム続き探索領域を半分にしていく
探索終了区間
4:条件判定
C G I K L O P S U W X★ ★ ★ ★ ★ ★ ★ ★ ★探索終了区間
P < U なのでUより右には存在しない 見る必要なし
探索開始位置 探索終了位置
中央
探索開始位置が移動 = 中央も変化
2分探索法アルゴリズム続き探索領域を半分にしていく
5:のこりの中央をめくる
C G I K L O P S U W X★ ★ ★ ★ ★ ★ ★ ★探索終了区間 探索終了区間
6:条件判定
C G I K L O P S U W X★ ★ ★ ★ ★ ★ ★ ★探索終了区間 探索終了区間
P == P なので残りを見ないで終了!
探索開始 位置
探索終了 位置
中央
探索終了位置が移動 = 中央も変化
2分探索法アルゴリズムの実装アルゴリズムからコードへ
中央のカードをめくる 条件判定を行う 探索領域が半分に減る(開始位置 or 終了位置が変化)
の繰り返しでした。
比較的単純なループ処理で実装できそうです。
当然ですが、英文字とカードという例でなくても 並び順が分かっている配列であれば このアルゴリズムで探索可能です。
2分探索法アルゴリズムの実装アルゴリズムからコードへ
int main(){
const int size = 11; const char a [size] = {‘C’, ‘G’, ‘I’, ‘K’, ‘L’, ‘O’, ‘P’, ’S’, ‘U’, ‘W’, ‘X’};
char x = ‘P’; // 探したい文字
cout << x << “は配列の中に”; if( binarySearch(a, size, x)) cout << “ あります”; else cout << “ありません”;
cout << endl;
binarySearch() 関数には 配列(の先頭要素のアドレス),要素数,探索文字を渡し bool で戻ってくることがわかる
bool binarySearch(const char a[], int sz, char x){
bool found = false;
int first = 0; // 探索開始位置 int last = sz - 1; // 探索終了位置
while( first <= last ) { int middle = (first + last) / 2; // 中央の位置
if(a[middle] == x){ found = true; break;
}
if(a[middle] < x) first = middle + 1; // 現在の中央より右側にある
else // 現在の中央より左側にある last = middle - 1;
}
return found; }
結果によって探索範囲が狭まる。
見つけたら終了
小数点は無視して問題ない
中央値より小: 探索開始位置が変化 中央値より大: 探索終了位置が変化
練習問題2分探索の実装
void が戻り値の 2分探索アルゴリズムを実装しましょう 見つけられなかった場合の処理もいれましょう。
ソート自分で並べる
2分探索法を行うためには 並び順が分かっている 必要 がありました。
自分である法則に従って並び変えるのがソートアルゴリズムです。
選択ソートバブルソート
他
今回紹介遅い 実装楽遅い 実装楽速い 実装難
バブルソート2つずつ比較するのを繰り返す
14 78 3
14 78 3
14 78 3
14 78 3
1
2
3
4
隣の相手と比較して順番を入れ替える1周目
14 78 3
14 78 3
1478 3
5
6
7
8
14 78 3
最小値確定2周目
1周目終了
1478 3
9 2周目終了
1478 3
1478 3
10
1478 3
11
ソート完了
3周目終了
4周目終了
比較が10回入れ替えは 場合による
1478 3 1周終了で1つ確定する3周目
12 1478 3
134周目
14
5周目1478 315
実際は 4周終われば ソート終了としてよい
周回が進むと 比較回数が減っていく
バブルソートの実装例えばこう
for(int i=0; i <size -1; i++){ // 最後のループは比較・交換の必要なし for(int j =0; j<size -1 -i; j++){ // ループが進むにつれて確定していくので比較対象が減る if(mn[j] < mn[j+1]) // 隣と比較
// これは入れ替え関数 swap(mn[j],mn[j+1]); } }
選択ソート
14 78 3
降順に並べ変えます
1. 先頭を最大と仮定して より大きい物を探す
14 78 3
3. 最大と先頭を入れ替える1 4 78 3
1 4 78 34. 最大は求まり,配列の先頭。 次から繰り返えせばよい
2. 大きいのを見つけたら それと残りを比較
最大(最小)を見つけて入れ替える
1478 3
1 4 78 3
1 4 78 3
1478 3
1478 3
1478 3 ソート完了
同じ位置に入れ替えた。
比較が10回入れ替えは最大4回
選択ソートの実装例えばこう
int j; for(int i=0; i <size -1; i++){ // 一番最後は比較・交換の必要なし int m = i; // ループの開始を最大と仮定 for(j =i+1; j<size; j++){ // 残りを比較していく if(mn[m] < mn[j]) //より大きければ m = j; // その後の基準を変更 } // 配列の最後まで最大のものを探し続ける
// 現在の先頭と最大値を入れ替え swap(mn[i],mn[m]); }
その他のソート例マージソート
その他のソート例クイックソート
標準アルゴリズムの利用当然あらかじめ用意されています
#include <algorithm>を使うと、予め用意されたアルゴリズムが利用可能
min();max();swap();
sort();関数テンプレートで紹介
binary_search();find();
今回紹介
これらを標準アルゴリズム(の一部)といいます。
標準アルゴリズムでの配列範囲指定配列の範囲は 先頭 から 末尾+1 まで
標準アルゴリズムの関数に配列型データを渡す場合 配列の範囲を指定する。(配列全部か一部か選べる)
例 : 配列のソート int a[size] size = 5a[0] a[1] a[2] a[3] a[4]
完全ソート sort(&a[0] , &a[size]);
部分ソート sort(&a[1] , &a[4]);
先頭要素のアドレスと末尾の次の要素のアドレスを渡す
345 12a[5]
存在しない a[5] のアドレスを渡す
a[1] から a[3] までのソート
sort() の例何でも昇順にソートする!
#include<iostream> #include<algorithm> using namespace std;
int main(){ const int size = 5; int a[size] = {5, 3, 2, 7, 2};
sort(&a[0], &a[size]);
for(int i = 0; i < size; i++){ cout << a[i] << ‘ ’;
cout << endl; return 0;
}
ヘッダファイルのインクルード
0 から 最後尾+1 = size
ソートしてから2分探索2分探索には独自の関数を使います。
#include<iostream> #include<algorithm> using namespace std; // binarySearch関数は次のページ int main(){ const int size = 11; char a[size] = {‘Z’, ‘G’, ‘K’, ‘Y’, ’T’, ‘P’, ’S’, ‘C’, ‘I’, ‘X’}; sort(&a[0], &a[size]);
char x; while( cin >> x){
if(binarySearch(a, size, x)) cout << x << is found. << endl; else cout << x << is not found. << endl;
} return 0;
}
ソート(先頭と最後の要素+1を渡す) 今回は char 型
先程作った2分探索関数を呼ぶ
bool binarySearch(const char a[], int sz, char x){
bool found = false;
int first = 0; // 探索開始位置 int last = sz - 1; // 探索終了位置
while( first <= last ) { int middle = (first + last) / 2; // 中央の位置
if(a[middle] == x){ found = true; break;
}
if(a[middle] < x) first = middle + 1; // 現在の中央より右側にある
else // 現在の中央より左側にある last = middle - 1;
}
return found; }
結果によって探索範囲が狭まる。
見つけたら終了
小数点は無視して問題ない
中央値より小: 探索開始位置が変化 中央値より大: 探索終了位置が変化
標準アルゴリズムによる2分探索binary_search()
binary_search(先頭アドレス, 要素数+1のアドレス, 探索対象);
与えられた配列内に探索対象があれば true を返す (bool が戻り値)
自分で関数を作成する必要が無くなる。 sort()と同様に組込型(int とか charとか)に全体対応
double型のような実数型で誤差が生じる場合は注意
#include<iostream> #include<algorithm> using namespace std; int main(){ const int size = 11; int a[size] = {‘Z’, ‘G’, ‘K’, ‘Y’, ’T’, ‘P’, ’S’, ‘C’, ‘I’, ‘X’}; sort(&a[0], &a[size]);
char x; while( cin >> x){
if(binary_search(&a[0],&a[size], x)) cout << x << is found. << endl; else cout << x << is not found. << endl;
} return 0;
}
binary_search()による2分探索これで完結
引数の渡し方に注意
標準アルゴリズムによる逐次探索find()
find(先頭アドレス, 要素数+1のアドレス, 探索対象);
与えられた配列内に探索対象があれば 対象要素のアドレスを返す
探索対象がなければ 第二引数のアドレスを返す
探索する配列の最後の要素 + 1 のアドレス
逐次探索させてみましょう
#include <iostream>#include <algorithm>
using namespace std; int main() {
const int size = 11; const char a[size] = {’Z’, ’G’, ’W’, ’K’, ’Y’, ’T’, ’P’, ’S’, ’C’, ’I’, ’X’}; char x; while (cin >> x) { if (find(&a[0], &a[size] , x) != &a[size]) {
cout << x << " is found in the array a[]." << endl;
}else{
cout << x << " is not found." << endl; }return 0;
}
逐次探索例find()の引数と戻り値に注意
第二引数が戻ってこないなら
速度を調べてみましょう
練習問題選択ソートとfind()
選択ソートを実装しましょう。 また、標準アルゴリズムを使ってみましょう
binary_search() と find()どっち使います?
探索という意味では binary_search() の方が速いです
しかし、一旦 sort() を呼ぶ必要があるなら 結果として find() を使って端から調べたほうがいい場合もあります。
あらかじめ並び順が決まっているなら binary_search() それ以外だとケースバイケースといった所でしょうか
実験の諸注意注目!
口頭試問を受けたら僕宛にメールで Source Codeを送って下さい。(実行結果は必要ないです) [email protected]
座席は前から 3, 4列目 で変わらず。 問1 問2 が終了したら一旦呼んで下さい。
ソースにコメントを入れてから呼ぶこと コメントがないと受け付けません。
実験問題の軽い説明
問3 の難関は構造体の作成でしょう
読み取った文字列 (string ) は 1文字目がカードのマーク (mark) 2文字目がカード番号(num)の10の位 3文字目がカード番号(numの1の位
構造体を作ってしまえば,2回ソートするうち どちらを先にやるか選べるようにしなさいという問題です。