Chapter 1 Introduction Objectives Consistency and importance of

Chapter (1) – Introduction Objectives • Consistency and importance of the performance of a program for inputs of Different sizes. • Basic mathematical background. • Review Recursion. • Review some important features of C++. ___________________________ 1

How important is to know which algorithm is better for solving a problem? How do we determine which method algorithm is better? Example (1): Suppose we are given a group on n numbers, how do pick the kth largest? For demonstration purposes let’s use the set of n=10 numbers {12, 34, 5, 20, 11, 32, 14, 8, 17, 13} and find the 4 th largest number among them. Method 1: Sort the numbers and save them in an array, a[ ], of size n: A[0]= 34, A[1]=32, A[2]=20, A[3]=17, A[4]=14, A[5]=13, A[6]=12, A[7]=11, A[8]=8, A[9]=5. 2

Example (1) - Method 2: Step 1: Take the first 4 numbers and sort them in an array of size 4: A[0]=34, A[1]=20, A[2]=12, A[3]=5. Step 2: Read the rest of numbers one by one and compare them with the last number in the array A. If the number is smaller than the last element of the array A, ignore it and get the next number and repeat step 2. If the number is larger than the last member, A then insert it in proper place in the array and take the last element out. Reading 11, 11 > 5, 11 should go after 12, and 5 should be deleted, now the sequence looks like this: A[0]=34, A[1]=20, A[2]=12, A[3]=11. 3

Reading 32, 32>11, 32 should go after 34, and 11 should be deleted, thus we will have: A[0]=34, A[1]=32, A[2]=20, A[3]=12. Reading 14, 14>12, 14 should go after 20, and 12 should be deleted, thus we will have: A[0]=34, A[1]=32, A[2]=20, A[3]=14. Reading 8, 8<14, ignore it and get the next number. Reading 17, 17>14, 17 should go after 20, and 14 should be deleted, thus we will have: A[0]=34, A[1]=32, A[2]=20, A[3]=17. Reading 13, 13<17, ignore it. No more number left, the 4 th largest element is in A[3], i. e. ; A[0]=34, A[1]=32, A[2]=20, A[3]=17. 4

Which method do you prefer to use? Both algorithms are simple to implement. But when n is very large and k is also a large number, none of these two algorithms is efficient, i. e. , They do not finish in a reasonable amount of time. Example (2): Word puzzle. Find the words in the puzzle. These words may be horizontal, vertical, or diagonal. 1 2 3 4 1 T W O F 2 H A A G 3 I T H D 4 S S G T 5

Method (1) : Take from the list of words one word at a time and check the strings in the table in all possible combinations (rows, columns, diagonals)to see if that word exists in the table. Method (2): Take all possible combinations of letters from the table (rows, columns, diagonals, number of characters) one by one, and compare the string with the words in the list of words to see if that string exists as a word. Once again, both algorithms are relatively easy to implement, but they both suffer from the same problem as the one mentioned in the previous example. They do not finish in reasonable amount of time. 6

Things to remember before we move on: Writing a working program is not good enough. Writing a program that runs efficiently is the very important. How do we compare the running time of different algorithms without writing the codes for them? In this course we learn some of the techniques: to improve the running time, and to determine the bottlenecks in a code. In the following sections, we review some of the mathematical concepts that we need for this purpose. 7

Mathematical Review: Exponents: Logarithms: Note: In computer science, all logarithms are in base 2, unless specified otherwise. Definition: If and only if (iff)logxb=a. (1) log 10100=2, i. e. 102 =100 8

THEOREM 1. 1: PROOF: Let x=logcb, y=logca, and z=logab. Using definition (1), we can write: cx = b, cy=a, and az=b. Using these we can write: az= (cy)z= cx or cyz= cx which means : x = yz. By replacing x, y, and z with their logarithmic equivalent we will get : logcb = logca. logab. And from this we can find: 9

