问题
I have been asked to write a recursive function that will calculate the Catalan number of monotonic lattice paths along the edges of a grid with n × n square cells, which do not pass above the diagonal (pic)
I was not allowed to use for-loops, only recursive calls... This is what I did:
public long C(int n) {
if (n == 1)
return 0;
return C(n, 0, 0);
}
private long C(int n, int i, int j) {
// CAN MOVE UP & RIGHT
if (j - i > 0 && j + 1 <= n)
return paths(n, i + 1, j) + paths(n, i, j + 1);
// CAN MOVE UP
else if (j - i > 0)
return paths(n, i + 1, j);
// CAN MOVE RIGHT
else if (j + 1 <= n)
return paths(n, i, j + 1);
// CAN'T MOVE
else
return 1;
}
I don't know if this code is the best but it works... I want to convert this function to be a Memoized function. But I can't understand how & also why it would make the function more efficient. I understand why Fibonnaci in memoized is more efficient, but here I will always have to get to the end of the path and then return 1 or 0 so what does it matter if I store 1 / 0 inside an array for say?
Thanks for any kind of help
回答1:
It seems like you know what Memoization is. Basically, all you do is create a table memo
which stores a value once you reach it so you don't have to calculate it in the recursion again. Something similar to why fibonacci(5)
won't have to go into recursion to find fibonacci(3)
, if we have already calculated, say fibonacci(6)
, because we have memoized it. I hope you get it. Here is the code, memoized in the same spirit. Andrea's question has great visuals to understand.
long[][]memo; //Memo table
public long C(int n)
{
if (n == 1)
return 0;
memo=new int[n+1][n+1]; //Increase to n+1 and won't crash!
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
memo[j][i]=-1;
return C(n, 0, 0, memo);
}
private long C(int n, int i, int j, it) {
// CAN MOVE UP & RIGHT
if (j - i > 0 && j + 1 <= n)
{
if(memo[i+1][j]==-1)
memo[i+1][j]=paths(n, i + 1, j);
if(memo[i][j+1]==-1)
memo[i][j+1]=paths(n, i, j + 1);
return memo[i+1][j]+memo[i][j+1];
}
// CAN MOVE UP
else if (j - i > 0)
{
if(memo[i+1][j]==-1)
memo[i+1][j]=paths(n, i + 1, j);
return memo[i+1][j];
}
// CAN MOVE RIGHT
else if (j + 1 <= n)
{
if(memo[i][j+1]==-1)
memo[i][j+1]=paths(n, i, j + 1);
return memo[i][j+1];
}
// CAN'T MOVE
else
return 1;
}
回答2:
But I can't understand [...] why it would make the function more efficient.
Looking at the image, numbering the pictures starting with 1, and (x,y)
coordinates with (0,0)
in lower left corner, you can see that pictures 2,3,4, 5,6,7,8,10, 12, are all going through the point at (3,1)
.
Paths from (3,1)
:
(3,1) → (4,1) ↑ (4,2) ↑ (4,3) ↑ (4,4)
- Picture 2, 4, 5
(3,1) ↑ (3,2) → (4,2) ↑ (4,3) ↑ (4,4)
- Picture 3, 6, 8
(3,1) ↑ (3,2) ↑ (3,3) → (4,3) ↑ (4,4)
- Picture 7, 10, 12
As you can see, you're walking the same path multiple (3) times. If you could cache (memoize) the fact that there are 3 paths from (3,1)
to the end, you're saving time.
Time savings will grow as the grid gets bigger.
So, what you do, is that first time you get to a point, you calculate the result using recursion, as you already do, then save the number for that point, and when getting to the point again, you just use the cached value:
public static long paths(int n) {
if (n == 1)
return 0;
return paths(n, 0, 0, new long[n][n]);
}
private static long paths(int n, int y, int x, long[][] cache) {
long result = cache[y][x];
if (result == 0) {
if (y < x && x < n) // CAN MOVE UP & RIGHT
result = paths(n, y + 1, x, cache) + paths(n, y, x + 1, cache);
else if (y < x) // CAN MOVE UP
result = paths(n, y + 1, x, cache);
else if (x < n) // CAN MOVE RIGHT
result = paths(n, y, x + 1, cache);
else // CAN'T MOVE
result = 1;
cache[y][x] = result;
}
return result;
}
来源:https://stackoverflow.com/questions/34244047/recursion-for-catalan-number-to-memoized