• ベストアンサー

走査アルゴリズムについて

n個の要素の配列x[]について 配列xの連続した要素(部分配列)でその和が最大になるものを見つけて、 その和を出力するアルゴリズムについてです。 これなのですが解き方の考え方に 「x[0...n]までの部分配列の最大和は、x[0...n-1]の部分配列の最大和か、x[n]から左方向に伸びた配列の和のいずれかである。」 とあり プログラムには float maxSoFar(float x[], int length){ float maxsofar = 0; float maxendinghere = 0; for(int i = 0; i < length; i++){ maxendinghere = max(0.0f, maxendinghere + x[i]); maxsofar = max(maxsofar, maxendinghere); } return maxsofar; } とあるのですが、アルゴリズムの仕組みがよくわかりません。 なぜ上のような説明でこのようにプログラムできるのかが わからないので、どなたかうまい説明できる人お願いします><

質問者が選んだベストアンサー

  • ベストアンサー
noname#50176
noname#50176
回答No.3

<訂正> 誤:x[m]~x[n-1]までの連続した累積(0≦m≦n-1) 正:x[m]~x[p]までの間の連続した累積(0≦m<p , 0<p≦n-1) <追記> Q.累積値とは具体的に何であるか? A.単にある時点の加算の合計です <例> x[3]とした時、可能性として ・2つ以上の部分からなる、x[0]~[n-1]各部をつなげた累積 x[0] + x[2] = 合計和の最大値 ・x[m]~x[p]までの間の連続した累積(0≦m<p,0<p≦n-1) x[0] + x[1] = 合計和の最大値 x[1] + x[2] = 合計和の最大値 x[0] + x[1] + x[2] = 合計和の最大値 尚、累積値に負数を足した時は必ず最大値でなくなります。

takusoe
質問者

お礼

たくさんのご説明ありがとうございました! ちゃんと理解することができました! なかなか難しいアルゴリズムですよね><

その他の回答 (3)

  • bender
  • ベストアンサー率45% (108/236)
回答No.4

N個の要素を含む配列が与えられたとき、求めるような 『連続する』部分配列の最大和を探すためには、単純に 考えると、配列のs番目(0<=s<N)からはじまってe番目 (s<e<N)で終わるような部分配列すべてについて(つまり N*(N+1)/2の部分配列について)、その和をひとつずつ 調べてみればよいわけですが、その方法では N が大きい ときには結構な時間がかかります。 問題文にあるアルゴリズムのすごいところは、配列を たった一回読み通す間に(つまり、一重のforループ だけで)求める最大和が見つかるところだと思います。 > x[0...n]までの部分配列の最大和は、x[0...n-1]の > 部分配列の最大和か、x[n]から左方向に伸びた > 配列の和のいずれかである。 このヒントは、x[0...n]というn+1個の要素を含む配列が 与えられたとき、『連続する』部分配列のうちでその和が 最大となるようなものは、x[n]を含まない配列(すなわち x[0...n-1])の中にみつかるか、もしくはx[n]を含む 部分配列(すなわち「x[n]から左方向に伸びた配列」)で あるかのいずれかである、といっています。 このヒントがいうことは、ある意味当たり前かもしれません。 というのも、x[0...n]のどんな部分配列も、x[0...n-1]の 部分配列か、x[n]から左に伸びる配列のいずれかでしか あり得ないからです。とはいえ、このことから問題文に あるような賢いプログラムが導かれます。 ヒントにある「x[0...i-1]の部分配列の最大和」がmaxsofarと 対応していて、「x[i]から左方向に伸びた配列の和」は maxendinghere = max(0.0f, maxendinghere + x[i]) と対応 しているといえます。ただ、 maxendinghere = max(0.0f, maxendinghere + x[i]); については、おそらく、 maxendinghere = max(x[i], maxendinghere + x[i]); とした方がヒントに忠実であるように私は思います。

takusoe
質問者

お礼

