15110 Spring 2012 [Cortina/von Ronne]

Problem Set 2 - due Monday, June 18, in class

Reading Assignment

Read chapters 3-6 of the textbook Explorations in Computing.

Instructions

Exercises

  1. In class, we discussed the linear search algorithm, shown below in Ruby:

    def search(list, key)
            index = 0
            while index != list.length do
                    if list[index] == key then
                            return index
                    end
                    index = index + 1
            end
            return nil
    end
    

    Suppose that we know the additional fact that the list is sorted in decreasing order (this means that list[i] > list[i+1], for 0 ≤ i < list.length-1). For example, if our list has the values:

    [94, 82, 79, 73, 61, 45, 37, 25]
    

    then if we want to search for the key 70 using linear search, we can stop when we reach 61 and return nil (assuming that the list is sorted).

    1. Revise the method above so that it also returns nil immediately as soon as it can be determined that the key cannot be in the list assuming that the list is sorted in decreasing order.

    2. If the array has n elements, what is the number of elements that would be examined in the worst case for this revised method using big O notation? Why?

    3. In order to use your new method, you should probably have a method that allows you to check to make sure that the array is sorted in decreasing order before you use the search method. Write a Ruby function sorted? that returns true if an array called list is sorted in decreasing order or false if it is not.

      def sorted?(list)
      
      
      
      
      end
      

      HINT: Set up a for loop to compare list[i] with list[i+1]. If you ever get two neighboring elements that are not in decreasing order, then the whole list cannot be sorted. Be careful with the range you use for i.

    4. A loop invariant for this function is: list[0..index-1] does not contain the key. That is, at the end of each iteration, the key is not in positions 0 through index-1 in the list. Using the loop invariant, explain why the search function is always correct if it returns nil. (HINT: When the loop runs to completion, what is true besides the loop invariant?)

  2. If a list is sorted, we can search the list using another algorithm called Binary Search. The basic idea is to find the middle element, then if that is not the key, you search either the first half of the list or the second half of the list, depending on the half that could contain the key. The process is repeated iteratively until we either find the key or we run out of elements to examine.

    Here is an implementation of binary search in Ruby using iteration:

    def bsearch(list, key)
        min = 0
        max = list.length-1
        while min <= max do
            mid = (min+max)/2
            return mid if list[mid] == key
            if key > list[mid] then
                min = mid + 1
            else
                max = mid - 1
            end
        end
        return nil
    end
    

    Let list = [4, 10, 12, 16, 21, 24, 30, 33, 46, 58, 60, 72, 73, 85, 96].

    1. Trace the function above for the function call bsearch(list, 21), showing the values of min and max after each iteration of the while loop is completed. Also write down the value returned by the function. We have started the trace with the initial values of min and max.

       min     max
      --------------
         0      14
      
      

    2. Trace the function above for the function call bsearch(list, 85), showing the values of min and max after each iteration of the while loop is completed. Also write down the value returned by the function. We have started the trace with the initial values of min and max.

       min     max
      --------------
         0      14
      
      
      

    3. Trace the function above for the function call bsearch(list, 11), showing the values of min and max after each iteration of the while loop is completed. Also write down the value returned by the function. We have started the trace with the initial values of min and max.

       min     max
      --------------
         0      14
      
      
      

  3. Using the binary search function from the previous problem, answer the following questions clearly and concisely.

    1. If the list has an even number of elements, how does the function determine the location of the "middle element"? Give an example to illustrate your answer.

    2. If the list has 2N-1 elements, where N > 0, what is the maximum number of elements that will be compared to the key for equality? Give at least 2 examples to illustrate your answer. (HINT: The list in the previous problem has 15 = 24 - 1 elements.)

  4. Write a recursive Ruby function sum_positive(n) that has one parameter n that represents a positive integer. The function should return the sum of the first n positive integers. Your solution must use recursion and should not have a loop in it.

    For example, if we call sum_positive(5), then the function should return the value 15 since 1 + 2 + 3 + 4 + 5 = 15.

    HINT: The solution is similar to the factorial function we did in class.

  5. You are given the following recursive function in Ruby:

    def f(n)
    	if n == 0 or n == 1 or n == 2 then
    		return n
    	else
    		return f(n-3) + f(n/3)
    	end
    end
    

    Trace the computation of f(9) by drawing a recursion tree (like the one we drew for Fibonacci numbers), showing all of the recursive computations that need to be computed to find the value for f(9). Then using your recursion tree, compute the value of f(9).

  6. In Ruby, in addition to non-negative indices, you can use negative index values when accessing array elements. These count backwards from the end of an array with -1 being an index for the last element in an array. The range notation for obtaining sub-arrays can also use negative elements or a mix of positive and negative elements. For example:

    >> a = ["v", "w", "x", "y", "z"]
    => ["v", "w", "x", "y", "z"]
    >> a[-1]
    => "z"
    >> a[-2]
    => "y"
    >> a[-3]
    => "x"
    >> a[-3..-1]
    => ["x", "y", "z"]
    >> a[1..-3]
    => ["w", "x"]
    

    If we refer to integers whose 1's digit is 3 (3, 13, 23, 33, etc.) as "3-ending", then a recursive algorithm that takes a list of positive integers and counts the number of "3-ending" integers in that list can be described as follows:

    1. If the list is empty, then the number of "3-ending" integers is 0.
    2. Otherwise, if the last element of the list is not "3-ending", then the number of "3-ending" integers in the list is the number of "3-ending" integers in the list without the last element.
    3. Otherwise (the last element of the list must be "3-ending"), the number of "3-ending" integers in the list is 1 plus the number of "3-ending" integers in the list without the last element.

    Complete the Ruby function below to perform this computation recursively using the algorithm above:

    def count3ending(list)
    
    	if ________________________ then
    
    		return 0
    
    	end
    
    	if ____________________________________ then
    
    		return  ___________________________________
    
    	else
    
    		return 1 + ____________________________________
    	end
    end
    

    [Added 2/22] Hint: you may want to use negative indices to obtain an array with all of the elements of list except the last element.

  7. Consider the array [16, 26, 55, 13, 21, 26, 31, 44] which we want to sort using merge sort:

    Merge Sort Algorithm:
    1. Sort the first half of the array using merge sort.
    2. Sort the second half of the array using merge sort.
    3. Merge the two sorted halves to get a sorted array.

    1. Show the list after step 1 of the algorithm is completely done.

    2. Show the list after step 2 of the algorithm is completely done.

    3. Show the merge process of step 3 by tracing this step as shown in the course slides/notes.

    4. Explain why this algorithm is recursive. What is its base case?

  8. A music playing application stores a sequence of four-minute music files for playback. (All songs are the same length of time.) The music files can be stored in one of two ways:

    Algorithm 1: Songs are stored in the computer's memory in arbitrary order. Each song has a code that indicates the location in memory of the song that plays next. The player keeps track of the location of the first song in the playback sequence only.

    Algorithm 2: Songs are stored in the computer's memory in the order of playback starting at a specific fixed location in computer memory which cannot be changed.

    1. If the application user wants to skip to the 50th song in the playback list, which algorithm will allow the user to hear this song faster? Explain your answer by describing what each algorithm would do to accomplish this task.

    2. If the application user wants to insert a new song at the beginning of the playlist, which algorithm will allow the user to complete this operation faster? Explain your answer by describing what each algorithm would do to accomplish this task.

  9. An RPN expression can be stored in array as follows:

    rpn = [23, 3, "-", 4, 6, "+", "/"]
    

    Recall the algorithm to compute the value of a RPN expression using a stack:

    1. Set i equal to 0.
    2. Set x equal to rpn[i].
    3. Set s equal to an empty stack.
    4. While i is not equal to the length of the rpn array, do the following:
       a. If x is a number, then push x on stack s.
       b. If x is a string (i.e. operator), then do the following:
          i.   Pop stack s and store number in b.
          ii.  Pop stack s and store number in a.
          iii. If operator is "+", push a+b on stack s.
          iv.  If operator is "-", push a-b on stack s.
          v.   If operator is "*", push a*b on stack s.
          vi.  If operator is "/", push a/b on stack s.
       c. Add 1 to i.
    5. Pop stack s and return this number.
    

    Trace how this algorithm computes the value of the following RPN expression stored as an array:

    rpn = [7, 3, "+", 4, 2, "-", "*", 8, 2, "/", 1, "+", "/"]
    

    (Draw a new stack whenever a number is pushed or popped to show how the stack progresses throughout the computation.)

  10. A stack is a data structure with a LIFO (Last In First Out) property. That is, the last element you put in is the first one you can take out. Now consider a queue, a data structure with a FIFO (First In First Out) property. In this case, the first element you put in is the first one you can take out. With a queue, you enqueue (enter the queue) on to the rear of the queue, and you dequeue (depart the queue) from the front of the queue.

    Suppose we represent a queue using an array named q such that the first element in the array (at index 0) is the front of the queue and the last element in the array (at index q.length-1) is the rear of the queue.

    1. Show how to enqueue an element stored in the variable x on to the rear of the queue q using Ruby.

    2. Show how to dequeue an element from the front of the queue q and store this element in the variable y.

  11. A hash table has 13 buckets (i.e. its table size is 13). When keys are stored in the hash table, we use the following hash function:

    def h(string, table_size)
      k = 0
      for i in 0..string.length-1 do
        k = string[i] + k * 256
      end
      return k % table_size
    end
    

    In the function above, string[i] returns the ASCII code of the ith character of string. Here are the ASCII codes for the lowercase letters:

      a   b   c   d   e   f   g   h   i   j   k   l   m  
     97  98  99 100 101 102 103 104 105 106 107 108 109
    
      n   o   p   q   r   s   t   u   v   w   x   y   z
    110 111 112 113 114 115 116 117 118 119 120 121 122
    

    1. Given the hash function above, in which bucket would the following words be stored? Show all steps of the computation to show the bucket that is chosen. Do not write the final answer only.

      • cow

      • fox

      • dog

      • owl

    2. Do any of these words collide if we were to put them into a hash table of size 13 using the hash function above? Why or why not?

    3. If 3n words are put into a hash table with n buckets so that the number of words per bucket is 3, what is the order of complexity to search for a word in the hash table in big O notation? Briefly explain your answer.

  12. This problem deals with a binary tree known as a binary search tree.

    1. Insert the following integers into a binary search tree one at a time in the order given and show the final result:

      59 24 35 78 61 42 90 88 15 57

    2. Suppose you are now looking for the key 57 in your binary search tree. Which keys in the tree do you examine during your search? List the keys you have to examine in the order you examine them.

  13. This problem deals with a binary tree known as a max-heap.

    1. Insert the following integers into a max-heap one at a time in the order given and show the final result:

      66 22 81 23 50 73 39 33 42

      NOTE: For this problem, you will probably need to show the max-heap after each individual element is inserted so you don't get lost.

    2. For a max-heap of integers, where must the minimum integer be? Explain.