THEOREM 1. 2: logcab = logca + logcb PROOF: Let x=logcb, y=logca, and z=logcab. Using definition (1), we can write: cx = b, cy=a, and cz=ab. Using these we can write: cz= ab=cy. cx or cz= cy+x which means : z = x+y. By replacing x, y, and z with their logarithmic equivalent we will get: logcab = logca + logab. 10

Can you show that: 1) logc(a/b) = logca – logcb, 2) logc(ab) = blogca, 3) logx < x for all x > 0, and 4) log 1= 0, log 2= 1, log 1, 024= 10, log 1, 048, 576 = 20 ? Hints: • Do the same thing as we did for Theorem 1. 2. • Write ab as a. a. a…. a and use Theorem 1. 2. • Look at the definition of logarithm … • Write the numbers in power of 2 and use (2). 11

Series: Formulas to remember: Note: See how a number between 0 and 1 behaves at the higher powers. One can see that if n ॠthen the sum will approach 1/(1 a). These are the “geometric formula”. We can prove the last one: Let S = 1 + a 2 + a 3 + … Then a. S = a + a 2 + a 3 + …, and S – a. S = 1, which implies that: 12

Use the technique in the previous page to compute: We can write: We multiply S by 2 to get: And of course 2 S – S = S, thus, And this approaches to 2 for large number of terms, which means that: S=2. 13

Arithmetic Series: Basic formula: Example: 2+5+8+…+(3 k-1) = ? This can be written as; (3 -1) + (6 -1) + (9 -1) + … + (3 k-1) = (3. 1 -1)+(3. 2 -1)+(3. 3 -1)+…+(3 k-1)= (3. 1)+(3. 2)+(3. 3)+…(3. k)-(1+1+1…+1)= 3(1+2+3+…+k) – (1+1+1+…+1)=(3 k(k+1)/2)-k = k(3 k+1)/2. Or we could do it differently as: 2 3 8 …. (3 k-2) (3 k-1) + (3 k-1) 3 k-2 3 k-3 …. 3 2 --------------------------3 k+1 …. 3 k+1 = k(3 k+1) But this is twice as many terms as we need, so we will divide it 14 by 2 to get: k(3 k+1)/2.

Modular Arithmetic We say that a is congruent to b modulo n, written a b (mod n), iff n divides a - b. What does this mean ? It means that the remainder is the same when either a or b is divided by n. Example: 81 61 1 (mod 10). Rule : if a b (mod n), then a + c b + c (mod n), and ad bd (mod n). The P Word In data structure analysis are proof by induction and proof by contradiction. 15

Proof by Induction (PBI) has two standard parts: 1 st) providing a base case, that is, establishing that theorem is true for some small value(s). This step is almost always trivial. 2 nd) assuming an inductive hypothesis, That is we assume that theorem is true for all cases up to some limit k. Using this assumption, theorem is then shown to be true for the next value, k+1. Note that k is finite. 16

Example (1) : Prove that the Fibonacci series, F 0 = 1, F 1 = 1, F 2 = 2, F 3 = 3, F 4 = 5, …, Fi = Fi-1 + Fi-2, satisfy : Fi < (5/3)i, for i > 1. 1 st) Base case: F 1 = 1 < (5/3) and F 2 = 2 < (5/3)2 = (25/9). 2 nd) Inductive hypothesis: We assume that theorem is true for i = 1, 2, …, k. Thus, Fk-1<(5/3)k-1 and Fk<(5/3)k are also true. We want to prove that: Fk+1 < (5/3)k+1. From the definition of the Fibonacci series we have: Fk+1 = Fk + Fk-1. From the Inductive hypothesis we have: Fk+1 = Fk-1 + Fk < (5/3)k-1 + (5/3)k < (3/5)(5/3)k+1 + (3/5)2(5/3)k+1 <[(3/5) + (9/25)](5/3)k+1 <(24/25) (5/3)k+1 since (24/25) < 1, then 17 k+1 < (5/3).

