Algorithm to efficiently determine the [n][n] element in a matrix

前端 未结 6 443
半阙折子戏
半阙折子戏 2020-12-30 07:07

This is a question regarding a piece of coursework so would rather you didn\'t fully answer the question but rather give tips to improve the run time complexity of my curren

6条回答
  •  再見小時候
    2020-12-30 07:36

    This is another one of those questions where it's better to examine it, before diving in and writing code.

    The first thing i'd say you should do is look at a grid of the numbers, and to not represent them as decimals, but fractions instead.

    The first thing that should be obvious is that the total number of enter image description here you have is just a measure of the distance from the origin, enter image description here.

    If you look at a grid in this way, you can get all of the denominators:

    enter image description here

    Note that the first row and column are not all 1s - they've been chosen to follow the pattern, and the general formula which works for all of the other squares.

    The numerators are a little bit more tricky, but still doable. As with most problems like this, the answer is related to combinations, factorials, and then some more complicated things. Typical entries here include Catalan numbers, Stirling's numbers, Pascal's triangle, and you will nearly always see Hypergeometric functions used.

    Unless you do a lot of maths, it's unlikely you're familiar with all of these, and there is a hell of a lot of literature. So I have an easier way to find out the relations you need, which nearly always works. It goes like this:

    1. Write a naive, inefficient algorithm to get the sequence you want.
    2. Copy a reasonably large amount of the numbers into google.
    3. Hope that a result from the Online Encyclopedia of Integer Sequences pops up.

      3.b. If one doesn't, then look at some differences in your sequence, or some other sequence related to your data.

    4. Use the information you find to implement said sequence.

    So, following this logic, here are the numerators:

    enter image description here

    Now, unfortunately, googling those yielded nothing. However, there are a few things you can notice about them, the main being that the first row/column are just powers of 3, and that the second row/column are one less than powers of three. This kind boundary is exactly the same as Pascal's triangle, and a lot of related sequences.

    Here is the matrix of differences between the numerators and denominators:

    enter image description here

    Where we've decided that the f(0,0) element shall just follow the same pattern. These numbers already look much simpler. Also note though - rather interestingly, that these numbers follow the same rules as the initial numbers - except the that the first number is one (and they are offset by a column and a row). T(i,j) = T(i-1,j) + T(i,j-1) + 3*T(i-1,j-1):

                         1 
                      1     1
                   1     5     1
                1     9     9     1
             1     13    33    13    1
          1     17    73    73    17    1
       1     21    129   245   192   21    1
    1     25    201   593   593   201   25    1
    

    This looks more like the sequences you see a lot in combinatorics.

    If you google numbers from this matrix, you do get a hit.

    And then if you cut off the link to the raw data, you get sequence A081578, which is described as a "Pascal-(1,3,1) array", which exactly makes sense - if you rotate the matrix, so that the 0,0 element is at the top, and the elements form a triangle, then you take 1* the left element, 3* the above element, and 1* the right element.

    The question now is implementing the formulae used to generate the numbers.

    Unfortunately, this is often easier said than done. For example, the formula given on the page:

    T(n,k)=sum{j=0..n, C(k,j-k)*C(n+k-j,k)*3^(j-k)}

    is wrong, and it takes a fair bit of reading the paper (linked on the page) to work out the correct formula. The sections you want are proposition 26, corollary 28. The sequence is mentioned in Table 2 after proposition 13. Note that r=4

    The correct formula is given in proposition 26, but there is also a typo there :/. The k=0 in the sum should be a j=0:

    enter image description here

    Where T is the triangular matrix containing the coefficients.

    The OEIS page does give a couple of implementations to calculate the numbers, but neither of them are in java, and neither of them can be easily transcribed to java:

    There is a mathematica example:

    Table[ Hypergeometric2F1[-k, k-n, 1, 4], {n, 0, 10}, {k, 0, n}] // Flatten 
    

    which, as always, is ridiculously succinct. And there is also a Haskell version, which is equally terse:

    a081578 n k = a081578_tabl !! n !! k
    a081578_row n = a081578_tabl !! n
    a081578_tabl = map fst $ iterate
       (\(us, vs) -> (vs, zipWith (+) (map (* 3) ([0] ++ us ++ [0])) $
                          zipWith (+) ([0] ++ vs) (vs ++ [0]))) ([1], [1, 1])
    

    I know you're doing this in java, but i could not be bothered to transcribe my answer to java (sorry). Here's a python implementation:

    from __future__ import division
    import math
    
    #
    # Helper functions
    #
    
    def cache(function):
      cachedResults = {}
      def wrapper(*args):
        if args in cachedResults:
          return cachedResults[args]
        else:
          result = function(*args)
          cachedResults[args] = result
          return result
      return wrapper
    
    
    @cache
    def fact(n):
     return math.factorial(n)
    
    @cache
    def binomial(n,k):
      if n < k: return 0
      return fact(n) / ( fact(k) * fact(n-k) )
    
    
    
    
    
    def numerator(i,j):
      """
      Naive way to calculate numerator
      """
      if i == j == 0:
        return 0
      elif i == 0 or j == 0:
        return 3**(max(i,j)-1)
      else:
        return numerator(i-1,j) + numerator(i,j-1) + 3*numerator(i-1,j-1)
    
    def denominator(i,j):
      return 3**(i+j-1)
    
    
    
    def A081578(n,k):
      """
      http://oeis.org/A081578
      """
      total = 0
      for j in range(n-k+1):
        total += binomial(k, j) * binomial(n-k, j) * 4**(j)
      return int(total)
    
    def diff(i,j):
      """
      Difference between the numerator, and the denominator. 
      Answer will then be 1-diff/denom.
      """
      if i == j == 0:
        return 1/3
      elif i==0 or j==0:
        return 0
      else:
        return A081578(j+i-2,i-1)
    
    def answer(i,j):
      return 1 - diff(i,j) / denominator(i,j)
    
    
    
    
    # And a little bit at the end to demonstrate it works.
    N, M = 10,10
    
    for i in range(N):
      row = "%10.5f"*M % tuple([numerator(i,j)/denominator(i,j) for j in range(M)])
      print row
    
    print ""
    for i in range(N):
      row = "%10.5f"*M % tuple([answer(i,j) for j in range(M)])
      print row
    

    So, for a closed form:

    enter image description here

    Where the enter image description here are just binomial coefficients.

    Here's the result:

    enter image description here

    One final addition, if you are looking to do this for large numbers, then you're going to need to compute the binomial coefficients a different way, as you'll overflow the integers. Your answers are lal floating point though, and since you're apparently interested in large f(n) = T(n,n) then I guess you could use Stirling's approximation or something.

提交回复
热议问题