Data Structures, Algorithms, & Applications in Java
Chapter 8, Exercise 23

(a)
When a triadiagonal matrix is stored by rows in a one-dimensional array element, the mapping is element[0:3n-3] = [M(1,1), M(1,2), M(2,1), M(2,2), M(2,3), M(3,2), M(3,3), M(3,4), M(4,3), ...]. If |i-j| > 1, then M(i,j) is not on the tridiagonal. For elements in the tridiagonal, we see that the row 1 elements are stored in element[0] and element[1]. So, when i = 1, M(i,j) is at element[j-1]. When i > 1, there are i-1 rows that come before the first element of row i. These i-1 rows contain 3(i-1)-1 elements. Within row i, M(i,j) is the (j-i+2)th element. So, if i > 1, M(i,j) is at element[j+2i-3]. When i = 1, j+2i-3 = j-1. So, this formula may also be used for the case i = 1. Note also that the lower diagonal is stored in positions 2, 5, 8, ... of the one-dimensional array; the main diagonal occupies positions 0, 3, 6, ...; and the upper diagonal ocupies positions 1, 4, 7, .... With these observations in mind, we arrive at the following code:
public class TridiagonalByRows
{
   // data members
   int rows;            // matrix dimension
   Object zero;         // zero element
   Object [] element;   // element array

   // constructor
   public TridiagonalByRows(int theRows, Object theZero)
   {
      // validate theRows
      if (theRows < 1)
         throw new IllegalArgumentException
                   ("number of rows must be > 0");
   
      // create and initialize the matrix
      rows = theRows;
      zero = theZero;
      element = new Object [3 * rows - 2];
      for (int i = 0; i < 3 * rows - 2; i++)
         element[i] = zero;
   }
   
   // methods
   /** create string suitable for output */
   public String toString()
   {
      StringBuffer s = new StringBuffer(); 
   
      s.append("Lower diagonal is \n");
      for (int i = 2; i < 3 * rows - 2; i += 3)
         s.append(element[i] + "  ");
      s.append("\n");
   
      s.append("Main diagonal is \n");
      for (int i = 0; i < 3 * rows - 2; i += 3)
         s.append(element[i] + "  ");
      s.append("\n");
   
      s.append("Upper diagonal is \n");
      for (int i = 1; i < 3 * rows - 2; i += 3)
         s.append(element[i] + "  ");
      s.append("\n");
   
      // create equivalent String
      return new String(s);
   }
   
   /** input a tridiagonal matrix from the given input stream */
   public void input(Object theZero, MyInputStream inStream)
   {
      Method inputMethod;
      Object [] inputMethodArgs = {inStream};
      Class [] parameterTypes = {inStream.getClass()};
      zero = theZero;
      try
      {
         // get the proper method to be used to read in the values
         inputMethod = theZero.getClass().
                          getMethod("input", parameterTypes);
   
         // input number of rows in the matrix
         System.out.println("Enter number of rows");
         rows = inStream.readInteger();
         // validate input
         if (rows < 1)
            throw new IllegalArgumentException
                  ("number of rows must be > 0");
      
         // create the element array
         element = new Object [3 * rows - 2];
   
         // input elements
         System.out.println("Enter lower diagonal");
         for (int i = 2; i < 3 * rows - 2; i += 3)
            element[i] = inputMethod.invoke(null, inputMethodArgs);
   
         System.out.println("Enter main diagonal");
         for (int i = 0; i < 3 * rows - 2; i += 3)
            element[i] = inputMethod.invoke(null, inputMethodArgs);
   
         System.out.println("Enter upper diagonal");
         for (int i = 1; i < 3 * rows - 2; i += 3)
            element[i] = inputMethod.invoke(null, inputMethodArgs);
      }
      catch (Exception e)
      {
         System.out.println(e);
         throw new IllegalArgumentException
               ("matrix element type "
                + "must have the static method input() defined");
      }
   }
   
   /** throws IndexOutOfBoundsException when i < 1
     * or j < 1 or i > rows or j > rows */
   void checkIndex(int i, int j)
   {
      if (i < 1 || j < 1 || i > rows || j > rows)
         throw new IndexOutOfBoundsException
                   ("i = " + i + " j = " + j +
                    " rows = " + rows + " cols = " + rows);
   }
 
   /** return the element this(i,j)
     * throws IndexOutOfBoundsException when i or j invalid */
   public Object get(int i, int j)
   {
      checkIndex(i, j);

      // determine element to return
      switch (i - j)
      {
         case 1: case 0: case -1: // in tridiagonal
               return element[2 * i + j - 3];
         default: return zero;
      }
   }
   
   /** set this(i,j) = newValue
     * throws IndexOutOfBoundsException when i or j invalid */
   public void set(int i, int j, Object newValue)
   {
      checkIndex(i, j);

      // store newValue
      switch (i - j)
      {
         case 1: case 0: case -1: // in tridiagonal
               element[2 * i + j - 3] = newValue;
               break;
         default: if (!((Zero) newValue).equalsZero())
                      throw new IllegalArgumentException
                            ("elements not on tridiagonal must be zero");
      }
   }

   /** return this + b */
   public TridiagonalByRows add(TridiagonalByRows b)
   {
      if (rows != b.rows)
         throw new IllegalArgumentException
                   ("Matrices must have same dimensions");
   
      // create result array w
      TridiagonalByRows w = new TridiagonalByRows(rows, zero);
      for (int i = 0; i < 3 * rows - 2; i++)
          w.element[i] = ((Computable) element[i]).add(b.element[i]);
   
      return w;
   }
   
   /** return this - b */
   public TridiagonalByRows subtract(TridiagonalByRows b)
   {
      if (rows != b.rows)
         throw new IllegalArgumentException
                   ("Matrices must have same dimensions");
   
      // create result array w
      TridiagonalByRows w = new TridiagonalByRows(rows, zero);
      for (int i = 0; i < 3 * rows - 2; i++)
          w.element[i] = ((Computable) element[i]).subtract(b.element[i]);
   
      return w;
   }
   
   /** return the transpose of this */
   public TridiagonalByRows transpose()
   {
   
      // create result array w
      TridiagonalByRows w = new TridiagonalByRows(rows, zero);
   
      // copy lower diagonal of this to upper diagonal of w and copy
      // the upper diagonal of this to lower diagonal of w
      for (int i = 1; i < 3 * rows - 2; i += 3)
      {
          w.element[i] = element[i + 1];
          w.element[i + 1] = element[i];
       }

      // copy main diagonal of this to main diagonal of w
      for (int i = 0; i < 3 * rows - 2; i += 3)
          w.element[i] = element[i];
   
      return w;
   }
}



(b)
The test program, input, and output are in the files TridiagonalByRows.*.

(c)
The complexity of the store and retrieve methods is Theta(1). The complexity of the remaining methods is Theta(rows).