Proof by Induction Example (2) : THEOREM: PROOF: 1 st) Base case: F 1 = 12 = [1(1+1)(2. 1+1)]/6 = 6/6. 2 nd) Inductive hypothesis: We assume that theorem is true for Thus, is also true. We will prove that: 18

We can write the sum as: 19

Proof by Counterexample In this case we bring an example that is against the assumption. Example: Prove that Fk > k 2 is false. The easiest way to prove this is to bring a counter example. F 11 = 144 > 112. 20

Proof by Contradiction In this case we assume that theorem is false and then we show that this assumption implies that some known property(ies) is false, hence the original assumption was erroneous. Example: Proof that there is an infinite number of primes. 1 st) We assume that theorem is false, so that there is some largest prime pk. Now we write the serious of prime numbers from p 1 to pk in order as; p 1, p 2, p 3, …, pk. Now consider a number N = p 1 p 2 p 3 …pk + 1, Clearly N is larger than pk so by assumption N is not a prime. However, none of p 1, p 2, p 3, …, pk divide N exactly, because there is always a remainder of 1. This is a contradiction, since every number is either a prime or a product of primes. Thus, the assumption that pk is the largest prime is false, which implies that theorem is true. 21

Introduction to Recursion Most mathematical functions are described in simple formula and can be easily translated to their C++ equivalents. Example: Formula for converting degree Fahrenheit to Celsius: C = 5(F-32)/9. In some cases, a function is defined in terms of itself: Example: f(x) = 2 f(x-1) + x 2. Looking at this statement, it is evident that we cannot start our calculations of f(x) unless we know f(x-1). Also, we need to know the value of f at some starting point to find the f at the next point. Assume that f(0) = 0, then we can compute: f(1) = 2 f(0) + 12 = 1, f(2) = 2 f(1) + 22 = 6, f(3) = 2 f(2) + 32 = 21, …. 22

/* 1 */ int /* 2 */ F( const int X) /* 3 */ { /* 4 */ if( X = 0 ) /* 5 */ /* 6 */ /* 7 */ return 0; else return 2 * F( X - 1) + X * X; /* 8 */ } 23

Common Errors in Recursive Calls There are two common errors that usually are made: 1) The base is defined such that it will not be reached. Example: If we want to compute f(-7) in the previous example we never get to the base that is defined at x = 0, i. e, we never get to f(0). Here is why; f(-7) = 2*f(-8) + (-7)2. As you can see to compute f(-7) we need f(-8) and to find f(-8) we will need f(-9), and so on. We will never use the base f(0) = 0 and the answer will never be determined. 24

2) The base in not defined. Example: /* 1 */ int /* 2 */ Bad( const int N ) /* 3 */ { /* 4 */ if( N == 0) /* 5 */ /* 6 */ /* 7 */ return 0; else return Bad( N/3 + 1) + N -1; /* 8 */ } We make call to Bad(1) but Bad(0) will never be reached. We get 25 stock in Bad(1). Thus the program never resolves.

Example: Print out integer numbers assuming the I/O routine takes single-digit numbers and output it to the terminal. For example for 76234 it takes 4, leave 7623 out. Then takes 3, leave 762 out. It continues until it gets to 7 which will the base case. /* 1 */ void /* 2 */ Print_Out( const unsigned int N ) /* 3 */ { /* 4 */ if ( N < 10 ) /* 5 */ Print_Digit( N ); /* 6 */ else /* 7 */ { /* 8 */ Print_Out( N/10 ); /* 9 */ Print_Digit( N%10); /* 10 */ } /* 11 */ } 26

We wanted to print a number 76234. We need to print: 7623 then 4 To print 4 we use Print_Digit(N%10). To print 7623 we use Print_Out(N/10). This will work fine if the base is defined properly. To stop the program we just need to distinguish the numbers less than 10. Using induction can you prove that the above program works? 27

