Sonoma State University
 
Algorithm Analysis
Instructor: Henry M. Walker

Lecturer, Sonoma State University
Professor Emeritus of Computer Science and Mathematics, Grinnell College

Although CS 415 has been well developed for several years, last year the CS faculty made a significant, long-term curricular change regarding SSU's Upper Division GE Area B Requirement.

Assignment on Binary Exponentiation, Dynamic Programming, and Hashing

Binary Exponentiation

  1. For this problem, refer to Levitin, Section 6.5, for discussions of right-to-left and left-to-right binary exponentiation.

    1. How many multiplications are needed to compute a10, a12, a13, and a15 for left-to-right and right-to-left binary exponentiation? For example, is the number of multiplications the same in each case, are they all different, or are some the same and some different? Explain briefly.
    2. Apply the left-to-right binary exponentiation algorithm to compute a14, where a = 2.
    3. Apply the right-to-left binary exponentiation algorithm to compute a14, where a = 2.

    Notes: For Part b and c of this problem:

Dynamic Programming

  1. Recall that the Fibonacci sequence is defined recursively as follows: In practice, this sequence arises is a surprising number of applications!

    Program fibonacci.c defines two procedures to compute the nth Fibonacci number.

    1. Compile and run the program to clarify what it does. (In the interests of time, you may want to change the upper bound of the loop in the main procedure from 45 to 41.)
    2. In fibSeq2, why do you think fibSeq2 has only an int as parameter, whereas fibSeq2Helper has both an int and an array as parameters?
    3. Explain in a few sentences the role of the array arr.
    4. Why not declare and initialize the array in one step
          int arr [n+1] = {0};
          
    5. Write a sentence or two to explain the test
          if (fibArr[n] != 0)
          
    6. Explain why an assignment is included in the return statement
          return fibArr[n] = 1;
          

Finding a Pair of Numbers (in an Array) that Add to a Target Sum

This problem is based on the Stack Overflow blog, The complete beginners guide to dynamic programming, with a translation to C/C++ and some additional modifications.

Jargon

As you know, "dynamic programming" is commonly used to denote a programming practice of storing numbers the first time they are computed, and then refers to those results as needed later on, rather than recomputing expressions repeatedly.

Other terms, commonly used for the same approach, include "a memoized approach", "memoization" or "memoisation". According to Wikipedia, "In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls to pure functions and returning the cached result when the same inputs occur again."

Wikipedia also notes that "memoization" is derived from the word "memorandum" ("to be remembered"), which in American English is abbreviated "memo". This term "has a specialized meaning in computing", which is separate from the term, "memorization".

The Pair-Sum Problem

Given an array a of n [non-negative] integers, a[0], a[1], . . . a[n-1], and a target sum. Determine if there exist two of these numbers (a[i] and a[j] (with i ≠ j)), so that a[i] + a[j} = sum. If such numbers exist, print i and j; otherwise, print "none".

Brute Force Approach

