Java递归斐波那契序列


156

请解释以下简单代码:

public int fibonacci(int n)  {
    if(n == 0)
        return 0;
    else if(n == 1)
      return 1;
   else
      return fibonacci(n - 1) + fibonacci(n - 2);
}

我对最后一行感到困惑,尤其是因为例如,如果n = 5,则将调用fibonacci(4)+ fibonacci(3),依此类推,但我不理解该算法如何以此来计算索引5的值方法。请详细解释!


8
请注意,这是递归的,并且在指数时间内运行。对于较大的N值,这是低效率的。使用迭代方法,我能够计算序列中的前10,000个数字。他们可以在这里找到-goo.gl/hnbF5
Adam

@AdamFisher:能否请您按顺序共享用于计算10,000个数字的代码?我真的很想知道这一点。
Shumail 2013年

4
@AdamFisher您引用的链接已死。
iRuth 2015年

2
该视频将在10分钟内解释理解递归函数youtube.com/watch?v=t4MSwiqfLaY
Chathura Palihakkara

2
还有一种迭代方法,对您来说可能不太困难。关于递归和迭代的出色文章,此处提供代码-codeflex.co/java-get-fibonacci-number-by-index
user5495300

Answers:


165

在斐波那契数列中,每一项都是前两项的总和。因此,您编写了一个递归算法。

所以,

fibonacci(5) = fibonacci(4) + fibonacci(3)

fibonacci(3) = fibonacci(2) + fibonacci(1)

fibonacci(4) = fibonacci(3) + fibonacci(2)

fibonacci(2) = fibonacci(1) + fibonacci(0)

现在您已经知道了fibonacci(1)==1 and fibonacci(0) == 0。因此,您可以随后计算其他值。

现在,

fibonacci(2) = 1+0 = 1
fibonacci(3) = 1+1 = 2
fibonacci(4) = 2+1 = 3
fibonacci(5) = 3+2 = 5

从斐波那契数列中0,1,1,2,3,5,8,13,21....我们可以看到5th element斐波那契数列返回5

请参阅此处的递归教程


它会起作用,但要等到优化后才能优化。请看看我的答案。让我知道的建议/意见的情况下
中号萨克

52

您的代码有2个问题:

  1. 结果存储在只能处理前48个斐波那契数的int中,此后整数填充减位,结果是错误的。
  2. 但是您永远无法运行fibonacci(50)。
    该代码
    fibonacci(n - 1) + fibonacci(n - 2)
    是非常错误的。
    问题是它调用斐波那契不是50次,而是更多次。
    首先,它 每次调用fibonacci(n)时都会调用fibonacci(49)+ fibonacci(48),然后调用fibonacci(
    48)+ fibonacci(47)和fibonacci(47)+ fibonacci(46)
    ,因此复杂度是指数级的。 在此处输入图片说明

非递归代码的方法:

 double fibbonaci(int n){
    double prev=0d, next=1d, result=0d;
    for (int i = 0; i < n; i++) {
        result=prev+next;
        prev=next;
        next=result;
    }
    return result;
}

4
尽管其他一些答案可以更清楚地解释递归,但这可能是更深层次上最相关的答案。
Hal50000 2015年

1
“整数填充减位”是什么意思?
理查德

1
@richard,关于整数的存储方式。在int达到2 ^ 31-1之后,下一位与符号有关,因此数字变为负数。
chro

然后快得多,然后递归。唯一的保留是它对于n = 1无效。需要附加条件
v0rin

1
实际上,“每次变差2 ^ n”实际上是总函数调用的数量2*fibonacci(n+1)-1,因此它的增长与斐波纳契数本身的复杂度相同,即1.618 ^ n而不是2 ^ n
Aemyl

37

在伪代码中,n = 5,将发生以下情况:

斐波那契(4)+斐波那契(3)

这可分为:

(fibonacci(3)+ fibonnacci(2))+(fibonacci(2)+ fibonnacci(1))

这可分为:

