/////// init /////////
int a[n]; 
for ( int i=0; i<n; ++i ) { 
    a[i] = i+1;
}
//n assignments 
//n comparisons
//n increments
//n additions
n*T(as)+n*T(co)+n*T(in)+n*T(ad)

// f(n) = Cn = O(n)









/////// find 
int n = 0;
while (x != a[n]) {
    ++n;
}
// 1...n comparisons
// best, average, worst case scenarios
//                      if val is IN array
//                      otherwise

// 1 if a[0] == x
// n if a[size-1] == x, or x is not in the array



/////// binary search
int a[n] = { .... }; // sorted

int end   = n;
int begin = 0;

while ( end-begin > 0 ) {
    if      ( a[ (begin + end) /2 ] > val ) { end   = (begin + end) /2; }
    else if ( a[ (begin + end) /2 ] < val ) { begin = (begin + end) /2; }
    else {
        return (begin + end) /2; // found 
    }
}
return n; // not found

// Analysis:
// 2 ... 2*log_2 m comparisons

// size is
// n -> n/2 -> n/2^2 -> ... -> n/2^k ->   .... -> 1=n/2^m
// 0     1       2               k
// 2^m=n
// log_2 (2^m) = log_2 (n)
// log_2 ( exp_2 (m) ) = log_2 (n)
// m = log_2 (n)


// Algorithm O( N )
// N=10  - 1s
// N=100 - ?
// 
// Algorithm O( N^2 )
// N=10  - 1s
// N=20  - ?
// N=100 - ?
// N=1000 - ?
// Proof:
// Assume 
// O(N^2) = K*N^2 
// 
// K*10^2 = 1 - given
// 
// K*20^2 = K*(10*2)^2 = K*10^2*2^2 = 1sec * 2^2
// 
// Algorithm O( N^3 )
// N=10  - 1s
// N=20  - 8 sec
// N=100 - 1000 sec
// N=1000 - (100)^3 sec = 1,000,000 s = 16666.666 min = 277.778 h = 11.57 days
// Proof:
// O(N^3) = K*N^3
// K*10^3 = 1
// K*20^3 = K*(10*2)^3 = K*10^3*2^3 = 1sec * 2^3 = 8 sec


for (int i = 1; i <= n; ++i) {      // n iterations
  for (int j = 1; j <= n; ++j) {    // n iterations
    a[i - 1][j - 1] = (i / j) * (j / i); // n^2 multiplications (assignments)
  }
}
// N*N

for (int i = 0; i < N; ++i) {   // N iterations
  for (int j = i; j < N; ++j) { //  N, N-1 ,..., 1
      a[i][j] = (i == j) ? 1:0; 
  }
}

// N + N-1 + ... + 1 =                  N(N+1) 
// 1 + 2 + 3 + ... + N = (N+1) * N/2 = ------  = N^2/2 + N/2 = O(N^2) 
//                                        2


// duplicates
int a[] = { ... }; // unsorted array of integers
for (int i = 0; i < n; ++i) {   // n iterations
  for (int j = 0; j < n; ++j) { // n iterations
    if ( i != j && a[i] == a[j] ) return true;
  }
}
return false;
// best case 1
// worst case n^2
// average: ?????


// printing dots (N,N/2,N/4,....,4,2,1 dots)
// ........
// ....
// ..
// . 
int N = ...;
while( N>=1 ) {
    for (int i = 0; i < N; ++i) {
        std::cout << ".";
    }
    std::cout << std::endl;
}
// N + N/2 + N/4 + .... + 4 + 2 + 1
// Assume N = 2^n, then we need to calculate 
// 2^0 + 2^1 + 2^2 + ... + 2^(n-1) + 2^n =
// this is geometric progression                      n+1
//                                                   b    - 1
// FORMULA: b^0 + b^1 + b^2 + ... + b^(n-1) + b^n = ---------
//                                                    b - 1
// if b=2:
// 2^0 + 2^1 + 2^2 + ... + 2^(n-1) + 2^n = 2^(n+1) - 1
// since 2^(n+1) = 2 * 2^n = 2N
// N + N/2 + N/4 + .... + 4 + 2 + 1 = 2N -1 
// Run time is linear: O(N)


// find a value in an unsorted array M times, assume array size is N
// using brute-force find, we get M*N
//
// or first sort array and then use binary search:
// N*log(N) + M*log(N) = (M+N)*log(N)
//
// notice that comparison of M*N and (M+N)*log(N) depends on values of N and N
// M is much smaller than N:
//      M*N          = O(N)          smaller
//      (M+N)*log(N) = O( N*log(N) )  
// M is comparable to N:
//      M*N          = O(N^2)          
//      (M+N)*log(N) = O( N*log(N) )  smaller