290 likes | 438 Vues
This article explores the concept of recursion in programming, specifically focusing on data structures like lists. We discuss how to define operations recursively, such as calculating the length of a list or implementing insertion sort. Through induction, we prove the correctness of these operations. The article also addresses complex algorithms like merge sort and highlights the general applicability of recursion beyond lists, including natural numbers and factorial operations. Additionally, we touch upon preprocessor directives used in conditional compilation.
E N D
Recursion - Control • Given recursive data (types), we can define operations also recursively! • Consider computing the length of a sequential access list: • Length of an empty list is 0. • Length of a non-empty list ( contains at least one element) is 1 more than the length of the list from which this was obtained by adding an element. Length(L) =Length(prevList) +1
Recursive Invocation of Functions len=0 Is list empty? Stop yes no len=len+1 List=delete(List)
Link ls; … int len=0; while (ls!=NULL) { len++; ls = ls->next; } Define function f: int f (Link ls) { if (ls == NULL) return 0; else return 1 + f(ls->next); } Recursive Invocation of Functions
Correctness Arguments • For the recursive version: • Prove hypothesis is true for base case ( say, list is empty) • Prove hypothesis is preserved for one recursive / / inductive step ( f(ls) = 1 + f(ls->next) ) • It is proved (by Induction) that the hypothesis is true for this function definition.
More examples • Is length the only thing we can define recursively? • No. For instance, • Addition / insertion is built into the definition. • More complicated examples? How about sorting?
Insert AN-1 into sorted list Insertion Sort – Top Down Design Sort list: A0 to AN-1 Sort list: A0 to AN-2 Sort list: A0 to AN-3 …
Insertion Sorting - Algorithm • Recursive version of insertion sorting: • void isort(Element ls[], int size) • { • if (size>1) { • isort(ls, size-1); • insert(ls[size-1], ls, size-1); • } • }
Insertion Sorting - Correctness • insert is order-preserving: • If ls is an ordered list (of size n-1) then insert(e, ls, n-1) returns an ordered list (of size n). • Inductive argument: • Basis: Empty list (size==0) is sorted. • Hypothesis: isort(ls, n-1) returns a sorted list • Step: insert(e, isort(ls, n-1), n) returns a sorted list by hypothesis and by the “order-preserving” property of insert.
Insertion Sorting - Complexity • Let time taken by isort(ls,N) be Tisort(N). • Then Tisort(N) can be broken into (for N>0): • Time taken by isort(ls, N-1): Tisort(N-1) • Time taken by insert(e, ls, N-1): N-1 • Thus Tisort(N) = Tisort(N-1) + N-1 • This is called a recurrence relation or recurrence equation. We solve it by simple substitution: Tisort(N) = Tisort(N-1) + N-1 = Tisort(N-2) + N-2 + N-1 = … = Tisort(0) + 1 + 2 + … + N-1 =O(N2) = because Tisort(0) = 1
Merge Sorting – Top Down Design Sort list: A[0] to A[N-1] M Sort list: A[0] to A[N/2] Sort list: A[N/2+1] to A[N-1] M Sort list: A[0] to A[N/4 ] Sort A[N/4 +1] to A[N/2] M …
mid = (st+end)/2; msort(ls, st, mid); msort(ls, mid+1, end); mergeIn(ls, st, mid+1, end); Merge Sorting – Algorithm void msort(Element ls[], int st, int end) { if (st >= end) return; else { } }
Merge Sorting - Correctness • merge is order-preserving: • If l1,l2 are ordered lists. then merge(l1,l2) returns an ordered list • Inductive argument: • Basis: Empty list & Singleton list are sorted. • Hypothesis: msort(l1,s1,e1) returns a sorted list for (e1-s1+1)<=n/2 • Step: merge(l1, l2) where l1 and l2 are the results of msort returns a sorted list by hypothesis and by the “order-preserving” property of merge.
Merge Sorting – Complexity • Tmsort(N) = 2*Tmsort(N/2) + N ; when N > 0 • Tmsort(N) = 2*Tmsort(N/2) + N = 4*T(N/4) + 2*N/2 + N = 8*T(N/8) + 4*N/4 + 2*N /2 + N = 2k * T(N/2k) + k*N = N * T(1) + k*N (when N is close to 2k) = (k+1)*N) (since T(1) = 1) = O(N*logN) (since k = log2N)
recursion …only on lists? • No! It can be used on any inductive structure. • Consider natural numbers: • 0 is a natural number • n+1 is a natural number if n is a natural number • Consider any operation on natural numbers: • Factorial of 0 is 1. • Factorial of n+1 is n+1 times Factorial of n. • This leads to natural definition of factorial: • int fact(int n) { return (n==0) ? 1 : n*fact(n-1); }
The #define Preprocessor Directive: Macros • #undef • Undefines a symbolic constant or macro • If a symbolic constant or macro has been undefined it can later be redefined
Conditional Compilation • Conditional compilation • Control preprocessor directives and compilation • Cast expressions, sizeof, enumeration constants cannot be evaluated in preprocessor directives • Structure similar to if #if !defined( NULL ) #define NULL 0 #endif • Determines if symbolic constant NULL has been defined • If NULL is defined, defined( NULL ) evaluates to 1 • If NULL is not defined, this function defines NULL to be 0 • Every #if must end with #endif • #ifdef short for #if defined( name ) • #ifndef short for #if !defined( name )
Conditional Compilation • Other statements • #elif– equivalent of else if in an if structure • #else– equivalent of else in an if structure • "Comment out" code • Cannot use /* ... */ • Use #if 0 code commented out #endif • To enable code, change 0 to 1
Conditional Compilation • Debugging #define DEBUG 1 #ifdef DEBUG cerr << "Variable x = " << x << endl;#endif • Defining DEBUG to 1 enables code • After code corrected, remove #define statement • Debugging statements are now ignored
Assertions • assert macro • Header <assert.h> • Tests value of an expression • If 0 (false) prints error message and calls abort • Example: assert( x <= 10 ); • If NDEBUG is defined • All subsequent assert statements ignored #define NDEBUG