(((fibonacci(2)+ fibonnacci(1))+(((fibonacci(1)+ fibonnacci(0)))+((((fibonacci(1)+ fibonnacci(0))+ 1)))

这可分为:

((((fibonacci(1)+ fibonnacci(0))+1)+((1 + 0))+((1 + 0)+1)))

这可分为:

(((((1 + 0)+1)+((1 + 0))+((1 + 0)+1)))

结果是:5

给定fibonnacci序列为1 1 2 3 5 8 ...,第5个元素为5。您可以使用相同的方法来计算其他迭代。


我认为这个答案最好地解释了问题。真的很简单
阿米特(Amit)

这很整齐。解释第n个术语的值及其随后的序列。
分号

12

有时可能很难掌握递归。只需在一张纸上进行少量评估即可:

fib(4)
-> fib(3) + fib(2)
-> fib(2) + fib(1) + fib(1) + fib(0)
-> fib(1) + fib(0) + fib(1) + fib(1) + fib(0)
-> 1 + 0 + 1 + 1 + 0
-> 3

我不确定Java如何实际评估它,但是结果将是相同的。


在第二行中,最后的1和0来自何处?
pocockn

1
@pocockn FIB(2)= FIB(1)+ FIB(0)

因此,您有fib(4),因此n-1和n-2将为fib(3)+ fib(2),然后再次执行n-1和n-2,您会得到-> fib(2)+ fib(1 ),您从哪里获得+ fib(1)+ fib(0)的信息?添加到结尾
pocockn

@pocockn fib(2)+ fib(1)来自fib(3),fib(1)+ fib(0)来自fib(2)
蒂姆


8
                                F(n)
                                /    \
                            F(n-1)   F(n-2)
                            /   \     /      \
                        F(n-2) F(n-3) F(n-3)  F(n-4)
                       /    \
                     F(n-3) F(n-4)

需要注意的重要一点是该算法是指数算法,因为它不存储先前计算出的数字的结果。例如F(n-3)被调用3次。

有关更多详细信息,请参阅dasgupta的算法第0.2章


有一种编程方法可以避免使用动态编程
一遍又一遍地

8

大多数答案是好的,并解释了斐波那契递归的工作原理。

这是对包括递归在内的三种技术的分析:

  1. 对于循环
  2. 递归
  3. 记忆化

这是我的代码来测试所有三个:

public class Fibonnaci {
    // Output = 0 1 1 2 3 5 8 13

    static int fibMemo[];

    public static void main(String args[]) {
        int num = 20;

        System.out.println("By For Loop");
        Long startTimeForLoop = System.nanoTime();
        // returns the fib series
        int fibSeries[] = fib(num);
        for (int i = 0; i < fibSeries.length; i++) {
            System.out.print(" " + fibSeries[i] + " ");
        }
        Long stopTimeForLoop = System.nanoTime();
        System.out.println("");
        System.out.println("For Loop Time:" + (stopTimeForLoop - startTimeForLoop));


        System.out.println("By Using Recursion");
        Long startTimeRecursion = System.nanoTime();
        // uses recursion
        int fibSeriesRec[] = fibByRec(num);

        for (int i = 0; i < fibSeriesRec.length; i++) {
            System.out.print(" " + fibSeriesRec[i] + " ");
        }
        Long stopTimeRecursion = System.nanoTime();
        System.out.println("");
        System.out.println("Recursion Time:" + (stopTimeRecursion -startTimeRecursion));



        System.out.println("By Using Memoization Technique");
        Long startTimeMemo = System.nanoTime();
        // uses memoization
        fibMemo = new int[num];
        fibByRecMemo(num-1);
        for (int i = 0; i < fibMemo.length; i++) {
            System.out.print(" " + fibMemo[i] + " ");
        }
        Long stopTimeMemo = System.nanoTime();
        System.out.println("");
        System.out.println("Memoization Time:" + (stopTimeMemo - startTimeMemo));

    }


    //fib by memoization

    public static int fibByRecMemo(int num){

        if(num == 0){
            fibMemo[0] = 0;
            return 0;
        }

        if(num ==1 || num ==2){
          fibMemo[num] = 1;
          return 1; 
        }

        if(fibMemo[num] == 0){
            fibMemo[num] = fibByRecMemo(num-1) + fibByRecMemo(num -2);
            return fibMemo[num];
        }else{
            return fibMemo[num];
        }

    }


    public static int[] fibByRec(int num) {
        int fib[] = new int[num];

        for (int i = 0; i < num; i++) {
            fib[i] = fibRec(i);
        }

        return fib;
    }

    public static int fibRec(int num) {
        if (num == 0) {
            return 0;
        } else if (num == 1 || num == 2) {
            return 1;
        } else {
            return fibRec(num - 1) + fibRec(num - 2);
        }
    }

    public static int[] fib(int num) {
        int fibSum[] = new int[num];
        for (int i = 0; i < num; i++) {
            if (i == 0) {
                fibSum[i] = i;
                continue;
            }

            if (i == 1 || i == 2) {
                fibSum[i] = 1;
                continue;
            }

            fibSum[i] = fibSum[i - 1] + fibSum[i - 2];

        }
        return fibSum;
    }

}

结果如下:

By For Loop
 0  1  1  2  3  5  8  13  21  34  55  89  144  233  377  610  987  1597  2584  4181 
For Loop Time:347688
By Using Recursion
 0  1  1  2  3  5  8  13  21  34  55  89  144  233  377  610  987  1597  2584  4181 
Recursion Time:767004
By Using Memoization Technique
 0  1  1  2  3  5  8  13  21  34  55  89  144  233  377  610  987  1597  2584  4181 
Memoization Time:327031

因此,我们可以看到记忆是最佳的时间安排,并且for循环匹配紧密。

但是递归花费的时间最长,可能是您在现实生活中应该避免的。另外,如果使用递归,请确保优化解决方案。


1
“在这里我们可以看到for循环是最好的时间选择”;“对于循环时间:347688”;“记忆时间:327031”;347688>327031。–阿赞查尔斯
AjahnCharles)'17年

@CodeConfident是的,我今天才看到该错误,并将要纠正。不管怎么说,多谢拉 :)。
Pritam Banerjee

7

这是我发现的最好的视频,可以完全解释Java中的递归和斐波那契数列。

http://www.youtube.com/watch?v=dsmBRUCzS7k

这是他的序列代码,他的解释比我尝试键入的要好。

public static void main(String[] args)
{
    int index = 0;
    while (true)
    {
        System.out.println(fibonacci(index));
        index++;
    }
}
    public static long fibonacci (int i)
    {
        if (i == 0) return 0;
        if (i<= 2) return 1;

        long fibTerm = fibonacci(i - 1) + fibonacci(i - 2);
        return fibTerm;
    }

5

对于斐波那契递归解,重要的是保存较小的斐波那契数的输出,同时检索较大数的值。这称为“记忆”。

这是一个代码,用于记忆较小的斐波那契值,同时检索较大的斐波那契数。这段代码是有效的,并且不会发出相同功能的多个请求。

import java.util.HashMap;

public class Fibonacci {
  private HashMap<Integer, Integer> map;
  public Fibonacci() {
    map = new HashMap<>();
  }
  public int findFibonacciValue(int number) {
    if (number == 0 || number == 1) {
      return number;
    }
    else if (map.containsKey(number)) {
      return map.get(number);
    }
    else {
      int fibonacciValue = findFibonacciValue(number - 2) + findFibonacciValue(number - 1);
      map.put(number, fibonacciValue);
      return fibonacciValue;
    }
  }
}

4

斐波契数列中,前两项为0和1,其他各项为前两项的总和。即:
0 1 1 2 3 5 8 ...

所以第5个项目是第4个和第3个项目的总和。


4

Michael Goodrich等人在Java的数据结构和算法中提供了一种非常聪明的算法,用于通过返回[fib(n),fib(n-1)]数组在线性时间内递归求解斐波那契。

public static long[] fibGood(int n) {
    if (n < = 1) {
        long[] answer = {n,0};
        return answer;
    } else {
        long[] tmp = fibGood(n-1);
        long[] answer = {tmp[0] + tmp[1], tmp[0]};
        return answer;
    }
}

这产生fib(n)= fibGood(n)[0]。


4

这是O(1)解决方案:

 private static long fibonacci(int n) {
    double pha = pow(1 + sqrt(5), n);
    double phb = pow(1 - sqrt(5), n);
    double div = pow(2, n) * sqrt(5);

    return (long) ((pha - phb) / div);
}

Binet的斐波那契数公式用于上述实现。对于大的输入long可以用代替BigDecimal


3

Fibbonacci序列是一个将数字结果相加后的序列,从1开始。

      so.. 1 + 1 = 2
           2 + 3 = 5
           3 + 5 = 8
           5 + 8 = 13
           8 + 13 = 21

一旦了解了斐波纳契是什么,我们就可以开始分解代码。

public int fibonacci(int n)  {
    if(n == 0)
        return 0;
    else if(n == 1)
      return 1;
   else
      return fibonacci(n - 1) + fibonacci(n - 2);
}

第一个if语句检查基本情况,在此情况下循环可能会中断。下面的else if语句也做同样的事情,但是可以这样重写:

    public int fibonacci(int n)  {
        if(n < 2)
             return n;

        return fibonacci(n - 1) + fibonacci(n - 2);
    }

现在已经建立了一个基本案例,我们必须了解调用堆栈。您对“ fibonacci”的第一个调用将是最后一个在堆栈上解决的问题(调用顺序),因为它们以与调用相反的顺序进行解析。最后一个调用的方法首先解析,然后在该方法之前调用最后一个,依此类推...

因此,首先进行所有调用,然后再对这些结果进行“计算”。输入为8时,我们期望输出为21(请参见上表)。

一直调用fibonacci(n-1)直到到达基本情况为止,然后调用fibonacci(n-2)直到到达基本情况为止。当堆栈开始以相反顺序对结果求和时,结果将像这样...

1 + 1 = 1        ---- last call of the stack (hits a base case).
2 + 1 = 3        ---- Next level of the stack (resolving backwards).
2 + 3 = 5        ---- Next level of the stack (continuing to resolve).

他们不断冒泡(向后解析),直到正确的总和返回到堆栈中的第一个调用为止,这就是您得到答案的方式。

话虽如此,该算法效率很低,因为它为代码拆分到的每个分支计算相同的结果。更好的方法是“自下而上”的方法,不需要记忆(缓存)或递归(深层调用堆栈)。

像这样

        static int BottomUpFib(int current)
        {
            if (current < 2) return current;

            int fib = 1;
            int last = 1;

            for (int i = 2; i < current; i++)
            {
                int temp = fib;
                fib += last;
                last = temp;
            }

            return fib;
        }

2

这里提供的大多数解决方案都以O(2 ^ n)复杂度运行。在递归树中重新计算相同的节点效率低下并且浪费CPU周期。

我们可以使用备忘录使斐波那契函数在O(n)时间运行

public static int fibonacci(int n) {
    return fibonacci(n, new int[n + 1]);
}

public static int fibonacci(int i, int[] memo) {

    if (i == 0 || i == 1) {
        return i;
    }

    if (memo[i] == 0) {
        memo[i] = fibonacci(i - 1, memo) + fibonacci(i - 2, memo);
    }
    return memo[i];
}

如果我们遵循“自下而上的动态编程”路线,则以下代码足以计算斐波那契:

public static int fibonacci1(int n) {
    if (n == 0) {
        return n;
    } else if (n == 1) {
        return n;
    }
    final int[] memo = new int[n];

    memo[0] = 0;
    memo[1] = 1;

    for (int i = 2; i < n; i++) {
        memo[i] = memo[i - 1] + memo[i - 2];
    }
    return memo[n - 1] + memo[n - 2];
}

2

为什么这个答案不同

每隔一个答案:

  • 打印而不是退货
  • 每次迭代进行2次递归调用
  • 通过使用循环忽略该问题

(除了:这些都不是有效的;使用Binet公式直接计算第n 项)

尾递归Fib

这是一种递归方法,它通过传递之前的答案和之前的答案,从而避免了两次递归调用。

private static final int FIB_0 = 0;
private static final int FIB_1 = 1;

private int calcFibonacci(final int target) {
    if (target == 0) { return FIB_0; }
    if (target == 1) { return FIB_1; }

    return calcFibonacci(target, 1, FIB_1, FIB_0);
}

private int calcFibonacci(final int target, final int previous, final int fibPrevious, final int fibPreviousMinusOne) {
    final int current = previous + 1;
    final int fibCurrent = fibPrevious + fibPreviousMinusOne;
    // If you want, print here / memoize for future calls

    if (target == current) { return fibCurrent; }

    return calcFibonacci(target, current, fibCurrent, fibPrevious);
}

1

这是显示或获得1 1 2 3 5 8的输出的基本序列,是先前数字的和与当前数字的下一个显示的序列。

尝试观看下面的链接Java递归斐波那契数列教程

public static long getFibonacci(int number){
if(number<=1) return number;
else return getFibonacci(number-1) + getFibonacci(number-2);
}

单击此处,观看Java递归斐波那契数列教程以获取勺子


他需要了解的是代码如何工作以及为什么以编写方式编写代码。
Adarsh 2013年

我想我在第一句话中提到它是如何工作的?我编写代码使其更简单。顺便说一句,对不起。
Jaymelson Galang 2013年

您的代码没有错。只有那个家伙想了解该代码是如何工作的。检查RanRag的答案。这种东西:)
Adarsh

啊,好的,抱歉,我是stackoverflow的新手。只是想帮助^ _ ^
Jaymelson Galang 2013年

1

我认为这是一种简单的方法:

public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int number = input.nextInt();
        long a = 0;
        long b = 1;
        for(int i = 1; i<number;i++){
            long c = a +b;
            a=b;
            b=c;
            System.out.println(c);
        }
    }
}

