Неопределенное поведение
К сожалению, ваш код показывает неопределенное поведение из-за доступа к неинициализированному значению и из-за пределов доступа. Самым простым тестом, демонстрирующим это поведение, является m = 1, n = 0
. Это указывает только на две итерации внешнего l oop и одну итерацию внутреннего l oop и, следовательно, легче анализировать:
int ackermann_i(int m, int n) {
int* A = (int*) malloc((m+1) * (n+1) * sizeof(int));
for(int i = 0; i <= m; i++) {
for(int j = 0; j <= n; j++) {
if(i == 0) {
A[i*(n+1) + j] = j + 1; // (1)
} else if(j == 0) {
A[i*(n+1) + j] = A[(i-1)*(n+1) + 1]; // (2)
} else {
A[i*(n+1) + j] = A[(i-1)*(n+1) + A[i*(n+1) + (j-1)]]; // (3)
}
}
}
return A[m*(n+1) + n];
}
Итак, давайте итерацию вручную:
i = 0, j = 0
. Мы вводим (1)
и устанавливаем A[0 + 0] = 1
. i = 1, j = 0
. Мы вводим (2)
и устанавливаем A[2 + 0] = A[0 + 1]
. - всегда есть по крайней мере
j == 0
, поэтому нас не волнует (3)
.
Но есть проблема: мы никогда не устанавливаем A[0 + 1]
. Это значение может быть нулевым, оно может быть и другим случайным образом; возникает неопределенное поведение. Хуже того, наш A
недостаточно велик: (m+1)*(n+1)
здесь только 2
, поэтому A[2]
- это доступ к массиву вне границы.
Это указывает на две проблемы:
- наша выделенная память недостаточно велика и, возможно, никогда не будет, поскольку внутренний член в
a(m, a(m-1,n))
может вырасти намного больше, чем n
. , если бы у нас было решение для этого нам нужно сначала обработать тривиальные случаи, например,
for(int j = 0; j <= (n+1); ++j) {
A[0 + j] = j + 1; // set all A[i,j] where i = 0
}
Более глубокая проблема с алгоритмом
Однако есть еще одна более глубокая проблема. Ваш код подразумевает, что функция Аккермана может быть вычислена в θ (m * n). Это, однако, невозможно . Вместо этого вам нужен хотя бы стек или что-то подобное, которое может увеличиться в размере для вычисления результата. Эта реализация в Java дает некоторое вдохновение.