Four Basic Rules to Remember When Writing a Recursive Program: • Base Cases : You must always have some base cases, which can be solved without recursion. You have to tell the program to stop somewhere. • Making Progress: For the cases that are to be solved recursively, the recursive call must always be to a case that makes progress toward a base case. The program should continuously obtain new values. • Design Rule: Assume that all the recursive calls work. All recursive calls must work. • Compound Interest Rule: Never duplicate work by solving the same instance of a problem in separate recursive calls. Do not use recursion to evaluate simple mathematical functions. 28

C++ at a Glance C++ is a strongly typed language. Thus, every object must belong to a type. An object of one type cannot assigned to or compared with one another type except by a well-defined type conversion. • Call by reference is supported in C++. This removes some of the difficult pointer code. A direct result is that any large structure should be passed by reference. • The compiler directive const can be used in many places to imply that some object is unalterable. • Large structures can safely be passed as const and by reference, rather than using a pointer, 29

• Integers that are passed by value can be specified as cosnt to prevent accidental modification. • The use of #define for symbolic constants is considered bad practice in C++. Instead const is used for the same purpose. • Preprocessor macros are obsolete in C++, because inline functions are supported. • I/O has been revamped in C++ to use streams. Thus, it is not a good idea to use printf and scanf. • Malloc and free have been replaced by new and delete. These are syntactic improvements to C. 30

Classes. Overloading Templates Inheritance Example: A class that supports string operations. In C: we use strcmp and strcpy defined in <string. h> to do string operations. These operate on pointers to characters. These routines are available in C++. Limitations: • Copying and comparing strings looks different from copying and comparing integers. Thus, “=“ and “= =“ means different. If applied on strings, it results to errors. This can be fixed by overloading the comparison and assignment operators. • Storage issue. When a string is dynamically allocated the user has to remember to free the memory when the string is no longer needed. • Error handling. If the target of strcpy is not pointing to a sufficiently large array, disaster will happen. An array index out of bounds will 31 corrupt another variable.

Example: Lack of safety. char *S = strdup(“Hello”); We can modify this S currently holding word “Hello”: S[0] = ‘J’; Or in a very different way; char *T = S; T[0] = ‘M’; This alters S without S knowing about it. How ? 32

#include <iostream. h> /** * A class for simulating an integer memory cell. */ class Int. Cell { public: /** * Construct the Int. Cell. * Initial value is 0. */ Int. Cell( ) { stored. Value = 0; } /** * Construct the Int. Cell. * Initial value is initial. Value. */ Int. Cell( int initial. Value ) { stored. Value = initial. Value; } int read( ) { return stored. Value; } 1. 5 /** * Change the stored value to x. */ void write( int x ) { stored. Value = x; } private: int stored. Value; }; int main( ) { Int. Cell m; m. write( 5 ); /** * Return the stored value. */ cout << "Cell contents: " << m. read( ) << endl; return 0; } 33

#include <iostream. h> 1. 6 /** * A class for simulating an integer memory cell. */ class Int. Cell { public: /* 1*/ explicit Int. Cell( int initial. Value = 0 ) /* 2*/ : stored. Value( initial. Value ) { } /* 3*/ int read( ) const /* 4*/ { return stored. Value; } /* 5*/ void write( int x ) /* 6*/ { stored. Value = x; } private: /* 7*/ int stored. Value; }; int main( ) { Int. Cell m; m. write( 5 ); cout << "Cell contents: " << m. read( ) << endl; return 0; } 34

#ifndef Int. Cell_H_ #define Int. Cell_H_ Int. Cell. h /** * A class for simulating an integer memory cell. */ class Int. Cell { public: explicit Int. Cell( int initial. Value = 0 ); int read( ) const; void write( int x ); private: int stored. Value; }; #endif 35

#include "Int. Cell. h" Int. Cell. cpp /** * Construct the Int. Cell with initial. Value */ Int. Cell: : Int. Cell( int initial. Value ) : stored. Value( initial. Value ) { /** } /** * Return the stored value. */ int Int. Cell: : read( ) const { return stored. Value; } * Store x. */ void Int. Cell: : write( int x ) { stored. Value = x; } 36