1

RanRag(accepted)答案可以很好地工作,但这不是优化的解决方案,除非并且如Anil答案中所述记住它为止。

对于递归,请考虑以下方法,TestFibonacci最小的方法调用

public class TestFibonacci {

    public static void main(String[] args) {

        int n = 10;

        if (n == 1) {
            System.out.println(1);

        } else if (n == 2) {
            System.out.println(1);
            System.out.println(1);
        } else {
            System.out.println(1);
            System.out.println(1);
            int currentNo = 3;
            calFibRec(n, 1, 1, currentNo);
        }

    }

    public static void calFibRec(int n, int secondLast, int last,
            int currentNo) {
        if (currentNo <= n) {

            int sum = secondLast + last;
            System.out.println(sum);
            calFibRec(n, last, sum, ++currentNo);
        }
    }

}

1
public class febo 
{
 public static void main(String...a)
 {
  int x[]=new int[15];  
   x[0]=0;
   x[1]=1;
   for(int i=2;i<x.length;i++)
   {
      x[i]=x[i-1]+x[i-2];
   }
   for(int i=0;i<x.length;i++)
   {
      System.out.println(x[i]);
   }
 }
}

1

通过使用内部ConcurrentHashMap,从理论上讲,它可以允许此递归实现在多线程环境中正常运行,从而实现了同时使用BigInteger和Recursion的fib函数。大约需要53毫秒来计算前100个Fib编号。

