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)