#include "Int. Cell. h" #include <iostream> using namespace std; Int. Cell. Main. cpp int main() { Int. Cell m; //Or, Int. Cell m (0); but not Int. Cell m(); m. write( 5 ); cout << "Cell contents: " << m. read() << endl; return 0; } 37

Pointers #include <iostream> #include "Int. Cell. h" Int. Cell. Main. cpp int main( ) { /* 1*/ Int. Cell *m; /* 2*/ /* 3*/ /* 4*/ m = new Int. Cell( 0 ); m->write( 5 ); cout << "Cell contents: " << m->read( ) << endl; /* 5*/ /* 6*/ } delete m; return 0; 38

Parameter Passing • Call by value – void My. Function(int x) • Call by constant reference – void My. Function(const int x) • Call by reference – void My. Function(int& x) void My. Function(int x, int& y, const int z); int main( ) { int x = 8, y = 20, z = 100; What would be the output of this program? My. Function(x, y, z); cout << “x = “ << x << “ , y = “ << y << “ , z = “ << z << endl; return 0; } void My. Function(int x, int& y, const int z) { x = y = z; } 39

Return Passing Find the maximum string const string & find. Max( const vector<string> & a) { int max. Index = 0; for( int i = 1; i < a. size( ); i++) if( a[max. Index] < a [i] ) max. Index = i; return a[max. Index]; } Correct/Incorrect? const string & find. Max( const vector<string> & a) { string max. Value = a[0]; for( int i = 1; i < a. size( ); i++) if( max. Value < a [i] ) max. Value = a[i]; return max. Value; } Correct/Incorrect? 40