private final Map<BigInteger,BigInteger> cacheBig  
    = new ConcurrentHashMap<>();
public BigInteger fibRecursiveBigCache(BigInteger n) {
    BigInteger a = cacheBig.computeIfAbsent(n, this::fibBigCache);
    return a;
}
public BigInteger fibBigCache(BigInteger n) {
    if ( n.compareTo(BigInteger.ONE ) <= 0 ){
        return n;
    } else if (cacheBig.containsKey(n)){
        return cacheBig.get(n);
    } else {
        return      
            fibBigCache(n.subtract(BigInteger.ONE))
            .add(fibBigCache(n.subtract(TWO)));
    }
}

测试代码为:

@Test
public void testFibRecursiveBigIntegerCache() {
    long start = System.currentTimeMillis();
    FibonacciSeries fib = new FibonacciSeries();
    IntStream.rangeClosed(0,100).forEach(p -&R {
        BigInteger n = BigInteger.valueOf(p);
        n = fib.fibRecursiveBigCache(n);
        System.out.println(String.format("fib of %d is %d", p,n));
    });
    long end = System.currentTimeMillis();
    System.out.println("elapsed:" + 
    (end - start) + "," + 
    ((end - start)/1000));
}
测试的输出是:
    。
    。
    。
    。
    。
    93的fib是12200160415121876738
    94的fib是19740274219868223167
    95的fib是31940434634990099905
    96的fib是51680708854858323072
    97的fib是83621143489848422977
    98的fib是135301852344706746049
    99的fib是218922995834555169026
    fib为354224848179261915075
    播放时间:58,0

