// $Id: bigO.cpp,v 1.2 2003/01/10 01:22:34 durant Exp durant $

// Dr. E. Durant
// Original Version: Thursday 9 January 2003

// Big-O Examples with Source Code

#include <iostream>
using std::cout;
using std::endl;
#include <vector>
using std::vector;
#include <algorithm>
using std::sort;

// Example 1a: Recursive Fibonacci - O(2^n) - exponential time
unsigned int FibonacciR(unsigned int n)
{
	if (n == 0 || n == 1)
		return 1; // O(base) == O(1)
	else
		return FibonacciR(n-1) + FibonacciR(n-2);
		// double number of calls for each increase of 1 in n!
		// O(2^n) * O(base)
}

// Example 1b: Linear Fibonacci - O(n)
unsigned int FibonacciL(unsigned int n)
{
	vector<unsigned int> fib;
	fib.push_back(1); fib.push_back(1);
	while (n > fib.size() - 1) // body increases size by 1, so O(n) * O(body)
		fib.push_back(fib.back() + fib[fib.size()-2]); // O(body) == O(1)
	return fib[n]; // O(1)
}

// Example 1c: Linear Fibonacci with Memory - O(n), but reduces to O(1) after first call with max(n)
unsigned int FibonacciM(unsigned int n)
{
	static vector<unsigned int> fib;
	if (fib.empty()) { fib.push_back(1); fib.push_back(1); }
	while (n > fib.size() - 1) // Skipped if size bigger, otherwise O(n) * O(body)
		fib.push_back(fib.back() + fib[fib.size()-2]); // O(body) == O(1)
	return fib[n]; // O(1)
}

// Example 2: Find in sorted array - O(log n)
int find_sorted(const vector<int>& v, int target)
// Returns: int:    index of first instance target; -1 if not found
// Inputs:  v:      vector to search
//          target: number to search for
{
	unsigned int start = 0, end = v.size(); // start to 1 past end, like STL iterators
	while (start != end)
	{
		unsigned int mid = (start + end) / 2; // truncates
		if (v[mid] == target)
			return static_cast<int>(mid); // found it
		// Otherwise, exclude mid and everything on the "other" side of it
		else if (v[mid] < target)
			start = mid + 1;
		else
			end = mid;
	}
	return -1; // not found
}
// Decreasing quantity: How about "q = end - start"?
// Use q both to prove convergence (Method 2) and find complexity (both Methods).
// Invariant: q >= 0
// Worst case: target not found and q goes to 0

// Method 1: q is cut roughly in half each time, so we have O(log n)
// Drawback: Not a mathematically rigorous proof, but okay for CS285.
// Need to be careful of rounding.  A simple "off-by-1" error could
// get the algorithm stuck at (end - start) = 1.

// Method 2: Consider all the rounding possibilities...
// q_new = (end - (mid + 1))               OR (start - mid)
//       = (end - ((start + end) / 2) + 1) OR (start - (start + end) / 2)
// "/" above is the the C++ integer division operator (truncates - rounds down).
// "/" below is real number division operator
// Case 1: (start + end) is even.  After a bit of algebra...
// q_new = (end - start) / 2 - 1           OR (end - start) / 2
//       = q / 2 - 1                       OR q / 2
// These are clearly smaller than the original q.
// Case 2: (start + end) is odd, so we have to subtract 1/2 from quotient for switching operators.
// q_new = (end - ((start + end) / 2 + 1) + 1/2 ) OR (start - ((start + end) / 2 + 1/2))
//       = (end - start - 2 + 1) / 2              OR (end - start - 1) / 2
//       = (q - 1) / 2                            OR (q - 1) / 2
// These are also clearly smaller than the original q.
// So, q_new = q/2-1 or q/2, where "/" is real number division, but, by definition
// q_new is an integer, so the update equation is
// q_new = floor(q/2) [real division] -- or, equivalently, q /= 2 [integer division]
// Based on the discussion of the log function from class, ceiling(log2(n)) 
// is the number of divisions required to reduce q to 0, so we have
// O(ceil(log2(n))) = O(log2(n)) = O(log n)

int main()
// Exercise the functions to show that they work (not a complete test)...
{
	vector<int> vi; // The vector to search
	vi.push_back(7);	vi.push_back(9);	vi.push_back(2);	vi.push_back(-8);
	vi.push_back(0);	vi.push_back(3);	vi.push_back(1);	vi.push_back(6);

	sort(vi.begin(), vi.end());

	vector<int> targets; // Some numbers to search for
	targets.push_back(2);	targets.push_back(-79);		targets.push_back(4);
	for(unsigned int uidx = 0; uidx < targets.size(); uidx++)
		cout << "Location of " << targets[uidx] << ": " << find_sorted(vi, targets[uidx]) << endl;

	for(int idx = 0; idx < 20; idx++)
		cout << "FibonacciR(" << idx << ") = " << FibonacciR(idx) << '\t'
			 << "FibonacciL(" << idx << ") = " << FibonacciL(idx) << '\t'
			 << "FibonacciM(" << idx << ") = " << FibonacciM(idx) << endl;

	return EXIT_SUCCESS;
}
