CSCI 256
Design and analysis of algorithms
Solution to Assignment 6

Due Monday, 3/12/2001


Only turn in problems from the second section.

Practice problems:

  1. Suppose that there are two different skylines: One is projected on a screen with a blue color, and the other is superimposed on the first one with a red color. Design an efficient algorithm to compute the shape that will be colored purple. In other words, compute the intersection of the two skylines.

    Solution: Scan across both from left to right keeping track of current height of intersection. When get a new x coordinate in either, take the minimum of the current height and encountered height as the new height. (Only difference between this and regular skyline problem is that here you take the minimum, while there you take the maximum.)

  2. Problem 16-2 on page 325 of the text. Assume no word is longer than will fit into a line.

    Solution: This is a bit long and tricky. Define

        extras[i,j] = M - j + i - (Sumk = 1 to j lk)
    to be the number of extra spaces at the end of a line containing words i through j. Note that extras may be negative.

    Define the cost of including a line containing words i through j in the sum we want to minimize:

        lc[i,j] = infinity, if extras[i,j] < 0 (the words don't fit)
                = 0, if j = n and extras[i,j] >= 0 (last line costs 0)
                = (extras[i,j])3, otherwise
    By making the line cost infinite when the words don't fit on the line we can ensure that this will not happen when we minimize the sum. By making the cost of the last line 0, we prevent a short last line from affecting the balancing of previous lines.

    We want to minimize the sum of lc over all lines of the paragraph. Let c[j] be the cost of an optimal arrangement of words 1 to j. Then the cost of the overall optimal arrangement is c[n]. Define c recursively as follows:

        c[0] = 0
        c[j] = min1 <= i <= j(c[i-1] + lc[i,j])
    That is, to arrange the first j words on lines, pick some i such that the words i to j will be on the last line. The resulting cost will be that of an optimal arrangement of the first i-1 words on earlier lines plus the line-cost for that line with words i to j. Clearly we want to pick the i that will minimize this.

    We can calculate these using dynamic programming, filling in the table from left to right, as each value depends only on the earlier values of c and the values of lc. As usual we can keep track of what words go on which lines by keeping a parallel array that keeps track of where each c value comes from (i.e., the i that gives the minimimum in the computation of c[j]).

    Space requirements for the algorithm are Theta(n) -- space for the table(s). One can get by with time Theta(nM) if you work hard. It looks as though n computations are needed to calculate the minimum in computing c[j], but we can use the fact that at most M/2 words will fit on a line to bound the computations for each c[j] to O(M). The computations of the lc also look expensive, but if you look for the min by starting with j and then working down to 1, you will discover that extras[i,j-1] can be computed from extras[i,j] in constant time, so we only need at most M computations, each of which takes a constant time, in order to compute a fixed c[j]. Since there are n of these, the total time is Theta(nM).

Problems to be turned in:

  1. Let x1, ..., xn be a sequence of positive real numbers. Design an O(n) algorithm to find the subsequence xi, ..., xj (of consecutive elements) such that the product of the numbers in it is maximum over all consecutive subsequences. The product of the empty sequence is 1.
    Hint: Think inductively, but it is not enough to just consider the maximum consecutive subsequence of the sequence of n-1 numbers in order to find the max for n. The new element can become part of the max subsequence if it contributes sufficiently to a product of some suffix of the sequence. Strengthen the inductive assumption of your algorithm by assuming you can both find the maximum consecutive subsequence and the maximum consecutive sequence that ends with the last element of the sequence (though of course, they might turn out to be the same!).

    Solution: We will define an algorithm inductively that will return the consecutive subsequence with maximum product (CSMPn) as well as the suffix with maximum product (SMPn) for a sequence of n numbers.
    Let n = 0 then the CMSP and SMP are both the empty sequence with product 1
    Let n > 0: Inductively find CMSPn-1 and SMPn-1. Compute p = xn*SMPn-1, and let CMSPn be the max of CMSPn-1 and p. Let SMPn be the max of p and 1.

    Show by induction that this always gives the CMSP and SMP and both are >= 1. When the sequence is empty, the CMSP and SMP are both 1 (for the empty sequence), as are, by definition, CMSP0 and SMP0.

    Suppose the CMSP and and SMP for a sequence of size n-1 are given by CMSPn-1 and SMPn-1, respectively, and both are >= 1. Show for a sequence of size n that they are given by CMSPn and SMPn and both are >= 1. The CMSP for the sequence of size n involves the nth element or it only involves elements from the first n-1 elements of the sequence. If it does involve the nth element, then the sequence must be a suffix of the sequence. By induction, the SMP of the sequence of size n-1 is SMPn-1 >= 1. Then CMSPn = SMPn-1*xn > CMSPn-1 >= 1. If it does not involve xn, then CMSPn = CMSPn-1 >= 1.

    Suppose SMPn-1*xn > 1, then SMPn is that product because by induction SMPn-1 >=1 and hence SMPn-1*xn >= xn. Otherwise 1 (the product of the empty sequence) is SMPn.

    It is easy to modify the solution to this problem to get the actual sequence rather than just the value. When you store the CMSP, also store the beginning and end of the sequence generating that string. Similarly, when you store the SMP, store the beginning position (the end is always the last element of the sequence).

  2. The following are a sequence of generalizations of the knapsack problem. Each successive one is slightly harder than the previous. You need only turn in the last one you succeed with.

    1. Solve the variation of the knapsack problem where there is an unlimited supply of each item. In other words, the problem is to pack items of given sizes in a given sized knapsack, but each item may appear many times.

      Solution: There are two ways to approach this. The first is to replicate objects. I.e., if the ith element has size si and the total weight is K then throw in m = floor(K/si) copies of item i (since can never use more than that number of objects.

      Alternatively, modify the algorithm so that P(j,k) = P(j-1,k) or P(j-1,k-sj) or P(j-1,k-2*sj) or ... or P(j-1,k-m*sj), where, as above, m = floor(K/si). For the base case, p(1,k) will be true only if k is a multiple of s1.

    2. Solve the variation of the above where each item has an associated value. E.g. a can of beans may have size 15 and value 3 while a steak may have the same size but value 10. Design an algorithm to find how to pack the knapsack fully, such that the items in it have the maximal value among all possible ways to pack the knapsack.

      Solution: In the earlier versions of the problem there might be several sets of objects filling the knapsack, but we didn't care which one we took. Now modify the algorithm so that when there are several ways of getting a weight, we take the one with the maximum value. In order to recover the actual number of each item taken in the final solution, we shall keep two parallel arrays. The first will record the maximum value P(j,k) available with size k and objects chosen from 1 to j, while the other, Q(j,k) will record the number of copies of item j included in attaining that maximum value. You fill in the tables by recording in P(j,k) the maximum of the P(j-1, k - l*sj)) + l*vj for 0 <= l <= k/sj (i.e., try to see which number of copies of element j gives the largest value). Q[j,k] will contain the l that attains that maximum. We'll set Q[j,k] = -1 if k isn't actually attainable using the first j objects (and we'll avoid using those values from the previous row when we compute P[j,k]).

      As usual, you can build the actual set giving value P(n,k) by writing the number l of copies of item n from Q[n,k], then getting the number of copies of item n-1 from Q[n-1,k-l*sn], etc.

    3. The Ultimate Knapsack (optional -- for extra credit): Like the previous version but now we are not restricted to filling the knapsack exactly to capacity. we are interested only in maximizing the total value, subject to the constraint that there is enough room for the chosen items in the knapsack.

      Solution: This is a simple generalization from the previous version. Compute everything as before. Only now sweep across the entire bottom row looking for the biggest value in P(n,m) with m <= k. Everything else is as before.

  3. Write a computer program to solve the most complex variation of knapsack you submitted to the previous problem. The input should begin with an integer k representing the capacity of the knapsack, a number n representing the number of items, then n lines, each with the size of an item. If you do the second or third variant above, you should follow that by n more lines, each with the value of an item. Thus if item i has size si and value vi, si will appear alone on the ith line after n, while vi will appear on the ith line of values. You should print out the actual solution (which items fit into the knapsack and the total value if you are doing the second or third variants above.

    Please follow the guidelines from homework three on how to turn in your program. We will expect a well-documented and well-structured solution that has complexity O(kn).

    Solution:

    /*
            Dynamic programming solution to knapsack with capacity and values 
            and weight to each item.
            Written by Kim Bruce, 3/3/01
    */
    import java.io.*;
    import java.awt.FileDialog;
    import java.awt.Frame;
    
    public class Knapsack {
    
       private int capacity;           // total size of items fitting in knapsack
       private int numItems;           // number of items to be considered
       private int[] size;                     // size or weight of each item
       private int[] value;            // value of each item
       private int[][] quantity;       // dynamic programming table
       private int[][] totalValue;     // Table of total values for choices
       
       // read in file with knapsack capacity, # of items, size of each and value of
       // each.  Print optimal solution with fixed capacity.
       public Knapsack()
       {
          String fname = getFileName();   // file with data
          if (fname != null)
          {
             BufferedReader in = makeBufferedReader(fname);  // open file
             if (in != null)
             try{    // read in all data from file
                capacity = Integer.parseInt(in.readLine());
                numItems = Integer.parseInt(in.readLine());
                size = new int[numItems];
                value = new int[numItems];
                for(int itemNo = 0; itemNo < numItems; itemNo++)
                {
                   size[itemNo] = Integer.parseInt(in.readLine());
                }
                for(int itemNo = 0; itemNo < numItems; itemNo++)
                {
                   value[itemNo] = Integer.parseInt(in.readLine());
                }
                System.out.println("This knapsack problem has capacity "+capacity +
                   " and "+numItems + " possible items.");
                System.out.println("There are as many copies of items as needed, each "+
                   "item has an associated value.");
                System.out.println("We wish to maximize the values of the "+
                   "items without necessarily filling the knapsack.");
                System.out.println();
                for(int itemNo = 0; itemNo < numItems; itemNo++)
                {       // print out all info for error checking
                   System.out.println("size["+itemNo+"] = " + size[itemNo] + 
                            ", value["+itemNo+"] = " + value[itemNo]);
                }
                computeTable();    // compute dynamic programming table
             } catch (IOException ex){System.out.println("Not an int");}
          }
       }
       
       // Compute dynamic programming table and print with solution
       private void computeTable()
       {
          quantity = new int[numItems][capacity+1];       // dynamic programming table
          totalValue = new int[numItems][capacity+1]; // values for item choices
          
          for (int col = 0; col <= capacity; col++)
          {
             int count = col / size[0];
             if (col % size[0] == 0)    // only mults of first size are non-zero
             {
                quantity[0][col] = count;                
                totalValue[0][col] = count*value[0];
             } else {
                quantity[0][col] = -1;  // not allowed
                totalValue[0][col] = 0;
             }       
          }
          
          for (int row = 1; row < numItems; row++)
          {       // calculate new values for each row
             quantity[row][0] = 0;
             totalValue[row][0] = 0;
             for (int col = 1; col <= capacity; col++)
             { 
                int count = 1;
                int offset = size[row];
                int goodCount = 0;
                int goodOffset = 0;
                while (offset <= col)
                {
                   if (quantity[row-1][col-offset] >= 0 && 
                      totalValue[row-1][col-offset] + count*value[row]
                          > totalValue[row-1][col-goodOffset] + goodCount*value[row])
                   {
                      goodCount = count;
                      goodOffset = offset;
                   }
                   offset = offset + size[row];
                   count++;
                }
                quantity[row][col] = goodCount;
                totalValue[row][col] = 
                      totalValue[row-1][col-goodOffset] + goodCount*value[row];
             }
          }
          printSoln(getBestCapacity());
          printTable();
       }       
       
       // Return the capacity that gives the highest value
       private int getBestCapacity()
       {
          int bestCapacity = 0;
          for (int testCapacity = 1; testCapacity <= capacity; testCapacity++)
          {
             if (totalValue[numItems-1][testCapacity] > totalValue[numItems-1][bestCapacity])
                bestCapacity = testCapacity;
          }
          return bestCapacity;
       }
       
       // Print the dynamic programming table
       private void printTable()
       {
          System.out.println();
          System.out.println("The final table of values is:");
          // print out totalValue table
          for (int row = 0; row < numItems; row++)
          {
             for (int col = 0; col <= capacity; col++)
                System.out.print(totalValue[row][col]+" ");
             System.out.println();
          }
       }
       
       // Print out information about solution with bestCapacity.
       private void printSoln(int bestCapacity)
       {
          System.out.println("\nThe best solution contains:");
          int col = bestCapacity;
          int row = numItems-1;
          while (row >= 0)      // Calculate which items in best solution
          {       
             int count = quantity[row][col];
             System.out.println("\t"+count+" copies of item "+row);
             col = col - count*size[row];
             row = row-1;
          }
          System.out.println("with total size of "+ bestCapacity +
             " out of a maximum capacity of "+ capacity +
             ", and a total value of "+ totalValue[numItems-1][bestCapacity]);
       }
       
       // Pop up dialog box to select file to be read
       private String getFileName()
       {
       // Create a file dialog asking the user to select a file
       // to save the Web page in
       FileDialog dialog = new FileDialog( new Frame(),
                       "Select file",
                       FileDialog.LOAD);
       dialog.show();
          
       // If the user made a selection, concatenate the folder and
       // filenames and return it.
       if ( dialog.getDirectory() != null )
       {
          return dialog.getDirectory() + dialog.getFile();
       } 
       else 
       {
          // The user hit cancel in the file dialog.  Return null.
          return null;
       }
       }
       
       // Return buffered reader to read file with name fileName
        private BufferedReader makeBufferedReader(String fileName) 
        {
       try{
          return new BufferedReader (new FileReader (fileName));
       } catch (FileNotFoundException e)
       {
          return null;
       }
        }
    
       // Get program started
       public static void main(String args[]) {
          new Knapsack();
       }
    }
    
    


Back to:

  • CS256 home page
  • Kim Bruce's home page
  • CS Department home page
  • kim@cs.williams.edu