A brute force approach checks all a[i] with all a[j] to determine if adding them together yields the target sum.

  1. Implement this brute force approach

    1. Write the function bruteForcePairSum, with the following specification
            /** ******************************************************
            * function to determine whether two numbers in an array  *
            * add to a specified target                              *
            *                                                        *
            * @param  target   the desired sum                       *
            * @param  n        the size of the array                 *
            * @param  a        the array of candidate numbers        *
            *                                                        *
            * @pre  all integers in a are non-negative               *
            *                                                        *
            * @post if some indices i and j exist with i != j        *
            *          so that a[i] + a[j] = target                  *
            *          then i and j are printed                      *
            *          only one (i, j) pair need be printed, even    *
            *              if many such pairs satisfy this condition *
            *       if no (i, j) pairs satisfy this condition        *
            *          the function prints "none"                    *
            *                                                        *
            **********************************************************/
            bruteForcePairSum (int target, int n, int a [ ])
              

      In the above reading, i and j both range between 0 and n-1.
      However, if a[i] + a[j] = target, then a[j] + a[i] = target, as well. Thus, in printing the pair (i, j), one can specify that i < j.
      For efficiency, your implementation of bruteForcePairSum should ensure that in the nested loops for i and j, j is always larger than i.

    2. Of course, once an i and j are found, so that a[j] + a[i] = target, the (i, j) pair can be printed and the function can return. However, processing must continue to check all relevant pairs, before the conclusion "none" can be determined.

    3. For testing, place bruteForcePairSum within a program that reads the size of the array and the array elements and then calls the function.

    4. The above reading indicates that this approach yields an O(n2) algorithm. Perform a careful microanalysis of your implementation of bruteForcePairSum in the worse case to prove that this algorithm indeed has O(n2).

    5. Will this same approach work if the array elements are allowed to be negative as well as non-negative? Explain.
      Will this same approach work if the target sum is allowed to be negative as well as non-negative? Explain.

  1. Continuing the previous problem and building on the above reading from StackOverflow, write a function dynamicPairSum also with the above function specification. However, for this implementation using dynamic programming, proceed as follows.

    1. For a given a target, consider creating an array sumMinusElt[target] initialized to 0.
    2. From the "memoized approach" in the StackOverflow reading, loop through each element a[i] of array a, and set sumMinusElt[target-a[i]] = i. (In this code, you may need to check that subscript target-a[i] does not go out of bounds.)

    3. After adding this code your your dynamicPairSum function, explain why this array takes the place of the diff variable in the StackOverflow pseudocode. (Hint: this approach provides a very fast way to access known differences target-a[i]—but why is that helpful?)

    4. Add one more (non-nested) loop through array a to dynamicPairSum, checking for each j whether sumMinusElt[a[j]] is non-zero. If so, print j and sumMinusElt[a[j]]. If all elements sumMinusElt[a[j]] are zero, print "none". (Again, in this code, you may need to check that subscript a[i] does not go out of bounds.)

    5. After adding this final loop to your dynamicPairSum function, explain why this approach effectively takes handles the rest of the code in the StackOverflow pseudocode.

    6. The above reading indicates that this approach yields an O(n) algorithm. Perform a careful microanalysis of your implementation of dynamicPairSum in the worse case to proved that this algorithm indeed has O(n).

    7. Add your dynamicPairSum function to the program in Step 3, and run the program with several different arrays and target sums.

    8. (Optional, for extra credit) To test both functions in a worst case, you might try setting array a to a few thousand even numbers (no reading of numbers---just generating the array in code) and then search for a target sum that is odd. Compare the approximate run times of the two algorithms (using a clock or mobile device for rough timing) in this case. (In my testing, I found noticeable differences, when n=100000.)

    9. Will this same approach work if the array elements are allowed to be negative as well as non-negative? Explain.
      Will this same approach work if the target sum is allowed to be negative as well as non-negative? Explain.

Note: To submit your work for Steps 3 and 4, include a listing of the program, the output obtained from several tests with different arrays and target sums, and your statements answering questions in steps 4c, 4e, and 4i.

Looking Ahead: The function dynamicPairSum keeps track of differences target-a[i] in a way that can be accessed and checked quickly, although this approach may require a large sumMinusElt array. Later in the semester, we will consider another way to store the same basic data but in a form that may be considerable more compact (called a hash table).

Hashing

A hash function returns integers between 0 and 15 based on the first letter of a data item according to the following table.

A B C D E F G H I J K L M
15 11 6 11 13 15 4 11 12 9 7 3 4
 
N O P Q R S T U V W X Y Z
11 5 13 6 12 15 7 3 9 10 13 10 4

For example, according to this hash function, the string TM would hash to the value 7 (based on the initial letter T; the hash function does not look at the second letter M).

  1. Consider the following sequence of 13 data items:

    LM  MA  OP  ZA  CR  GH  JK  YF  VT  IA  HI  NZ  DT

    Suppose the above sequence is to construct each of the following data structures 16 locations (labeled 0 through 15), based on the hash function given above.

    1. Closed, unbucketed hash table, using linear probing
    2. Closed, unbucketed hash table, using quadratic probing (use both -i2 and +i2)
    3. Open, bucketed hash table (with chaining)

    Show the resulting structure by filling in the following tables:
    (If collision resolution does not find a location for an element, explain the trouble, omit that element, and go on to insert any remaining elements.)

    Closed, unbucketed hash table
    using linear probing
    Closed, unbucketed hash table
    using quadratic probing
    Open, bucketed hash table (with chaining)
    0     
    1     
    2     
    3     
    4     
    5     
    6     
    7     
    8     
    9     
    10     
    11     
    12     
    13     
    14     
    15     
    0     
    1     
    2     
    3     
    4     
    5     
    6     
    7     
    8     
    9     
    10     
    11     
    12     
    13     
    14     
    15     
    0  
    1  
    2  
    3  
    4  
    5  
    6  
    7  
    8  
    9  
    10  
    11  
    12  
    13  
    14  
    15  
created December, 2021
revised December-January 2021
revised and expanded July 29, 2022
minor editing October 20, 2022
adjustments of problem 4 for C and C++ October 24, 2022
revised December 31, 2022–January 3, 2023
revised Summer, 2023
revised December 1-2, 2024
Valid HTML 4.01! Valid CSS!
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu.
ccbyncsa.png

Copyright © 2011-2025 by Henry M. Walker.
Selected materials copyright by Marge Coahran, Samuel A. Rebelsky, John David Stone, and Henry Walker and used by permission.
This page and other materials developed for this course are under development.
This and all laboratory exercises for this course are licensed under a Creative Commons Attribution-NonCommercial-Share Alike 4.0 International License.