Реализация функции Tak с использованием хвостовой рекурсии

Можно ли реализовать функцию Tak:

введите здесь описание изображения

хвост рекурсивно в C/C++ таким образом, чтобы gcc/g++ мог выполнять оптимизацию хвостовой рекурсии?
Я не уверен, что вызовы вложенных рекурсивных функций запутают компилятор.


person jh314    schedule 13.11.2013    source источник
comment
Я не вижу, как это было бы? Поскольку он просто начал бы делать их все и продолжал бы ветвление?   -  person Rivasa    schedule 13.11.2013


Ответы (2)


Оптимизация хвостовой рекурсии в C++ требует, чтобы был только 1 рекурсивный вызов (что в основном позволяет преобразовать его в цикл), и этот рекурсивный вызов был последней операцией в функции:

Пример:

unsigned int f( unsigned int a ) 
{
   if ( a == 0 ) 
   {
      return a;
   }
   return f( a - 1 );   // tail recursion
}

Поскольку функция Tak требует 4 рекурсивных вызова на «итерацию»:

int tak(int x, int y, int z)
{
    if (x >= y)
    {
        return z;
    }
    else
    {
        return tak(tak(x-1, y, z), tak(y-1, z, x), tak(z-1, x, y)); // this is why it cannot happen
    }
}

Как видите, последний вызов рекурсивный, но внутри него 3 рекурсивных вызова. Это предотвращает оптимизацию хвостовой рекурсии (и нет логического метода для преобразования этого в нерекурсивный цикл, который требуется для оптимизации хвостовой рекурсии).

Другой способ его реализации:

int tak(int x, int y, int z)
{
    while (x > y) 
    {
        int oldx = x, oldy = y;
        x = tak(x - 1, y, z);
        y = tak(y - 1, z, oldx);
        if (x <= y) 
            break;
        z = tak(z - 1, oldx, oldy);
    }
    return z;
}

Что еще раз показывает, что даже в форме цикла он по-прежнему рекурсивен, что предотвращает оптимизацию хвостовой рекурсии.

person Zac Howland    schedule 13.11.2013
comment
Может ли компилятор преобразовать прямую реализацию в реализацию цикла? - person jh314; 14.11.2013
comment
@ jh314 jh314 Что вы имеете в виду под прямой реализацией? - person Zac Howland; 15.11.2013
comment
Тот, что с заявлением return tak(tak(x-1, y, z), tak(y-1, z, x), tak(z-1, x, y)); - person jh314; 17.11.2013
comment
Возможный? э. Маловероятно, что он увидит это и преобразует в петлевую версию. Единственное, что дает вам цикл, — это несколько меньше вызовов функций (это означает, что стек вызовов не так сильно переполняется). - person Zac Howland; 17.11.2013

Исходя из вашего математического определения, мы можем просто написать функцию как:

int tak(int x, int y, int z){
    if(x>y)
        return tak(tak(1-x,y,z), tak(y-1,z,x), tak(z-1,x,y));
    else
        return z;
} 

Однако вы не можете сделать это с хвостовой рекрузией, поскольку ее нельзя преобразовать в петлю. Так как существует более одного вызова рекрузии.

person Rivasa    schedule 13.11.2013
comment
Количество рекурсивных вызовов не является важной переменной. Есть функции с одним рекурсивным вызовом, которые нельзя превратить в цикл, а есть функции с большим количеством рекурсивных вызовов, которые могут. Важно место звонка. - person Jules; 01.07.2017
comment
Спасибо @Jules. Ценить это! - person Rivasa; 01.07.2017