1

这是一行febonacci递归:

public long fib( long n ) {
        return n <= 0 ? 0 : n == 1 ? 1 : fib( n - 1 ) + fib( n - 2 );
}

1

试试这个

private static int fibonacci(int n){
    if(n <= 1)
        return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

0

作为补充,如果您希望能够计算更大的数字,则应使用BigInteger。

一个迭代的例子。

import java.math.BigInteger;
class Fibonacci{
    public static void main(String args[]){
        int n=10000;
        BigInteger[] vec = new BigInteger[n];
        vec[0]=BigInteger.ZERO;
        vec[1]=BigInteger.ONE;
        // calculating
        for(int i = 2 ; i<n ; i++){
            vec[i]=vec[i-1].add(vec[i-2]);
        }
        // printing
        for(int i = vec.length-1 ; i>=0 ; i--){
            System.out.println(vec[i]);
            System.out.println("");
        }
    }
}

0

http://en.wikipedia.org/wiki/Fibonacci_number详情

public class Fibonacci {

    public static long fib(int n) {
        if (n <= 1) return n;
        else return fib(n-1) + fib(n-2);
    }

    public static void main(String[] args) {
        int N = Integer.parseInt(args[0]);
        for (int i = 1; i <= N; i++)
            System.out.println(i + ": " + fib(i));
    }

}

使它尽可能简单,无需使用while循环和其他循环


0
public class FibonacciSeries {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        for (int i = 0; i <= N; i++) {
            int result = fibonacciSeries(i);
            System.out.println(result);
        }
        scanner.close();
    }

    private static int fibonacciSeries(int n) {
        if (n < 0) {
            return 1;
        } else if (n > 0) {
            return fibonacciSeries(n - 1) + fibonacciSeries(n - 2);
        }
        return 0;
    }
}

