- ベストアンサー
C言語での並び替えのプログラム
- C言語で配列の並び替えを行うプログラムを作成したいです。
- 配列の要素を比較して組み合わせた文字列の数を求め、その総和が最小となる組み合わせ方を探したいです。
- 配列の要素数が最大で40個まで考慮した並び替えプログラムを作成する方法を知りたいです。
- みんなの回答 (9)
- 専門家の回答
質問者が選んだベストアンサー
>次に、文字列同士の比較で、(たぶん)無駄が多くて >スピードダウンすることになり、もし3000万個求める >ならば4分30秒掛かることとなりました。 >doubleを使った変数に、求めた組み分け数字を格納して >いくことで、スピードアップを図ることはできる >でしょうか? うーん、浮動小数は遅いのでお勧めしませんね。 自分が作るんだったら、MAXITEM*PAIRの初期化済み 配列を準備して、1度求めた文字列の長さを入れておくとかかな。 そうしたら、配列の添え字だけでアクセスできますし。
その他の回答 (8)
- taka_tetsu
- ベストアンサー率65% (1020/1553)
>if(MAXITEM - x * 4 >= 4){ >//(3)1つ前の組が存在するとき、その1つめの数字より 大きくないといけない >if( x > 0 && pair[x - 1][0] >= first) { >continue; >} >} > >と、することで解決しました。 4つずつ選んだ残りの2つも再帰で求めるんですか? うーん、最初からそういうつくりになっていないんで。 ShowResultの中で残った2つという風にすればいいという 気でいたもんで。 なんで、私が動かしたときは、 #define MAXPAIR ((int)(MAXITEM / PAIR)) にして、 MAXPAIRは2として動かしていたので問題なく 動作していました。 #というか、組み合わせた文字列の長さを求めるんだから、あまった分はいらないと思いました(^^;; >比較回数が630通りで、組み分けの理論解だと >315通りになるはずなのですが、その倍になってしまったので >この場合何故そうなるのかわからなかったため、 >8をいれることにしました。 うーん、630のとき、重複結果出ていませんか? 4*2+1にして、MAXPAIRは2にしてみてください。 >あと、比較なんですが、総個数28個の場合、4個ずつ組み分けの >理論解は1.31896*10^16通りあり、私のパソコンでは5分間で >3千万通りしか比較することができず、結果がでるのに >約4000年かかることになります。ですから現実的には >不可能ではないかと思いました。 前に書いたような(^^;;; このロジック、わかりやすくするために無駄だらけって。 いくらでもスピードアップ、つまりチューニングできます。 特に、結果表示にprintfなんて使ってたら 遅さまんてんです(^^;; 枝刈りだって不十分だし。 特に、余りが出ないときなんて無駄な比較が多いこと(^^;; あと、総個数を増やせば増やすほど、このロジックでは 使用済みの数字のチェックに時間がかかります。 うまく考えればこのチェックはループ不要で 毎回1回のみの比較で出来ますし。 他にも微々たるものですが、符号無し整数を使うと アセンブラレベルで命令が1つ減るとか。 また、当初の目的である、文字列の長さを求めるのだって 毎回求めるのではなく、一度求めた値を変数に 入れておくとか。
お礼
返信ありがとうございます。 なるほど~。勉強になります。 printfは遅いので、何千万個に一回だけ表示させるようにはプログラムしました。それで、5分で、3000万個の比較となってしまったんです。 使用済みの数字のチェックを毎回一回のifにすることで、少しプログラムが速くなりました。あと、あまりの数字についてはShowResultに入れました。4個の数字を選ぶからx組目の2個目の数字は(総個数-2)になるし、x組目の3個目の数字は(総個数-1)になるので、それも試してみました。また、x組目の1個目の数字はx-1個目の数字より大きい数からfirstが始まるように改良しました。すると、組み分けの番号だけ出すならすごく速くなりました。(3000万個の組み分け数字を求める計算は20秒くらいになりました) 次に、文字列同士の比較で、(たぶん)無駄が多くてスピードダウンすることになり、もし3000万個求めるならば4分30秒掛かることとなりました。doubleを使った変数に、求めた組み分け数字を格納していくことで、スピードアップを図ることはできるでしょうか?
- taka_tetsu
- ベストアンサー率65% (1020/1553)
>[3,4,5,6,7,8,9,10,1,2] ? でましたよ。hamlabi13さんのソースそのままで。 ただし、0~9の10個の数字なので、 [2,3,4,5,6,7,8,9,0,1] となりますけど。 ちなみに、 >(2)x組目はx*2以下しか使用不可 が、x*4+2以下になるはずですが、いきなり10と書いてありますね。 まぁ、無駄にループ回るだけなんで、実害はないですが。 >これだけの比較回数だと比較しきることは出来ないですかね? >教えていただけないでしょうか? 普通はそんなことないですよ。 ただ、再帰を使っているのであまり階層が深くなりすぎるとスタックオーバーフローになる可能性があります。 100や200じゃ平気ですよ。
お礼
返信ありがとうございます。 また、書き間違っていて申し訳ないです。 [2,3,4,5,6,7,8,9,0,1] ですね。 わからなかった箇所ですが //(3)1つ前の組が存在するとき、その1つめの数字より大きくないといけない if( x > 0 && pair[x - 1][0] >= first) { continue; } の、部分を if(MAXITEM - x * 4 >= 4){ //(3)1つ前の組が存在するとき、その1つめの数字より大きくないといけない if( x > 0 && pair[x - 1][0] >= first) { continue; } } と、することで解決しました。 また、総個数が10個で、4個ずつ組み分けしていくと2個の組が出てくる場合はtaka_tetsuがおっしゃったとおり4*2+2でいけましたが、総個数が9個で4個ずつ組み分けした場合1個あまりが出ます。このときは4*2+1でいけるのかな?っと思ったのですが比較回数が630通りで、組み分けの理論解だと315通りになるはずなのですが、その倍になってしまったのでこの場合何故そうなるのかわからなかったため、8をいれることにしました。 あと、比較なんですが、総個数28個の場合、4個ずつ組み分けの理論解は1.31896*10^16通りあり、私のパソコンでは5分間で3千万通りしか比較することができず、結果がでるのに約4000年かかることになります。ですから現実的には不可能ではないかと思いました。 私の用いたPCはCPU:アスロン2400で、ソフトはCPad for borland C++ Compilerです。
- taka_tetsu
- ベストアンサー率65% (1020/1553)
なお、これらのサンプルなんですが、わかりやすく書いたので、ありえない値のチェックに無駄が多いです。 普通は、最初からありえない条件については省いておきます。そうすることで、ロジックが簡単になる場合が多いです(複雑になるときもありますが)。 また、処理も高速になります。 今回の場合、実は先頭の組の1つめの数字は0しかありえません。 また、先頭の組の2つめの数字は0を除いたすべての数字が入る可能性があります。 これらを踏まえると、再帰ではない方では、ループの数が減ったりとかなりロジックがすっきりするはずです。 このような問題でロジックを考える場合、基本的にしらみつぶしで全パターンを求めれば抜けはないというのが一般的です。 で、しらみつぶしに一番有効なのが再帰になります。 ただし、メモリを食う、処理が遅いなどの欠点はあります。 そして、特殊な条件をつけて重複を省いたり、ありえない条件を最初から考えない(枝切りとも呼ばれます)といった肉付けをしていくことで、本当に必要な結果だけに絞っていきます。 こんな感じですかね? ただし、条件をみつけるのは自分で考える必要があります。数学的な条件になるのでプログラムとは関係ありませんし。
お礼
返信ありがとうございます。気づかずに前の作ったプログラムを21個まで繰り返していました。 前は総個数N個の中から2個ずつ取り出していましたが、次は4個ずつ取り出すプログラムを作っています。 taka_tetsuさんのプログラムを参考に、4個ずつ取り出す場合を考えています。 で、例えば総個数が10個の場合、4個ずつ取り出すと、2個余ります。このとき、 [3,4,5,6,7,8,9,10,1,2] という組合せ方ができなくて困っています。 自分で作ってみたプログラムではNo.5の方に書いてみました。 下のプログラムとmain関数とShowResult関数は文字数が足りなくて省略しました。 また、もし総個数が28個の場合、組み分けの仕方の数は、1.31896*10^16個あります。 これだけの比較回数だと比較しきることは出来ないですかね? 教えていただけないでしょうか? //----------------- int IsUse( int pair[MAXPAIR][PAIR], int num ) { int i; for( i = 0; i < MAXPAIR; i++ ) { if( pair[i][0] == num || pair[i][1] == num || pair[i][2] == num || pair[i][3] == num ) { return( 1 ); } } return( 0 ); }
- taka_tetsu
- ベストアンサー率65% (1020/1553)
で、こっちが再帰のサンプルになります。 どちらがすっきりしてると思います? #大事なことを忘れました。 1つめの数字を決定する条件で、 「1つ前の組が存在するとき、その1つめの数字より大きくないといけない」という条件を抜かしてしまいました。 これを入れないと重複結果が出てきてしまいます。 私のサンプルには入れておいたので参考にしてください。 ShowResultとIsUseは前のサンプルと同一です。 //----------------------------------------- #include <stdio.h> #define MAXITEM 6 #define MAXPAIR (MAXITEM / 2) void GetFirst( int x, int pair[MAXPAIR][2] ); void GetSecond( int x, int pair[MAXPAIR][2] ); void ShowResult( int pair[MAXPAIR][2] ) { //とりあえず、printfで結果表示だけしときます printf( "%d, %d, %d, %d, %d, %d\n", pair[0][0], pair[0][1], pair[1][0], pair[1][1], pair[2][0], pair[2][1]); return; } int IsUse( int pair[MAXPAIR][2], int num ) { int i; for( i = 0; i < MAXPAIR; i++ ) { if( pair[i][0] == num || pair[i][1] == num ) { return( 1 ); } } return( 0 ); } //2. x組目の1つめの数字を選択する。 void GetFirst( int x, int pair[MAXPAIR][2] ) { int first; //(2)x組目はx*2以下しか使用不可 for( first = 0; first <= x * 2; first++ ) { //(1)すでに使用済みの数字は不可 if( 0 != IsUse(pair, first) ) { continue; } //(3)1つ前の組が存在するとき、その1つめの数字より大きくないといけない if( x > 0 && pair[x - 1][0] >= first ) { continue; } pair[x][0] = first; GetSecond( x, pair ); } pair[x][0] = -1; return; } //3. x組目の1つめの数字が決まったら、2つめの数字を選択する。 void GetSecond( int x, int pair[MAXPAIR][2] ) { int second; //(2)1つめの数字より大きくないといけない for( second = pair[x][0] + 1; second < MAXITEM; second++ ) { //(1)すでに使用済みの数字は不可 if( 0 != IsUse(pair, second) ) { continue; } pair[x][1] = second; if( x + 1 >= MAXPAIR ) { //最終組だったら目的の値を取得する ShowResult( pair ); } else { GetFirst( x + 1, pair ); } } pair[x][1] = -1; return; } int main() { int x; int pair[MAXPAIR][2]; for( x = 0; x < MAXPAIR; x++ ) { //結果領域の初期化 pair[x][0] = -1; pair[x][1] = -1; } GetFirst( 0, pair ); return( 0 ); }
お礼
//2. x組目の1つめの数字を選択する。 void GetFirst( int x, int pair[MAXPAIR][PAIR] ) { int first; //(2)x組目はx*2以下しか使用不可 for( first = 0; first <= 10; first++ ) { //(1)すでに使用済みの数字は不可 if( 0 != IsUse(pair, first) ) { continue; } //(3)1つ前の組が存在するとき、その1つめの数字より大きくないといけない if( x > 0 && pair[x - 1][0] >= first ) { continue; } pair[x][0] = first; GetSecond( x, pair ); } pair[x][0] = -1; return; } //3. x組目の1つめの数字が決まったら、2つめの数字を選択する。 void GetSecond( int x, int pair[MAXPAIR][PAIR] ) { int second; //(2)1つめの数字より大きくないといけない for( second = pair[x][0] + 1; second < MAXITEM; second++ ) { //(1)すでに使用済みの数字は不可 if( 0 != IsUse(pair, second) ) { continue; } pair[x][1] = second; GetThird( x, pair ); } pair[x][1] = -1; return; } //4. x組目の1つめの数字が決まったら、3つめの数字を選択する。 void GetThird( int x, int pair[MAXPAIR][PAIR] ) { int third; //(2)1つめの数字より大きくないといけない for( third = pair[x][1] + 1; third < MAXITEM; third++ ) { //(1)すでに使用済みの数字は不可 if( 0 != IsUse(pair, third) ) { continue; } pair[x][2] = third; GetFourth( x, pair ); } pair[x][2] = -1; return; } //5. x組目の1つめの数字が決まったら、4つめの数字を選択する。 void GetFourth( int x, int pair[MAXPAIR][PAIR] ) { int fourth; //(2)1つめの数字より大きくないといけない for( fourth = pair[x][2] + 1; fourth < MAXITEM; fourth++ ) { //(1)すでに使用済みの数字は不可 if( 0 != IsUse(pair, fourth) ) { continue; } pair[x][3] = fourth; if( x + 1 >= MAXPAIR ) { //最終組だったら目的の値を取得する ShowResult( pair ); } else{ GetFirst( x + 1, pair ); } } pair[x][3] = -1; return; }
- taka_tetsu
- ベストアンサー率65% (1020/1553)
>これを頑張って40個まで条件付けて行くという方法ですね。 >プログラムは長くなりますが、確かにできますね。 40回はやめましょうよ、やっぱり(^^;; 「N回繰り返す」って考えてみてくださいね。 本当は再帰ではないサンプルは面倒くさいので 書くつもりなかったんですが、 hamlabi13さんががんばったので。 #ShowResult()の中だけは、6個の数字用になってます。 //--------------------------------- #include <stdio.h> #define MAXITEM 6 #define MAXPAIR (MAXITEM / 2) void ShowResult( int pair[MAXPAIR][2] ) { //とりあえず、printfで結果表示だけしときます printf( "%d, %d, %d, %d, %d, %d\n", pair[0][0], pair[0][1], pair[1][0], pair[1][1], pair[2][0], pair[2][1]); return; } int IsUse( int pair[MAXPAIR][2], int num ) { int i; for( i = 0; i < MAXPAIR; i++ ) { if( pair[i][0] == num || pair[i][1] == num ) { return( 1 ); } } return( 0 ); } int main() { int x; int first; int second; int check; int pair[MAXPAIR][2]; for( x = 0; x < MAXPAIR; x++ ) { //結果領域の初期化 pair[x][0] = -1; pair[x][1] = -1; } //1. MAXPAIR組の組み合わせを求める x = 0; check = 1; while( x >= 0 ) { //2. x組目の1つめの数字を選択する。 if( check == 1 ) { for( first = pair[x][0] + 1; first < MAXITEM; first++ ) { //(1)すでに使用済みの数字は不可 if( 0 != IsUse(pair, first) ) { continue; } //(2)x組目はx*2以下しか使用不可 if( first > x * 2 ) { continue; } //(3)1つ前の組が存在するとき、その1つめの数字より大きくないといけない if( x > 0 && pair[x - 1][0] >= first ) { continue; } //使われていなかったら格納し、ループを抜け、2つ目を求める pair[x][0] = first; check = 2; break; } //値が格納されなかったら前の組を求める if( pair[x][0] != first ) { pair[x][0] = -1; pair[x][1] = -1; check = 2; x--; continue; } } //3. x組目の1つめの数字が決まったら、2つめの数字を選択する。 if( check == 2 ) { for( second = pair[x][1] + 1; second < MAXITEM; second++ ) { //(1)すでに使用済みの数字は不可 if( 0 != IsUse(pair, second) ) { continue; } //(2)1つめの数字より大きくないといけない if( pair[x][0] >= second ) { continue; } //使われていなかったら格納し、ループを抜ける pair[x][1] = second; break; } //値が格納されたら次の組を求める if( pair[x][1] == second ) { check = 1; x++; //最終組だったら目的の値を取得する if( x >= MAXPAIR ) { ShowResult( pair ); x--; pair[x][1] = -1; } } else { //格納されなかったら1つめの数字を求めなおす pair[x][1] = -1; check = 1; } } } return( 0 ); }
- taka_tetsu
- ベストアンサー率65% (1020/1553)
>で、全然OKなのですが、そのロジックの方法がまったく思いつきません。 >ヒントを頂きましたが、具体的にはどんな文を使ってどんなロジックを書けばよいのかわかりません ヒントをそのままロジックにしてみましょう。 #これは再帰ではありません 1. 3組の組み合わせを求める(3回ループすればいい) 2. x組目の1つめの数字をすべて選択する。(xは0、1、2) 条件は、 (1)すでに使用済みの数字は不可 (2)x組目はx*2以下しか使用不可 3. x組目の1つめの数字が決まったら、2つめの数字をすべて選択する。 (1)すでに使用済みの数字は不可 (2)1つめの数字より大きくないといけない 1.、2.、3.および条件を上の言葉どおりにロジックにしてみてください。 これなら解りますよね?
お礼
こんな感じになりました。 これを頑張って40個まで条件付けて行くという方法ですね。 プログラムは長くなりますが、確かにできますね。 #include<stdio.h> main(){ int i,j,k,n,x,y,z,kioku,count; int a[6]={1,2,3,4,5,6}; int b[6]; int c[15][6]; count=0; n=6; //配列の並び替え(組み分け) for(i = 1 ; i < n ;i++){ b[0] = a[0];/*1組目の1個目*/ b[1] = a[i];/*1組目の2個目*/ for(x = 1; x < 3 ;x++){ if(b[1] != a[x]){ b[2] = a[x];/*2組目の1個目*/ for(j = 1 ; j < n ;j++){ if(b[1] != a[j] && b[2] != a[j] && b[2] < a[j]){ b[3] = a[j];/*2組目の2個目*/ for(y = 2 ; y < 5 ; y++){ if(b[1] != a[y] && b[2] != a[y] && b[3] != a[y]){ b[4] = a[y];/*3組目の1個目*/ for(z = 3; z < n ; z++){ if(b[1] != a[z] && b[2] != a[z] && b[3] != a[z] && b[4] != a[z] && b[4] < a[z]){ b[5] = a[z];/*3組目の2個目*/ for(kioku = 0 ; kioku < n ; kioku++){ c[count][kioku] = b[kioku]; printf("%d ", c[count][kioku]); } printf("\n"); count++; } } } } } } } } } }
- taka_tetsu
- ベストアンサー率65% (1020/1553)
やっぱりソートは不要。 15パターンの組み合わせを求めるだけで十分です。 必要なロジックはソートではなく、6つの数字から、 任意の2つの文字列を3組取り出したときの組み合わせを すべて求めるということです。 これが求まったら、あとは求めた添え字で該当の文字列にアクセスするだけ。 #hamlabi13さんのプログラムでは、[0]、[1]と直接書いてありますが。 というのを求めるロジックをがんばって作れば、40個だってちゃんと対応できます。 ヒントとしては、 1.x組目の1つ目を選ぶ際は、(x-1)*2以下にする 例:3組目の1つめの数字は(3-1)*2=4なので、5はNG #C言語なんで、0から数えはじめれば、実はx*2になります。 2.組となる要素を選ぶ際は、必ず1つ目の要素より大きな数字を選ぶ。 例:5を選んだら4はNG ですかね。 参考までに、8つのときは 01、23、45、67 01、23、46、57 01、23、47、56 01、24、35、67 01、24、36、57 01、24、37、56 ・ ・ ・ みたいな数字の組を求めるようなロジックを組んであげれば、OKでしょう。 #個人的には、これなら再帰処理でロジックを組みます。 再帰じゃなくても組めますけど。
お礼
回答ありがとうございます。 そうですね。並び替えのプログラムじゃなくてもかまいませんね。 おっしゃったように >必要なロジックはソートではなく、6つの数字から、 >任意の2つの文字列を3組取り出したときの組み合わせを >すべて求めるということです。 で、全然OKなのですが、そのロジックの方法がまったく思いつきません。 ヒントを頂きましたが、具体的にはどんな文を使ってどんなロジックを書けばよいのかわかりません。 もし、よろしければ、もう少し詳しく、そのロジックの作り方について説明していただけないでしょうか? まず、再起処理のロジックについて考えてみます。
- taka_tetsu
- ベストアンサー率65% (1020/1553)
>(a).A[1][5]の文字列とA[2][5]の文字列を比較して、 意味不明 A[1][5]ってどこが文字列なのですか? C言語ですよね。2つめの文字列の\0を指しているとしか 読めないんですが。 >"abceih"という組み合わせた文字列を作り、その文字列の数を出します(この場合6個)。 なんとなくわからないでもないですが、いきなり”作り”と書かれても、作り方が書いてないと普通はわかりません。 で、本当に”文字列の数”なんですか?どう見ても作成した文字列の文字数なんですが。 正しく書いて頂かないと回答つかないですよ。 で、根本的なところなのですが、 >この配列の並びを前から配列を2つずつ取り出して比較すれば全組み合わせが完了したことになります。 わざわざソートを行う必要は? a[0]とa[1]、a[0]とa[2]、・・・a[0]とa[5]、 a[1]とa[2]、a[1]とa[3]、・・・a[1]とa[5]、 a[2]とa[3]、a[2]とa[4]、a[2]とa[5]、 a[3]とa[4]、a[3]とa[5]、 a[4]とa[5] と比較を行えばいいだけでは?
お礼
申し訳ございません。 1)A[1][5]ではないですね。A[1]です。 2)文字列の数ではなく、文字数の間違いです。 文字列の比較は下のように文字の組み合わせプログラムです。 3)わざわざ並び替えるのは 1.(A[0]比較A[1])+(A[2]比較A[3])+(A[4]比較A[5])=総和 2.(A[0]比較A[1])+(A[2]比較A[4])+(A[3]比較A[5])=総和 3.(A[0]比較A[1])+(A[2]比較A[5])+(A[3]比較A[5])=総和 4.(A[0]比較A[2])+(A[1]比較A[3])+(A[4]比較A[5])=総和 ・・・ と続き、その総和の一番少ない組み合わせを、探すプログラムを組むために並び替えが必要となるわけです。 文字列の比較のプログラムは #include<stdio.h> int main(){ char ctemp[26] = {"abcdefghijklmnopqrstuvwxyz"}; char itemp[26] = {"iiiiiiiiiiiiiiiiiiiiiiiiii"}; //同じ文字が存在するとき iをeに書き換える事とする char a[][4]={{"abcd"},{"abex"}}; int i,j,count1; count1 = 0; for( i = 0 ; i<4 ; i++ ){ for( j = 0 ; j<26 ; j++){ if(a[0][i] == ctemp[j]){ itemp[j] = 'e'; } } for( j = 0 ; j<26 ; j++){ if(a[1][i] == ctemp[j]){ itemp[j] = 'e'; } } } for(i=0 ; i<26 ; i++){ if(itemp[i] == 'e'){ printf("%c ",ctemp[i]); itemp[i] = 'i'; count1++; } } printf(" %d", count1); } となります。
お礼
遅くなりましたが、長い間付き合って頂いてありがとうございました。 ある程度、作成することができました。 まだ、完璧には作ることはできませんでしたが、 ほぼ満足いく結果が得られました。