ありがとうございます! すごいわかりやすかったです! このオーダーnはかなりビックリでした。

noname#50176
noname#50176
回答No.2

>あとおそらくちょっとこっちのミスだったと思うのですが いえ、私も説明があやふやでしたので…。 >勘違いでしょうか・・? 勘違いではありません。 「x[0...n]までの部分配列の最大和は、x[0...n-1]の部分配列の最大和」 は、2つ以上の部分からなる、x[0]~[n-1]各部をつなげた累積で 「x[n]から左方向に伸びた配列の和」 は、x[m]~x[n-1]までの連続した累積(0≦m≦n-1)ですね。 失礼しました。 簡単に言えばこのアルゴリズム、 先頭から数値を累積していき、1つ前の累積より小さくなったら 前回、大きくなったら最新、の累積を最新のものとします。 これって、単にプラスの数値だけを足していくと言うことなんですよね…。

noname#50176
noname#50176
回答No.1

まずこのアルゴリズムの内容は次の流れになります。 1.n個のデータを用意 2.配列のx[n]を確保 x[n]確保は、x[0]~x[n-1]が使えます、これはご存知ですね。 x[3]と確保すれば、x[0],x[1],x[2] の3つですから、x[個数]です。 3.処理開始(下記参照) <処理内容> トレースチャートで示しますね(部分結果図の事) float maxSoFar(float x[], int length){ float maxsofar = 0;累積値は最初0 float maxendinghere = 0;累積値は最初0 for(int i = 0; i < length; i++){ 0~n-1=n回繰り返す maxendinghere = max(0.0f, maxendinghere + x[i]); 0.0f(f=浮動小数記号)つまり0.0と現在累積値+現在値を比較、 これで大きい方をmaxendinghere(現在累積値)に代入 (負数の場合は現在の累積を0.0に繰り上げるということ) maxsofar = max(maxsofar, maxendinghere); (最新累積値と現在累積値を比較し大きい方をmaxsofar(最新累積値) に代入) } return maxsofar; (最新累積値を返す) } (例) ・x[5] 5個確保する x[0]=0.5 x[1]=0.2 x[2]=-0.6 x[3]=-0.9 x[4]=0.7 となっていた場合、 (cur=現在累積値 new=最新累積値 とします) cur=0,new=0 0.0とcur+x[0]を比較 (0.0<0.0+0.5)でcur=0.5 newとcurを比較 (0.0<0.5)でnew=0.5 0.0とx[1]を比較 (0.0<0.5+0.2)でcur=0.7 newとcurを比較 (0.5<0.7)でnew=0.7 0.0とx[2]を比較 (0.0<0.7-0.6)でcur=0.1 newとcurを比較 (0.7>0.1)でnew=0.7 0.0とx[3]を比較 (0.0>0.7-0.9)でcur=0.0 newとcurを比較 (0.7>0.0)でnew=0.7 0.0とx[4]を比較 (0.0<0.7+0.7)でcur=1.4 newとcurを比較 (0.7<1.4)でnew=1.4 よって1.4が最大値です。 「x[0...n]までの部分配列の最大和は、x[0...n-1]の部分配列の最大和」 とは配列すべての合計値(x[n]は存在しない) 「x[n]から左方向に伸びた配列の和のいずれかである。」 とはx[0]~x[n-1] 内で任意の所の数値を足した値と考えます。 おそらく合っていると思います。

takusoe
質問者

補足

すいませんたくさんの回答を><ありがとうございます! ただちょっと自分の聞き方でミスがあったようで プログラム自体の計算の流れはわかっていて具体的な数列で 確かめたりしたのですが なぜこのような計算方法でどんな理論でうまくいってるのか?が わからなかったんです;; 教えてくださった回答では ・累積値とは具体的に何であるか? と 最後の方に書いてある >x[0]~x[n-1] 内で任意の所の数値を足した値と考えます。 のところなどがちょっとわからないんです>< どんな理論でこのような計算が立っているのか・・? あとおそらくちょっとこっちのミスだったと思うのですが 「x[0...n]までの部分配列の最大和は、x[0...n-1]の部分配列の最大和」というのは、 x[0..n]でx[n]は存在しませんがニュアンスとしては 最後の項を除いた配列の部分和の最大という意味で 「x[n]から左方向に伸びた配列の和のいずれかである。」とは 最後の項から左に向かって最大部分和を探した時の値~ といった感じかなと思ってたのですが・・・ 勘違いでしょうか・・?

