0

2 つの数値が特定の範囲に収まらないように乱数を生成するプログラムを作成する必要があります。残念ながら、私は学校でそれを行う必要があり、コンピューターで使用できる唯一の言語は Java です (それ以外の場合は C++ で行います)。私は Java をほとんど知らないのですが、私のプログラムは 20 個ほどの数値を生成した後にスタック オーバーフローを引き起こします。誰かが理由を説明してもらえますか? プログラムはやや醜いですが、急いで準備する必要があります。

import java.io.*;
import java.util.*;

public class RandomNumbers {
public static int GenerateNumber(int previousNumber, int[] numberUsed) {
    Random random = new Random();
    int number = random.nextInt(39);
    if (previousNumber >= 1 && previousNumber <= 9) {
        while (number >= 1 && number <= 9)
            number = random.nextInt(39) + 1;
    } else if (previousNumber >= 10 && previousNumber <= 17) {
        while (number >= 10 && previousNumber <= 17)
            number = random.nextInt(39) + 1;
    } else if (previousNumber >= 18 && previousNumber <= 32) {
        while (number >= 18 && previousNumber <= 32)
            number = random.nextInt(39) + 1;
    } else if (previousNumber >= 33 && previousNumber <= 41) {
        while (number >= 32 && number <= 41)
            number = random.nextInt(39) + 1;
    }
    return number;
}

public static void main(String[] args) {
    int[] numberUsed;
    numberUsed = new int[40];
    for (int i = 0; i < 40; ++i) {
        numberUsed[i] = 0;
    }
    int previousNumber = 0;
    for (int y = 0; y < 40; ++y) {
        int number = 1;
        while (numberUsed[ number = GenerateNumber
                         (previousNumber, numberUsed) ] != 0);
        numberUsed[number] = 1;
        previousNumber = number;
        System.out.println(y);
    }
}
}

編集: さて、何らかの理由で、for ループ (カウンターとして y を持つもの) は、while ループを含めると 40 回実行されません。誰かがなぜこれが起こっているのか説明できますか?

4

6 に答える 6

2

関数には、最終的にメインに戻る問題が発生します。

あなたは再帰問題に直面するでしょう

if (numberUsed[number] != 0) {
    return GenerateNumber(previousNumber, numberUsed);
}

配列が 1 でいっぱいになると、実際に正常に戻るのではなく、関数呼び出しを実行し続けます。関数が再帰的に実行され、最終的にスタック オーバーフローが発生します。

関数を再度呼び出すと、パラメーター (previousNumber と numberUsed) は最後の実行と同じで、何も変わりません。ゼロ以外にヒットする可能性が非常に高いため、同じ問題に遭遇し続けるため、再帰は永遠に積み重なり、最終的にはクラッシュします。

関数内の if ステートメントを削除し、数値を main に戻して、そこでチェックを行う必要があります。

于 2012-05-18T19:17:41.393 に答える
0

このコードは袋小路を検出し、最後に到達するまで再起動を続けます(通常は1回または2回の再試行のみを行います)。また、許可された選択肢の収集されたシーケンスから乱数を直接選択するため、1回の試行で確率的な実行時間は発生しません。

import java.util.Arrays;
import java.util.BitSet;
import java.util.Random;

public class RandomNumberGenerator {
  static final Random random = new Random();
  static final BitSet usedNumbers = new BitSet(40);
  static final int[] ticks = {0, 1, 10, 18, 33, 41};
  static int previousNumber;

  public static int generateNumber() {
    for (int i = 1; i < ticks.length; i++)
      if (previousNumber < ticks[i])
        return generateOutsideRange(ticks[i-1], ticks[i]);
    return generateOutsideRange(0, 0);
  }

  static int generateOutsideRange(int low, int high) {
    final int[] numsToChoose = new int[40];
    int choiceLimit = 0;
    for (int i = (low > 1? 1 : high); i < 41; i = (++i == low? high : i))
      if (!usedNumbers.get(i)) numsToChoose[choiceLimit++] = i;
    if (choiceLimit == 0) throw new CulDeSacException();
    final int r = numsToChoose[random.nextInt(choiceLimit)];
    usedNumbers.set(r);
    previousNumber = r;
    return r;
  }

  public static void main(String[] args) {
    while (true) try {
      usedNumbers.clear();
      previousNumber = -1;
      final int[] rands = new int[40];
      for (int i = 0; i < rands.length; i++) rands[i] = generateNumber();
      System.out.println(Arrays.toString(rands));
      break;
    } catch (CulDeSacException e) { System.out.println("Retry"); }
  }
}

class CulDeSacException extends RuntimeException {}
于 2012-05-18T20:13:44.250 に答える
0

ゼロになった配列から始めて、徐々に 1 で埋めていき、それらを 0 にリセットすることはありません。配列のほとんどの要素が非ゼロの場合GenerateNumber、非常に深いネストで再帰的に呼び出す可能性が非常に高くなります。 、それはあなたが経験するものです。

また、 で変更することはないpreviousNumberためGenerateNumber、生成された乱数に関係なく、常に同じパスをたどることになります。

于 2012-05-18T19:09:05.757 に答える
0

StackOverFlow は、メソッド/コンストラクターなどのオーバーラップが原因で発生します。ここで、

if (numberUsed[number] != 0) {
        return GenerateNumber(previousNumber, numberUsed);
    }

このコードは GenerateNumber メソッド内にあり、同じメソッドを呼び出し続けています。それが理由です。確認したい場合は、その部分を削除して試してみると、正常にコンパイルされます。これを取り除くには、上記のプロセスを削除します。

ループ、while ループを使用し、その中にすべての if else ステートメントを入れることでそれを行うことができます

于 2012-05-18T19:32:06.103 に答える
0

これは予想される動作ですか?「配列のシャッフル」

    ArrayList<Integer> numbers = new ArrayList<Integer>();
    int[] scrambled = new int[40];
    Random rnd = new Random();
    for(int i=0;i<scrambled.length;i++){
        numbers.add(i+1);
    }
    int idx=0;
    while(!numbers.isEmpty()){
        int pos = rnd.nextInt(numbers.size());

        scrambled[idx] = numbers.get(pos);
        numbers.remove(pos);

        idx++;
    }
    for(int i=0;i<scrambled.length;i++){
        System.out.println(scrambled[i]);
    }
于 2012-05-19T20:53:09.783 に答える
0

これをしばらく見つめた後、実用的な解決策を見つけたと思います。それはまだ本当に醜いですが、あなたはそれで大丈夫のようです.

public static int GenerateNumber(int previousNumber, 
                                int[] numberUsed) {
    Random random = new Random();
    int number = random.nextInt(39);
    while (numberUsed[number] != 0) {
        number = random.nextInt(39);
        if (previousNumber >= 1 && previousNumber <= 9) {
            while (number >= 1 && number <= 9)
                number = random.nextInt(39) + 1;
        } else if (previousNumber >= 10 && previousNumber <= 17) {
            while (number >= 10 && previousNumber <= 17)
                number = random.nextInt(39) + 1;
        } else if (previousNumber >= 18 && previousNumber <= 32) {
            while (number >= 18 && previousNumber <= 32)
                number = random.nextInt(39) + 1;
        } else if (previousNumber >= 33 && previousNumber <= 41) {
            while (number >= 32 && number <= 41)
                    number = random.nextInt(39) + 1;
        }
    }
    return number;
}

余談ですが、リストの最後に近づくとランタイムもひどいものになりますが、スタックオーバーフローは発生しません。また、考えてみると無限ループになる可能性があるため、おそらくこれを完全に再設計する必要があります。

于 2012-05-18T19:16:10.793 に答える