Destructor, Copy Constructor, and Operator = Int. Cell: : ~Int. Cell( ) { // Does nothing } Int. Cell: : Int. Cell(const Int. Cell & rhs) : stored. Value( rhs. stored. Value) { } /* 1 */ /* 2 */ /* 3 */ const Int. Cell & Int. Cell: : operator =(const Int. Cell & rhs) { if( this != &rhs) //standard alias test stored. Value = rhs. stored. Value; return *this; } 41

When default do not work What is the problem? #include <iostream> class Int. Cell { public: int f( ) explicit Int. Cell( int initial. Value = 0 ) { stored. Value = new int( initial. Value ); } { Int. Cell a( 2 ); Int. Cell b = a; int read( ) const Int. Cell c; { return *stored. Value; } void write( int x ) { *stored. Value = x; } private: int *stored. Value; }; Output: 4 4 4 c = b; a. write( 4 ); cout << a. read( ) << endl << b. read( ) << endl << c. read( ) << endl; return 0; } int main( ) { f( ); return 0; } 42

#include <iostream> class Int. Cell { public: explicit Int. Cell( int initial. Value = 0 ); Int. Cell( const Int. Cell & rhs ); ~Int. Cell( ); const Int. Cell & operator=( const Int. Cell & rhs ); int read( ) const; void write( int x ); private: int *stored. Value; }; Int. Cell: : Int. Cell( int initial. Value ) { stored. Value = new int( initial. Value ); } Int. Cell: : Int. Cell( const Int. Cell & rhs ) { stored. Value = new int( *rhs. stored. Value ); } 43

Int. Cell: : ~Int. Cell( ) { delete stored. Value; } const Int. Cell & Int. Cell: : operator=( const Int. Cell & rhs ) { if( this != &rhs ) *stored. Value = *rhs. stored. Value; return *this; } int Int. Cell: : read( ) const { return *stored. Value; } void Int. Cell: : write( int x ) { *stored. Value = x; } 44

int f( ) { Int. Cell a( 2 ); Int. Cell b = a; Int. Cell c; c = b; a. write( 4 ); cout << a. read( ) << endl << b. read( ) << endl << c. read( ) << endl; return 0; } int main( ) { f( ); return 0; } 45

Templates /** * Return the maximum item in array a. * Assumes a. size( ) > 0. * Comparable objects must provide * copy constructor, operator<, operator= */ template <class Comparable> const Comparable & find. Max( const vector<Comparable> & a ) { /* 1*/ int max. Index = 0; /* 2*/ /* 3*/ /* 4*/ /* 5*/ } for( int i = 1; i < a. size( ); i++ ) if( a[ max. Index ] < a[ i ] ) max. Index = i; return a[ max. Index ]; 46

int main( ) { vector<int> v 1( 37 ); vector<double> v 2( 40 ); vector<string> v 3( 80 ); // vector<Int. Cell> v 4( 75 ); // Additional code to fill in the vectors cout << find. Max( v 1 ) << endl; cout << find. Max( v 2 ) << endl; cout << find. Max( v 3 ) << endl; cout << find. Max( v 4 ) << endl; // // OK: Comparable = int // OK: Comparable = double // OK: Comparable = string // Illegal; operator< undefined return 0; }; Programs: g++ find. Max. cpp Int. Cell. cpp vector. cpp string. cpp 47

Memory. Cell template class Class Templates /** * A class for simulating a memory cell. */ template <class Object> class Memory. Cell { public: explicit Memory. Cell( const Object & initial. Value = Object( ) ) : stored. Value( initial. Value ) { } const Object & read( ) const { return stored. Value; } void write( const Object & x ) { stored. Value = x; } private: Object stored. Value; }; 48

Program that uses Memory. Cell template class int main( ) { Memory. Cell<int> m 1; Memory. Cell<string> m 2( "hello" ); m 1. write( 37 ); m 2. write( m 2. read( ) + " world" ); cout << m 1. read( ) << endl << m 2. read( ) << endl; return 0; } Program: g++ Fig 01_19. cpp string. cpp 49

/** Object and Comparable * Return the maximum item in array a. * Assumes a. size( ) > 0. * Comparable objects must provide * copy constructor, operator<, operator= */ template <class Comparable> const Comparable & find. Max( const vector<Comparable> & a ) { /* 1*/ int max. Index = 0; /* 2*/ /* 3*/ /* 4*/ /* 5*/ } for( int i = 1; i < a. size( ); i++ ) if( a[ max. Index ] < a[ i ] ) max. Index = i; return a[ max. Index ]; 50

class Employee { public: void set. Value( const string & n, double s ) { name = n; salary = s; } void print( ostream & out ) const { out << name << " (" << salary << ")"; } bool operator< ( const Employee & rhs ) const { return salary < rhs. salary; } // Other general accessors and mutators, not shown private: string name; double salary; }; // Define an output operator for Employee ostream & operator<< ( ostream & out, const Employee & rhs ) { rhs. print( out ); return out; } 51
![int main( ) { vector<Employee> v( 3 ); v[0]. set. Value( "Bill Clinton", 200000. int main( ) { vector<Employee> v( 3 ); v[0]. set. Value( "Bill Clinton", 200000.](http://slidetodoc.com/presentation_image_h2/7717b7d4f6a4fa41e5313d862710ec83/image-52.jpg)
int main( ) { vector<Employee> v( 3 ); v[0]. set. Value( "Bill Clinton", 200000. 00 ); v[1]. set. Value( "Bill Gates", 200000. 00 ); v[2]. set. Value( "Billy the Marlin", 60000. 00 ); cout << find. Max( v ) << endl; return 0; } 52

Matrix Class template <class Object> class matrix { public: matrix( int rows, int cols ) : array( rows ) { for( int i = 0; i < rows; i++ ) array[ i ]. resize( cols ); } matrix( const matrix & rhs ) : array( rhs. array ) { } const vector<Object> & operator[]( int row ) const { return array[ row ]; } vector<Object> & operator[]( int row ) { return array[ row ]; } int numrows( ) const { return array. size( ); } int numcols( ) const { return numrows( ) ? array[ 0 ]. size( ) : 0; } private: vector<Object> > array; }; 53
- Slides: 53