関連するQ&A

  • データ構造とアルゴリズムの問題です

    要素数がnである配列aの要素の最大値を求めるアルゴリズムのループ端によるフローチャートを完成せよ(前判定繰返し) max =a[0] i=1; while i<n do{ if(a[i]>max)max=a[i]; i++; } a[0] → max 1 → i 前判定繰返し □ | yes a[i]□max-----| |        □      NO i+1 → i 前判定繰返し □の中を埋めるんですが教えてください

  • String配列を扱うアルゴリズムについて

    よりパフォーマンスの良いアルゴリズムが、 ございましたらご教示下さい。 数レコード分のDBテーブルデータが格納されたString[][]型が存在するとします。 配列の要素は、String[行(フィールド)][列(カラム)]です。 ここで、全レコード中の列ごとの最大文字列長を int[]型に取得したいと思います。 そうした場合、自作した下記の処理よりも、 よいパフォーマンスを得られるアルゴリズムがございましたら、 ご教示願いたいと思います。 ※処理前提条件 ●String[][]型変数に、過不足無くテーブルデータが格納済みであるとします。 ●配列の第一(行)・第二(列)要素の最大値は取得済みであるとします。 ////////////// // 変数定義 // ////////////// String[][] tableData; ← テーブルデータ格納済み(過不足はありません) int 行数 = 全行数(取得済み); int 列数 = 全列数(取得済み); //列毎の最長文字列値を格納する。 int[] maxLen = new int[列数]; ////////// // 処理 // ////////// //列の個数分、処理を繰り返す for(int i = 0; i < 列数; i++) {   //行の個数分、処理を繰り返す   for(int j = 0; j < 行数; j++) {     //NULLを回避する     if(tableData[i][j] != null) {       //int配列に格納済みの数値より大きければ、改めて格納する       if(maxLen[i] < tableData[i][j].length()) {         maxLen[i] = tableData[i][j].length();       }     }   } } 以上です、どなかお知恵をお貸し頂けませんか。 宜しくお願い致します。

    • ベストアンサー
    • Java
  • アルゴリズムの名前を教えてください

    16ビットのデータが、たとえば1000個、配列で与えられているとします。 unsigned short data[1000]; このデータをしらべて、重複するものを除いて何種類の値があるかを数える場合、一番素朴な方法だと、次のようにやると思います。 int i, j; int count = 0; for(i = 0; i < 1000; i++) {  for(j = 0; j < i; j++) {   if(data[j] == data[i]) {    break;   }  }  if(i == j) {   count++;  } } この方法だと、2重のループがあって処理に時間がかかります。そこで、メモリに余裕があれば内側のループをやめて、 int i; int count = 0; int flag[0x10000]; int n; for(i = 0; i < 0x10000; i++) {  flag[i] = 0; } count = 0; for(i = 0; i < 1000; i++) {  n = data[i];  if(flag[n] == 0) {   flag[n] = 1;   count++;  } } これだと、2重のループを使うものよりは速くなりますが、データが1000個しかないのに、65536個のflagをクリアするのに時間がかかり、今ひとつです。 ところが、次のような賢い方法があり、2重のループも配列のクリアも無くすことができます。 int i; int count = 0; unsigned short flag[0x10000]; unsigned short link[0x10000]; int n; for(i = 0; i < 1000; i++) {  n = data[i];  if(flag[n] >= count || link[flag[n]] != n) {   flag[n] = count;   link[count] = n;   count++;  } } 私がこのアルゴリズムを知ったのはかなり昔なので、どこで知ったのか思い出せないのですが、これほど賢いアルゴリズムだから何か名前がついていると思うのですが、それがわかりません。 名前がわからないので、人に頼んだりする時、上のような長い説明をしなくてはなりませんが、名前がわかっていれば「??法でやっといて」、と言えばいいのですけど。

  • 2番目の最大値を求める

    n個の要素をもった配列で、2番目に大きい要素を見つけるコードを書きたいのですが、合っているのか不安になって、質問させてもらいます。 アルゴリズムとしては、まず、n-1回の比較をしてその中でmaxを見つけ、残りのn-1個の要素のなかでn-2回の比較をしてそのn-1個の配列のmaxを見つければ、2番目に大きい要素を見つけられると考えました。 int main(void) { int E[n]; int i, j, temp; for(i=0;i<N;i++){ for(j=1;j<N-i;j++){ if(E[j-1]>E[j]){ temp=E[j-1]; E[j-1]=E[j]; E[j]=temp; } } } return E[j]; } これであっていますでしょうか? よろしくお願いいたします。

  • 配列の頭に要素を挿入する方法

    初心者です。配列でご教授お願いします。 インデックス0からひとつずつ要素をずらして、配列の一番前に要素を挿入するにはどうしたらいいのでしょうか? int[] a = new int[10]; int n = a.length; for (int i = n-1; i < 1; i--){ a[i] = a[i-1]; } a[0] = 新しい要素 といった感じで書いたのですが、どうもループの中が実行されていないようなのです。 よろしくお願いいたします。

  • フローチャートの書き方

    要素数がnである配列aの要素の最大値を求めるアルゴリズムのループ端によるフローチャートを完成せよ(前判定繰返し) max =a[0] i=1; while i<n do{ if(a[i]>max)max=a[i]; i++; } フローチャートでかくとどうなりますか?

  • 転置行列アルゴリズム

    こんにちは。プログラミングを学んでいる学生です。 N*Nのint型の配列を転置するCプログラムでなるべく性能の良いものを書け、という課題が出て、それと同時にシンプルな(性能が悪いと思われる)サンプルが配布されてました。 ですが、それを超えるようなアルゴリズムがどうしても思いつきません。 何かアドバイスいただけたらうれしいです。 配布メソッド: #define N 64 //Nは64,128,512,...,2048をはかる typedef int matrix_t[N][N]; void naive_rotate(matrix_t src, matrix_t dst){   int i,j;   for(i=0;i<N;i++)    for(j=0;j<N;j++)       dst[N-1-j][i]=src[i][j]; return; } メソッドの7行目をdst[j][i]にしたらあまり変化ありませんでした。 また、i==Jのときcontinueするようにしたら、逆に遅くなってしまいました。

  • 2次元配列とじゃんけんアルゴリズムについて質問

    以下の、過去に私が質問した、2次元配列とじゃんけんアルゴリズムの質問のURLの見た上で私の質問に答えてください。 URL:ttp://okwave.jp/qa/q7038056.html 質問: public static int janken(int n){ int[][]tb1={ {9,9,9,9}, {9,0,1,2}, {9,2,0,1}, {9,1,2,0} }; int m=rand3(); System.out.println(m+" "); return tb1[n][m]; } 上記ソースコードの2次元配列について、何故「9」という数字があるのか、又1次元目と2次元目の要素数が「4」あるのかわかりませんでした。 上記のURL先で頂いた回答を元に、私は理解に努めました。その理解が正しいか判定してください。 「この勝敗表をあらわす2次元配列について、それぞれのプレイヤーのジャンケンの『手』を要素番号『1,2,3』に対応させている。つまり要素番号『0』は使っていないので、要素数が4つ必要。 また、要素番号『0』は、このjankenプログラムでは不要なので、何の値が入っても構わないので、『たまたま』9が入ってるだけで、9という数字に特に意味はない。因みに、その2つの要素番号に対応する要素が勝敗の結果の番号になる。」 こういうことでしょうか?

    • ベストアンサー
    • Java
  • じゃんけんプログラムとアルゴリズムについて質問

    以下の、過去に私が質問した、2次元配列とじゃんけんアルゴリズムの質問のURLの見た上で私の質問に答えてください。 URL:ttp://okwave.jp/qa/q7038056.html 質問: public static int janken(int n){ int[][]tb1={ {9,9,9,9}, {9,0,1,2}, {9,2,0,1}, {9,1,2,0} }; int m=rand3(); System.out.println(m+" "); return tb1[n][m]; } 上記ソースコードの2次元配列について、何故「9」という数字があるのか、又1次元目と2次元目の要素数が「4」あるのかわかりませんでした。 上記のURL先で頂いた回答を元に、私は理解に努めました。その理解が正しいか判定してください。 私の理解: 「この勝敗表をあらわす2次元配列について、それぞれのプレイヤーのジャンケンの『手』 を要素番号『1,2,3』に対応させている。要番号1,2,3を使用するためには、Javaでは要素番号は0から始まるため、「要素数」が4つ必要。だから、要素数が4ある。よって、この要素番号『0』はこのプログラムに於いては不要。 また、2次元目の要素番号『0』に関連する要素に『9』という数字がチョイスされてるが、『9』という数字のチョイスに特に意味はないが、、0、1、2以外の数字であることに意味はある。因みに、その2つの要素番号に対応する要素が勝敗の結果の番号になる。」 こういうことでしょうか?

    • ベストアンサー
    • Java
  • 昇順と降順

    C言語でクイックソートを行うプログラムを探していたところ、希望していたものが見つかりました。 このプログラムは与えられた数列を昇順に並び替えるものなのですが、これを降順に並び替えるにはどうしたらよいでしょうか? いろいろ試してみたのですが、無限ループになってしまいます。 #include <stdio.h> void QSort(int x[ ], int left, int right); void Swap(int x[ ], int i, int j); void ShowData(int x[ ], int n); void main(void); /* クイックソートを行う */ void QSort(int x[ ], int left, int right) { int i, j; int pivot; i = left; /* ソートする配列の一番小さい要素の添字 */ j = right; /* ソートする配列の一番大きい要素の添字 */ pivot = x[(left + right) / 2]; /* 基準値を配列の中央付近にとる */ while (1) { /* 無限ループ */ while (x[i] < pivot) /* pivot より大きい値が */ i++; /* 出るまで i を増加させる */ while (pivot < x[j]) /* pivot より小さい値が */ j--; /* 出るまで j を減少させる */ if (i >= j) /* i >= j なら */ break; /* 無限ループから抜ける */ Swap(x, i, j); /* x[i] と x[j]を交換 */ i++; /* 次のデータ */ j--; } ShowData(x, 10); /* 途中経過を表示 */ if (left < i - 1) /* 基準値の左に 2 以上要素があれば */ QSort(x, left, i - 1); /* 左の配列を Q ソートする */ if (j + 1 < right) /* 基準値の右に 2 以上要素があれば */ QSort(x, j + 1, right); /* 右の配列を Q ソートする */ } /* 配列の要素を交換する */ void Swap(int x[ ], int i, int j) { int temp; temp = x[i]; x[i] = x[j]; x[j] = temp; } /* n 個のデータを表示する */ void ShowData(int x[ ], int n) { int i; for (i = 0; i < n ; i++) printf("%d ", x[i]); printf("\n"); } void main(void) { /* ソートする配列 */ int x[ ] = {6, 3, 1, 7, 0, 4, 8, 5, 2, 9}; int n = 10; printf("ソート前:\n"); ShowData(x, n); printf("ソート中:\n"); QSort(x, 0, n - 1); printf("ソート後:\n"); ShowData(x, n); }