0

用途while

public int fib(int index) {
    int tmp = 0, step1 = 0, step2 = 1, fibNumber = 0;
    while (tmp < index - 1) {
        fibNumber = step1 + step2;
        step1 = step2;
        step2 = fibNumber;
        tmp += 1;
    };
    return fibNumber;
}

该解决方案的优点是易于阅读和理解代码,希望对您有所帮助


0

Fibbonacci序列是一个将一个数字的结果求和的序列,然后我们将其添加到先前的结果中,我们应该从1开始。以前的号码,我改变了立场。我正在搜索1到15的斐波纳契序列。

public static void main(String args[]) {

    numbers(1,1,15);
}


public static int numbers(int a, int temp, int target)
{
    if(target <= a)
    {
        return a;
    }

    System.out.print(a + " ");

    a = temp + a;

    return numbers(temp,a,target);
}

-1
 public static long fib(int n) {
    long population = 0;

    if ((n == 0) || (n == 1)) // base cases
    {
        return n;
    } else // recursion step
    {

        population+=fib(n - 1) + fib(n - 2);
    }

    return population;
}

-1

简单斐波那契

public static void main(String[]args){

    int i = 0;
    int u = 1;

    while(i<100){
        System.out.println(i);
        i = u+i;
        System.out.println(u);
        u = u+i;
    }
  }
}

2
欢迎来到SO。虽然您的答案确实计算了斐波那契数列。您的答案没有回答OP,后者询问了递归函数。
詹姆斯·K

-2

@chro很有用,但是他/他没有显示递归执行此操作的正确方法。解决方法如下:

class Fib {
    static int count;

    public static void main(String[] args) {
        log(fibWrong(20));  // 6765
        log("Count: " + count); // 21891
        count = 0;
        log(fibRight(20)); // 6765
        log("Count: " + count); // 19
    }

    static long fibRight(long n) {
        return calcFib(n-2, 1, 1);
    }

    static long fibWrong(long n) {
        count++;
        if (n == 0 || n == 1) {
            return n;
        } else if (n < 0) {
            log("Overflow!");
            System.exit(1);
            return n;
        } else {
            return fibWrong(n-1) + fibWrong(n-2);
        }

    }

    static long calcFib(long nth, long prev, long next) {
        count++;
        if (nth-- == 0)
            return next;
        if (prev+next < 0) {
            log("Overflow with " + (nth+1) 
                + " combinations remaining");
            System.exit(1);
        }
        return calcFib(nth, next, prev+next);
    }

    static void log(Object o) {
        System.out.println(o);
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.