Module 7: Supplemental Material
Ternary Search
Why ternary search?
  
  -  If dividing an array into two eliminates half the data
    
      ⇒
     dividing into three parts should eliminate two-thirds of the data.
   -  But there may be more overhead involved.
  
 
First, let's examine an implementation:
public class TernarySearch {
    static boolean ternarySearch (int[] A, int value, int start, int end)
    {
        if (start > end) {
            return false;
        }
        // First boundary: add 1/3 of length to start.
	int mid1 = start + (end-start) / 3;
        
        // Second boundary: add 2/3 of length to start.
	int mid2 = start + 2*(end-start) / 3;
	if (A[mid1] == value) {
	    return true;
	}
	else if (A[mid2] == value) {
	    return true;
	}
	else if (value < A[mid1]) {
	    // Search 1st third.
	    return ternarySearch (A, value, start, mid1-1);
	}
	else if (value > A[mid2]) {
	    // Search 3rd third.
	    return ternarySearch (A, value, mid2+1, end);
	}
	else {
	    // Middle third.
	    return ternarySearch (A, value, mid1,mid2);
	}
    }
}
Note:
  
  -  By increasing the number of comparisons slightly, we are able
    to eliminate two-thirds of the array at each recursive step.
  
 
 Exercise 1:
Binary search takes O(log2(n)) time.
We might guess that ternary search takes O(log3(n)) time.
Prove this by taking some large number n and repeatedly 
dividing by 3 until you can't divide anymore (i.e., the remainder is less than 3).
Next, use the fact that loga(n) = logb(n) /
logb(a) to evaluate log3(n) exactly
and compare.
 Exercise 2:
So, is ternary search faster? Download 
TernarySearch.java and compare with
Binary Search. All you have to do is compile and execute.
 Exercise 3:
Does it make sense to take this idea further to quartenary search?
Give an argument for why it does not make sense.
Mixing binary and ternary search:
  
  -  After recursing for a while, the array sizes start to get small
    
      ⇒
    The overhead for ternary search may not be worth it.
   -  One can switch to binary search in the middle of
  the recursion, when the sub-array size is small.
  
 
Here's how:
public class MixedSearch {
    static int CUT_OFF = 10;
    static boolean mixedSearch (int[] A, int value, int start, int end)
    {
        if (start > end) {
            return false;
        }
        // Switch to binary search if range is smaller than CUT_OFF.
        if (start-end < CUT_OFF) {
            return binarySearch (A, value, start, end);
        }
        
        // Otherwise, stay with ternary search.
        // First boundary: add 1/3 of length to start.
	int mid1 = start + (end-start) / 3;
        
        // Second boundary: add 2/3 of length to start.
	int mid2 = start + 2*(end-start) / 3;
	if (A[mid1] == value) {
	    return true;
	}
	else if (A[mid2] == value) {
	    return true;
	}
	else if (value < A[mid1]) {
	    // Search 1st third.
	    return ternarySearch (A, value, start, mid1-1);
	}
	else if (value > A[mid2]) {
	    // Search 3rd third.
	    return ternarySearch (A, value, mid2+1, end);
	}
	else {
	    // Middle third.
	    return ternarySearch (A, value, mid1,mid2);
	}
    }
}
 Exercise 4:
Download MixedSearch.java, compile
and execute to compare all three: binary, ternary and mixed search.
Experiment with different cut-off values to see what works best
for MixedSearch.
Array list revisited
Deletion in an array list:
  
  -  Recall: an array list is a list implemented with an array,
  which grows as large as necessary.
  
 -  To implement deletion in the same way:
    
      ⇒
    We should shrink the array after half the elements are deleted.
   -  We will consider an implementation for integers.
  
 -  We will implement two types of delete operations:
    
    -  delete(K): delete a specific integer in the list.
    
 -  deleteLast(): delete the last element in the list.
    
 
   
Here's the program:
public class OurArrayList2 {
    // This is the array in which we'll store the integers.
    Integer[] data = new Integer [1];
    // Initially, there are none.
    int numItems = 0;
    public void add (Integer K)
    {
        // ...
    }
    public Integer get (int i)
    {
        // ...
    }
    public boolean contains (Integer K)
    {
        // ...
    }
    public void delete (Integer K)
    {
	// First, find it.
	for (int i=0; i < numItems; i++) {
	    if (data[i] == K) {
		// Delete it and shift left all the items that come later.
		for (int j=i; j < numItems-1; j++) {
		    data[j] = data[j+1];
		}
		numItems --;
		break;
	    }
	}
        // Check if size needs to be reduced.
	if (numItems < data.length/2) {
	    // Reduce size.
            reduce ();
	}
    }
    void reduce ()
    {
        Integer[] data2 = new Integer [data.length/2];
        for (int i=0; i < numItems; i++) {
            data2[i] = data[i];
        }
        data = data2;
    }
    
    public void deleteLast ()
    {
        // All we need to do is change the current size.
        numItems --;
        
        // Check if size needs to be reduced.
	if (numItems < data.length/2) {
	    // Reduce size.
            reduce ();
	}
    }
}
Note:
  
  -  Although we could reduce the array size any time a deletion
  occurs, it makes sense to wait
    
      ⇒
    In our example we wait until the deleted portion is at least as
  large as the occupied portion (half).
   
 Exercise 5:
Consider an existing array list with n elements. Suppose
we repeatedly call deleteLast() n times.
Argue that the time taken is proportional to 
n/2 + n/4 + n/8 + ... + 1.
Write a small program to compute n/2 + n/4 + n/8 + ... + 1
for various values of n.
Can you simplify the sum and show that 
n/2 + n/4 + n/8 + ... + 1 = (1 - 1/n)?
© 2006-2020, Rahul Simha & James Taylor (revised 2020)