有人可以解释以下用于查找重复周期的长度或重复小数的周期的方法吗?

时间:2013-06-07 04:51:40

标签: java algorithm repeat

在维基百科页面上读取重复小数的信息后,我找到了一种方法来查找小数点重复部分的位数。

例如,

1/3 = 0.333333333333333333333333333333 ...所以结果是1位数。

1/7 = 0.142857142857142857142857142857 ...所以结果是6位数。

然而,我的方法(在Java中)不适用于1/6,它应该产生1,因为:

1/6 = 0.1666 ...所以尽管十进制的非重复部分,结果是1位数。

我找到了一个有效的解决方案(归功于Nayuki Minase)。

private static int getCycleLength(int n)
{
    Map<Integer,Integer> stateToIter = new HashMap<Integer,Integer>();
    int state = 1;
    int iter = 0;
    while (!stateToIter.containsKey(state))
    {
        stateToIter.put(state, iter);
        state = state * 10 % n;
        iter++;
    }
    System.out.println(iter + " - " + stateToIter.get(state));
    return iter - stateToIter.get(state);
}

有人可以向我解释这个算法是如何工作的吗?谢谢。

3 个答案:

答案 0 :(得分:1)

所以在这个算法中,这一行是关键。

while(!stateToIter.containsKey(state))

当程序发现重复状态时会破坏程序。现在找到重复状态意味着我们正在检测重复循环。让我们解决问题,说我们必须找出6。 我们做1/6的方式是

Problem :6 | 1 | Result = ?  
 Step 1: 
 Add 0. in the result and multiply 1 with 10
 6 | 10 | 0.

 Iteration 

 Step 2: 
 Do the division
 6 | 10 | 0.1
      6
    -----
      4 [Mod]

 Iteration = 0

 Step 3: 
 Multiply mod with 10 and carry on
 6 | 10 | 0.16
      6
    -----
      40 
      36
    -----
      04 [Mod]


 Iteration = 1

 Now we find a repeating mod so now matter how far we go we always get 4 as mod and our result will be 0.166666.. and so on so our repeating cycle will be 1 which is our iteration.   

答案 1 :(得分:1)

Nayuki在这里。代码来自Project Euler p026.java。让我解释一下发生了什么。

主要思想是我们模拟长除法并检测余数何时开始重复。让我们用计算1/7的例子来说明。

    0.142857...
  -------------
7 | 1.000000000
      7
    ---
      30
      28
      --
       20
       14
       --
        60
        56
        --
         40
         35
         --
          50
          49
          --
           10
           ...

要执行长除法,我们执行以下步骤:

  1. 设置divisor = 7.设置dividend = 1.(我们计算1/7。)

      ----
    7 | 1
    
  2. 除数进入股息的次数是多少?让它为 k 。将此数字附加到商。

        0
      ---
    7 | 1
    
  3. 从被除数中减去 k ×除数。这是余下的。

        0
      ---
    7 | 1
       -0
       --
        1
    
  4. 在右侧移动一个新数字。在我们的例子中,它是零的无限小数。这相当于将股息乘以10。

        0
      ---
    7 | 1.0
       -0
       --
        10
    
  5. 转到第2步并无限重复。

        0.1
      -----
    7 | 1.0
       -0
       --
        10
        -7
        --
         3
         ...
    
  6. 我们在长期划分的每次迭代中更新股息。如果被除数具有先前所做的值,则它将生成相同的十进制数字。

    现在,在代码中:

    // Step 1
    int divisor = 7;
    int dividend = 1;
    
    while (true) {
      // Step 2
      int k = dividend / divisor;  // Floor
    
      // Step 3
      dividend -= k * divisor;
    
      // Step 4
      dividend *= 10;
    }
    

    通过一些数学运算,步骤2和3可以合并为dividend %= divisor;。此外,这可以与步骤4结合以获得dividend = dividend % divisor * 10;


    地图记录了第一次看到每个红利状态。在我们的例子中:

    • 在迭代0中看到余数1。
    • 剩余3是在迭代1中看到的。
    • 余下2见于迭代2。
    • 余下的6是在迭代3中看到的。
    • 剩下的4是在迭代4中看到的。
    • 剩下的5是在迭代5中看到的。
    • 余下的1是在迭代6中看到的。

    迭代6的状态与迭代0的状态相同。此外,这是最短的周期。因此,循环长度为6-0 = 6。

答案 2 :(得分:0)

当修改为十进制时,你逐渐将你的值乘以10,直到你有0(不再写)或放弃(你已达到你的精度极限)

如果您再次使用相同的值,则会从该点重复相同的数字。当您获得重复值并计算自您第一次看到它之后的时间(重复周期的长度)时,该方法的作用是找到


BTW这个问题的另一个解决方案是避免使用Map。任何重复序列必须是1/9或1/99或1/999或1/9999等的倍数。这可以找出除数需要多少个9作为因子。这是它重复的重点。

public static void main(String... args) throws IOException {
    for (int i = 3; i < 100; i++) {
        System.out.println("i: " + i + " f: " + 1.0 / i + " repeat: " + getRepeatingCount(i));
    }
}

public static final BigInteger TEN_TO_19 = BigInteger.TEN.pow(19);

public static int getRepeatingCount(int divisor) {
    if (divisor <= 0) throw new IllegalArgumentException();
    while (divisor % 2 == 0) divisor /= 2;
    while (divisor % 5 == 0) divisor /= 5;
    int count = 1;
    if (divisor == 1) return 0;
    for (long l = 10; l > 0; l *= 10) {
        long nines = l - 1;
        if (nines % divisor == 0)
            return count;
        count++;
    }
    for(BigInteger bi = TEN_TO_19; ; bi = bi.multiply(BigInteger.TEN)) {
        BigInteger nines = bi.subtract(BigInteger.ONE);
        if (nines.mod(BigInteger.valueOf(divisor)).equals(BigInteger.ZERO))
            return count;
        count++;
    }
}