Fundamentals of Data Structures in C Horowitz PDF Fundamentals Of Data Structures In C Author: Ellis Horowitz, Anderson-Freed, Sahni other link other link . PDF | On Jan 1, , Ellis Horowitz and others published Fundamentals of Data Structure in C++. large files. pdf structures c ellis horowitz fundamentals data - downloadfundamentals data structures c ellis horowitz pdf. free pdf download conducted.
|Language:||English, Spanish, Indonesian|
|Genre:||Politics & Laws|
|ePub File Size:||28.75 MB|
|PDF File Size:||18.28 MB|
|Distribution:||Free* [*Regsitration Required]|
The study of data structures and algorithms is fundamental to computer into this Fundamentals of Data Structures in C By Ellis Horowitz, Sartaj Sahni, Susan. Fundamentals of Data Structures - Ellis Horowitz, Sartaj caite.info - Ebook download as PDF file:///C|/E%20Drive%20Data/My%20Books/Algorithm/DrDob . For many years a data structures course has been taught in computer science Weizenbaum), IPL-V (by A. Newell, C. Sh.
Sequential representation of A 1: In section 4. Thus we say that integers are represented by bit strings. Given A and B where A is m element is defined as for and n and B is n p, the product matrix C has dimension m p. Ackermann's function A m. The maximum number of iterations of the while loop of lines file:
On the other hand. For exponential algorithms. Notice how the times O n and O n log n grow much more slowly than the others. We will see cases of this in subsequent chapters. Given an algorithm. For small data sets. Then a performance profile can be gathered using real time calculation. In practice these constants depend on many factors.. This shows why we choose the algorithm with the smaller order of magnitude.
For large data sets. Figures When n is odd H. Coxeter has given a simple rule for generating a magic square: A magic square is an n x n matrix of the integers 1 to n2 such that the sum of every row. It emphasizes that the variables are thought of as pairs and are changed as a unit.
The statement i. The file: Academic Press. Fundamental Algorithms. Thus each statement within the while loop will be executed no more than n2. Special Issue: Since there are n2 positions in which the algorithm must place a number. For a discussion of tools and procedures for developing very large software systems see Practical Strategies for Developing Large Software Systems. For a discussion of the more abstract formulation of data structures see "Toward an understanding of data structures" by J.
ACM Computing Surveys. Kernighan and P. The while loop is governed by the variable key which is an integer variable initialized to 2 and increased by one each time through the loop. For a discussion of good programming techniques see Structured Programming by O. The Elements of Programming Style by B. For this application it is convenient to number the rows and columns from zero to n. The magic square is represented using a two dimensional array having n rows and n column. For a further discussion of program proving see file: Can you think of a clever meaning for S.
Describe the flowchart in figure 1. American Mathematical Society. Can you do this without using the go to? Now make it into an algorithm. Both do not satisfy one of the five criteria of an algorithm.
Look up the word algorithm or its older form algorism in the dictionary.? Concentrate on the letter K first. How would you handle people with the same last name.
Discuss how you would actually represent the list of name and telephone number pairs in a real machine. Which criteria do they violate? Consider the two statements: For instance.. What is the computing time of your method?
Strings x and y remain unchanged. Determine when the second becomes larger than the first. String x is unchanged. The rule is: If x occurs Given n boolean variables x Try writing this without using the go to statement. Implement these procedures using the array facility. Determine how many times each statement is executed. List as many rules of style in programming that you can think of that you would be willing to follow yourself. Represent your answer in the array ANS 1: NOT X:: Take any version of binary search.
Prove by induction: Using the notation introduced at the end of section 1. Trace the action of the procedure below on the elements 2. Write a recursive procedure for computing this function. Given n. If S is a set of n elements the powerset of S is the set of all possible subsets of S. Tower of Hanoi There are three towers and sixty four disks of different diameters placed on the first tower.
Write a recursive procedure for computing the binomial coefficient where. The pigeon hole principle states that if a function f has n distinct inputs but less than n distinct outputs then there exists two inputs a. Then write a nonrecursive algorithm for computing Ackermann's function. Ackermann's function A m. Analyze the time and space requirements of your algorithm. Analyze the computing time of procedure SORT as given in section 1. Write a recursive procedure to compute powerset S.
Write a recursive procedure which prints the sequence of moves which accomplish this task. The disks are in order of decreasing diameter as one scans up the tower. Give an algorithm which finds the values a. Monks were reputedly supposed to move the disks from tower 1 to tower 3 obeying the rules: It is true that arrays are almost always implemented by using consecutive memory. Using our notation this object can be defined as: If one asks a group of programmers to define an array.
Therefore it deserves a significant amount of attention. In mathematical terms we call this a correspondence or a mapping. For arrays this means we are concerned with only two operations which retrieve and store values. STORE is used to enter new index-value pairs. The array is often the only means for structuring data which is provided in a programming language.. For each index which is defined.
This is unfortunate because it clearly reveals a common point of confusion. If we interpret the indices to be n-dimensional. If we consider an ordered list more abstractly. If we restrict the index values to be integers.
There are a variety of operations that are performed on these lists. Ace or the floors of a building basement. Notice how the axioms are independent of any representation scheme. In section 2. These operations include: This we will refer to as a sequential mapping.. It is only operations v and vi which require real effort. This gives us the ability to retrieve or modify the values of random elements in the list in a constant amount of time.. By "symbolic. It is precisely this overhead which leads us to consider nonsequential mappings of ordered lists into arrays in Chapter 4.
The problem calls for building a set of subroutines which allow for the manipulation of symbolic polynomials. We can access the list element values in either direction by changing the subscript values in a controlled way.. It is not always necessary to be able to perform all of these operations.
In the study of data structures we are interested in ways of representing ordered lists so that these operations can be carried out efficiently. Let us jump right into a problem requiring ordered lists which we will solve by using one dimensional arrays. Perhaps the most common way to represent an ordered list is by an array where we associate the list element ai with the array index i. This problem has become the classical example for motivating the use of list processing techniques which we will see in later chapters.
Insertion and deletion using sequential allocation forces us to move some of the remaining elements so the sequential mapping is preserved in its proper form. The first step is to consider how to define polynomials as a computer structure. However this is not an appropriate definition for our purposes. A complete specification of the data structure polynomial is now given.
For a mathematician a polynomial is a sum of terms where each term has the form axe. When defining a data object one must decide what functions will be available.
MULT poly. We will also need input and output routines and some suitable format for preparing polynomials as input. These assumptions are decisions of representation. Suppose we wish to remove from P those terms having exponent one.
Then we would write REM P. Notice the absense of any assumptions about the order of exponents. Now we can make some representation decisions. Note how trivial the addition and multiplication operations have become.
These axioms are valuable in that they describe the meaning of each operation concisely and without implying an implementation. Now assuming a new function EXP poly exp which returns the leading exponent of poly. EXP B file: COEF B. Exponents should be unique and in decreasing order is a very reasonable first decision. EXP B. This representation leads to very simple algorithms for addition and multiplication.. COEF A. But are there any disadvantages to this representation?
Hopefully you have already guessed the worst one. B REM B. With these insights. Since the tests within the case statement require two terms. EXP A. The case statement determines how the exponents are related and performs the proper action..
We have avoided the need to explicitly store the exponent of each term and instead we can deduce its value by knowing our position in the list and the degree. EXP B: C A end end insert any remaining terms in A or B into C The basic loop of this algorithm consists of merging the terms of the two polynomials. As for storage. But scheme 1 could be much more wasteful.
It will require a vector of length Then for each term there are two entries representing an exponent-coefficient pair. In general. In the worst case. Is this method any better than the first scheme? The first entry is the number of nonzero terms. If all of A's coefficients are nonzero. Basic algorithms will need to be more complex because we must check each exponent before we handle its coefficient..
Suppose we take the polynomial A x above and keep only its nonzero coefficients. This is a practice you should adopt in your own coding. It is natural to carry out this analysis in terms of m and n. Comments appear to the right delimited by double slashes. Notice how closely the actual program matches with the original design. The procedure has parameters which are polynomial or array names.
Statement two is a shorthand way of writing r The basic iteration step is governed by a while loop. Three pointers p. Blocks of statements are grouped together using square brackets.. The code is indented to reinforce readability and to reveal more clearly the scope of reserved words. The assignments of lines 1 and 2 are made only once and hence contribute O 1 to the overall computing time. We are making these four procedures available to any user who wants to manipulate polynomials.
Taking the sum of all of these steps. He would include these subroutines along with a main procedure he writes himself.
Each iteration of this while loop requires O 1 time. This hypothetical user may have many polynomials he wants to compute and he may not know their sizes. If he declares the arrays too large. At each iteration.. Suppose in addition to PADD. In particular we now have the m lists a Instead we might store them in a one dimensional array and include a front i and rear i pointer for the beginning and end of each list.. Since the iteration terminates when either p or q exceeds 2m or 2n respectively.
Consider the main routine our mythical user might write if he wanted to compute the Fibonacci polynomials. To make this problem more concrete. Returning to the abstract object--the ordered list--for a moment. This worst case is achieved. These are defined by the recurrence relation file: A two dimensional array could be a poor way to represent these lists because we would have to declare it as A m.. In this main program he needs to declare arrays for all of his polynomials which is reasonable and to declare the maximum size that every polynomial might achieve which is harder and less reasonable.
This example shows the array as a useful representational form for ordered lists. Then the following program is produced. For example F 2. Different types of data cannot be accommodated within the usual array concept. Then by storing all polynomials in a single array. For instance. Let's pursue the idea of storing all polynomials in a single array called POLY.
If we made a call to our addition routine. This example reveals other limitations of the array as a means for data representation.
Exponents and coefficients are really different sorts of numbers.. If the result has k terms. The array is usually a homogeneous collection of data which will not allow us to intermix data of different types. Also we need a pointer to tell us where the next free location is. A much greater saving could be achieved if Fi x were printed as soon as it was computed in the first loop.. Such a matrix has mn elements. There may be several such polynomials whose space can be reused.
Even worse. As we create polynomials. As computer scientists. Such a matrix is called sparse. Now if we look at the second matrix of figure 2.
In Chapter 4 we will see an elegant solution to these problems. A general matrix consists of m rows and n columns of numbers as in figure 2. Each element of a matrix is uniquely characterized by its row and column position. It is very natural to store a matrix in a two dimensional array. Example of 2 matrices The first matrix has five rows and three columns.
We could write a subroutine which would compact the remaining polynomials. We might then store a matrix as a list of 3-tuples of the form i. A sparse matrix requires us to consider an alternate form of representation. When this happens must we quit? We must unless there are some polynomials which are no longer needed. Now we have localized all storage to one array.
There is no precise definition of when a matrix is sparse and when it is not. On most computers today it would be impossible to store a full X matrix in the memory at once. The alternative representation will explicitly store only the nonzero elements.
This comes about because in practice many of the matrices we want to deal with are large. This demands a sophisticated compacting routine coupled with a disciplined use of names for polynomials.
Then we can work with any element by writing A i. But this may require much data movement. When m is equal to n. Figure 2. Another way of saying this is that we are interchanging rows and columns. Sparse matrix stored as triples The elements A 0. Now what are some of the operations we might want to perform on these matrices? One operation is to compute the transpose matrix. We can go one step farther and require that all the 3-tuples of any row be stored so that the columns are increasing.
The transpose of the example matrix looks like 1. The elements on the diagonal will remain unchanged. This is where we move the elements so that the element in the i. In our example of figure 2. If we just place them consecutively. We can avoid this data movement by finding the elements in the order we want them. Since the rows are originally in order.
Let us write out the algorithm in full. The total time for the algorithm is therefore O nt. The variable q always gives us the position in B where the next term in the transpose is to be inserted. This computing time is a little disturbing since we know that in case the matrices had been represented as two dimensional arrays. It is not too difficult to see that the algorithm is correct.
This is precisely what is being done in lines The algorithm for this takes the form: How about the computing time of this algorithm!
For each iteration of the loop of lines The statement a. On the first iteration of the for loop of lines all terms from column 1 of A are collected. In addition to the space needed for A and B.
Since the number of iterations of the loop of lines is n. Since the rows of B are the columns of A. The terms in B are generated by rows. We now have a matrix transpose algorithm which we believe is correct and which has a computing time of O nt.
Lines take a constant amount of time. The assignment in lines takes place exactly t times as there are only t nonzero terms in the sparse matrix being generated.
This gives us the number of elements in each row of B. We can now move the elements of A one by one into their correct position in B. This is worse than the O nm time using arrays. From this information..
This algorithm. Hence in this representation. Each iteration of the loops takes only a constant amount of time. T j is maintained so that it is always the position in B where the next element in row j is to be inserted.
This is the same as when two dimensional arrays were in use. In lines the elements of A are examined one by one starting from the first and successively moving to the t-th element. The computation of S and T is carried out in lines When t is sufficiently small compared to its maximum of nm.
If we try the algorithm on the sparse matrix of figure 2. MACH m file: Suppose now you are working for a machine manufacturer who is using a computer to do inventory control. Associated with each machine that the company produces. T i points to the position in the transpose where the next element of row i is to be stored.
Regarding these tables as matrices this application leads to the general definition of matrix product: Given A and B where A is m element is defined as n and B is n p. The product of two sparse matrices may no longer be sparse. This sum is more conveniently written as If we compute these sums for each machine and each micropart then we will have a total of mp values which we might store in a third table MACHSUM m.
Once the elements in row i of A and column j of B have been located. Each part is itself composed of smaller parts called microparts.. Before we write a matrix multiplication procedure. Consider an algorithm which computes the product of two sparse matrices represented as an ordered list instead of an array. To compute the elements of C row-wise so we can store them in their proper place without moving previously computed elements. Its i. We want to determine the number of microparts that are necessary to make up each machine.
To avoid this. An alternative approach is explored in the exercises. It makes use of variables i. In each iteration of the while loop of lines either the value of i or j or of both increases by 1 or i and col are reset. Let us examine its complexity. The variable r is the row of A that is currently being multiplied with the columns of B. The total maximum increments in i is therefore pdr. This enables us to handle end conditions i. We leave the correctness proof of this algorithm as an exercise.
When this happens. If dr is the number of terms in row r of A then the value of i can increase at most dr times before i moves to the next row of A. The maximum number of iterations of the while loop of lines file: The maximum total increment in j over the whole loop is t2. In addition to all this.
In addition to the space needed for A. The while loop of lines is executed at most m times once for each row of A. At the same time col is advanced to the next column. C and some simple variables. Once again. A and B are sparse.. Since t1 nm and t2 np. The classical multiplication algorithm is: As in the case of polynomials. There are. It introduces some new concepts in algorithm analysis and you should make sure you understand the analysis. Since the number of terms in a sparse matrix is variable.
This would enable us to make efficient utilization of space. MMULT will outperform the above multiplication algorithm for arrays. Lines take only O dr time. MMULT will be slower by a constant factor. If an array is declared A l1: These difficulties also arise with the polynomial representation of the previous section and will become apparent when we study a similar representation for multiple stacks and queues section 3.
This is necessary since programs using arrays may. We see that the subscript at the right moves the fastest. In addition to being able to retrieve array elements easily. If we have the declaration A 4: While many representations might seem plausible. Then using row major order these elements will A 4. Recall that memory may be regarded as one dimensional with words numbered from 1 to m.
Assuming that each array element requires only one word of memory. Knowing the address of A i. Sequential representation of A u1. To simplify the discussion we shall assume that the lower bounds on each dimension li are 1. These two addresses are easy to guess.. In a row major representation.. Before obtaining a formula for the case of an n-dimensional array.
Sequential representation of A 1: This formula makes use of only the starting address of the array plus the declared dimensions. Then A 4. From the compiler's point of view. Suppose A 4. Another synonym for row major order is lexicographic order.
The general case when li can be any integer is discussed in the exercises. To begin with.. Each 2-dimensional array is represented as in Figure To review.. Repeating in this way the address for A i1. From this and the formula for addressing a 2 dimensional array.
In all cases we have been able to move the values around To locate A i. However several problems have been raised By using a sequential mapping which associates ai of a An alternative scheme for array representation..
The address of A i If is the address for A The address for A i Assume that n lists. For these polynomials determine the exact number of times each statement will be executed.
What can you say about the existence of an even faster algorithm? How much space is actually needed to hold the Fibonacci polynomials F The functions to be performed on these lists are insertion and deletion.
The i-th list should be maintained as sequentially stored. Assume you can compare atoms ai and bj. Try to minimize the number of operations. What is the computing time of your procedure? Write a procedure which returns What is the computing time of your algorithm?
The band includes a. What is the relationship between i and j for elements in the zero part of A? For large n it would be worthwhile to save the space taken by the zero entries in the upper triangle. Define a square band matrix An. Obtain an addressing formula for elements aij in the lower triangle if this lower triangle is stored by rows in an array B 1: When all the elements either above or below the main diagonal of a square matrix are zero. In this square matrix. Let A and B be two lower triangular matrices.
Tridiagonal matrix A If the elements in the band formed by these three diagonals are represented rowwise in an array. Devise a scheme to represent both the triangles in an array C 1: Another kind of sparse matrix that arises often in numerical analysis is the tridiagonal matrix. How much time does it take to locate an arbitrary element A i. A generalized band matrix An. A variation of the scheme discussed in section 2. Thus A B which determines the value of element aij in the matrix An.
The band of An. B where A and B contain real values. Consider space and time requirements for such operations as random access. Use a minimal amount of storage. Obtain an addressing formula for the element A i1. In this representation. Do exercise 20 assuming a column major representation. Assume a row major representation of the array with one word per element and l A complex-valued matrix X is represented by a pair of matrices A.. The figure below illustrates the representation for the sparse matrix of figure 2.
In addition. Given an array A 1: An m X n matrix is said to have a saddle point if some entry A i.. Write a program which computes the product of two complex valued matrices A.
How many values can be held by an array with dimensions A 0: How much time does your algorithm take? One possible set of axioms for an ordered list comes from the six operations of section 2. Assuming that he may move from his present tile to any of the eight tiles surrounding him unless he is against a wall with equal probability. All but the most simple of these are extremely difficult to solve and for the most part they remain largely unsolved.
The problem may be simulated using the following method: One such problem may be stated as follows: A drunken cockroach is placed on a given square in the middle of a tile floor in a rectangular room of size n x m tiles. The position of the bug on the floor is represented by the coordinates IBUG. JBUG and is initialized by a data card. There are a number of problems. All the cells of this array are initialized to zero.
The bug wanders possibly in search of an aspirin randomly from tile to tile throughout the room. The technique for doing so is called "simulation" and is of wide-scale use in industry to predict traffic-flow. Hard as this problem may be to solve by pure probability theory techniques.
When every square has been entered at least once. Have an aspirin This exercise was contributed by Olson. This assures that your program does not get "hung" in an "infinite" loop.
Each time a square is entered. Of course the bug cannot move outside the room. Many of these are based on the strange "L-shaped" move of the knight. A maximum of This will show the "density" of the walk. Chess provides the setting for many fascinating diversions which are quite independent of the game itself. Your program MUST: Write a program to perform the specified simulation experiment.
A classical example is the problem of the knight's tour. It is convenient to represent a solution by placing the numbers 1.
The goal of this exercise is to write a computer program to implement Warnsdorff's rule. The most important decisions to be made in solving a problem of this type are those concerning how the data is to be represented in the computer. One of the more ingenious methods for solving the problem of the knight's tour is that given by J.
Briefly stated. The eight possible moves of a knight on square 5. Perhaps the most natural way to represent the chessboard is by an 8 x 8 array B ARD as shown in the figure below. Note that it is not required that the knight be able to reach the initial position by one more move. His rule is that the knight must always be moved to one of the squares from which there are the fewest exits to squares not already traversed.
Warnsdorff in The ensuing discussion will be much easier to follow. Let NP S be the number of possibilities. The data representation discussed in the previous section is assumed.
J may move to one of the squares I. That is. J is located near one of the edges of the board. Below is a description of an algorithm for solving the knight's tour problem using Warnsdorff's rule. Recall that a square is an exit if it lies on the chessboard and has not been previously occupied by the knight. If this happens. J denotes the new position of the knight. In every case we will have 0 NP S 8.
The problem is to write a program which corresponds to this algorithm.. Go to Chapter 3 Back to Table of Contents file: This exercise was contributed by Legenhausen and Rebman. J records the move in proper sequence. A queue is an ordered list in which all insertions take place at one end.. Both these data objects are special cases of the more general data object. Figure 3. A stack is an ordered list in which all insertions and deletions are made at one end. Suppose we have a main procedure and three subroutines as below: E are added to the stack.
Equivalently we say that the last"element to be inserted into the stack will be the first to be removed. The ai are referred to as atoms which are taken from some set. They arise so often that we will discuss them separately before moving on to more complex objects. Thus A is the first letter to be removed. The restrictions on a queue require that the first element which is inserted into the queue will be the first one to be removed. One natural example of stacks which arises in computer programming is the processing of subroutine calls and their returns.
The first entry. This list operates as a stack since the returns will be made in the reverse order of the calls. For each subroutine there is usually a single location associated with the machine code which is used to retain the return address. If we examine the memory while A3 is computing there will be an implicit stack which looks like q.
This list of return addresses need not be maintained in consecutive locations. In each case the calling procedure passes the return address to the called procedure. Al then invokes A2 which in turn calls A3. Since returns are made in the reverse order of calls. ADD i. Associated with the object stack there are several operations that are necessary: When recursion is allowed. A2 before A1. Whenever a return is made. Thus t is removed before s. S which inserts the element i onto the stack S and returns the new stack.
Implementing recursion using a stack is discussed in Section This can be severely limiting in the case of recursive calls and re-entrant routines. The address r is passed to Al which saves it in some location for later processing. S file: TOP S which returns the top element of stack S. These five functions constitute a working definition of a stack.
However we choose to represent a stack. Lines 6 through 13 are the set of axioms which describe how the functions are related.. Lines 10 and 12 are the essential ones which define the last-in-first-out behavior. This will be dealt with when we represent this structure in a computer. The simplest way to represent a stack is by using a one-dimensional array.
With this decision made the following implementations result: Associated with the array will be a variable.. The above definitions describe an infinite stack for no upper bound on the number of elements is specified. Often a stack full condition will signal that more storage needs to be allocated and the program re-run CREATE initializes top to zero while ADD increments it by 1 so long as top is less than n this is necessary because we can implement only a finite stack.
The remainder of the axioms can be shown to hold similarly. Stack empty is often a meaningful condition. Let us show this for the first three rules. The correctness of the stack implementation above may be established by showing that in this implementation. In Section 3. Q which adds the element i to the rear of a queue and returns the new queue. A minimal set of useful operations on a queue includes the following: ADDQ i. Additions to the queue take place at the rear. Perhaps the most common occurrence of a queue in computer applications is for the scheduling of jobs.
The job at the front of the queue is the next one to be executed.. As mentioned earlier. This ignores the possible existence of priorities. In batch processing the jobs are ''queued-up'' as they are read-in and executed. Deletions are made from the front. The representation of a finite queue in sequential locations is somewhat more difficult than a stack. In addition to a one dimensional array Q 1: Remarks file: The conventions we shall adopt for these two variables are that front is always 1 less than the actual front of the queue and rear always points to the last element in the queue..
With these conventions. Gerhart, T. Standish, and J. Finally, we would like to thank our institutions, the University of Southern California and the University of Minnesota, for encouraging in every way our efforts to produce this book. Ellis Horowitz Sartaj Sahni Preface to the Ninth Printing We would like to acknowledge collectively all of the individuals who have sent us comments and corrections since the book first appeared.
For this printing we have made many corrections and improvements. October l file: One often quoted definition views computer science as the study of algorithms. This study encompasses four distinct areas: The goal is to study various forms of machine fabrication and organization so that algorithms can be effectively carried out. At one end are the languages which are closest to the physical machine and at the other end are languages designed for sophisticated problem solving.
One often distinguishes between two phases of this area: The first calls for methods for specifying the syntax and semantics of a language. The second requires a means for translation into a more basic set of commands. Abstract models of computers are devised so that these properties can be studied. This was realized as far back as by Charles Babbage, the father of computers.
An algorithm's behavior pattern or performance profile is measured in terms of the computing time and space that are consumed while the algorithm is processing. Questions such as the worst and average time and how often they occur are typical. We see that in this definition of computer science, "algorithm" is a fundamental notion. Thus it deserves a precise definition. The dictionary's definition "any mechanical or recursive computational procedure" is not entirely satisfying since these terms are not basic enough.
An algorithm is a finite set of instructions which, if followed, accomplish a particular task. In addition every algorithm must satisfy the following criteria: It is not enough that each operation be definite as in iii , but it must also be feasible. In formal computer science, one distinguishes between an algorithm, and a program. A program does not necessarily satisfy condition iv. One important example of such a program for a computer is its operating system which never terminates except for system crashes but continues in a wait loop until more jobs are entered.
In this book we will deal strictly with programs that always terminate. Hence, we will use these terms interchangeably. An algorithm can be described in many ways. A natural language such as English can be used but we must be very careful that the resulting instructions are definite condition iii. An improvement over English is to couple its use with a graphical form of notation such as flowcharts. This form places each processing step in a "box" and uses arrows to indicate the next step.
Different shaped boxes stand for different kinds of operations. All this can be seen in figure 1. The point is that algorithms can be devised for many common activities. Have you studied the flowchart? Then you probably have realized that it isn't an algorithm at all! Which properties does it lack? Returning to our earlier definition of computer science, we find it extremely unsatisfying as it gives us no insight as to why the computer is revolutionizing our society nor why it has made us re-examine certain basic assumptions about our own role in the universe.
While this may be an unrealistic demand on a definition even from a technical point of view it is unsatisfying. The definition places great emphasis on the concept of algorithm, but never mentions the word "data". If a computer is merely a means to an end, then the means may be an algorithm but the end is the transformation of data. That is why we often hear a computer referred to as a data processing machine.
Raw data is input and algorithms are used to transform it into refined data. So, instead of saying that computer science is the study of algorithms, alternatively, we might say that computer science is the study of data: Figure 1. Flowchart for obtaining a Coca-Cola There is an intimate connection between the structuring of data, and the synthesis of algorithms.
In fact, a data structure and an algorithm should be thought of as a unit, neither one making sense without the other. For instance, suppose we have a list of n pairs of names and phone numbers a1,b1 a2,b2 , This task is called searching.
Just how we would write such an algorithm critically depends upon how the names and phone numbers are stored or structured. One algorithm might just forge ahead and examine names, a1,a2,a3, This might be fine in Oshkosh, but in Los Angeles, with hundreds of thousands of names, it would not be practical. If, however, we knew that the data was structured so that the names were in alphabetical order, then we could do much better.
We could make up a second list which told us for each letter in the alphabet, where the first name with that letter appeared. For a name beginning with, say, S, we would avoid having to look at names beginning with other letters.
So because of this new structure, a very different algorithm is possible. Other ideas for algorithms become possible when we realize that we can organize the data as we wish. We will discuss many more searching strategies in Chapters 7 and 9. Therefore, computer science can be defined as the study of data, its representation and transformation by a digital computer.
The goal of this book is to explore many different kinds of data objects. For each object, we consider the class of operations to be performed and then the way to represent this object so that these operations may be efficiently carried out.
This implies a mastery of two techniques: The pedagogical style we have chosen is to consider problems which have arisen often in computer applications. For each problem we will specify the data object or objects and what is to be accomplished.
After we have decided upon a representation of the objects, we will give a complete algorithm and analyze its computing time. After reading through several of these examples you should be confident enough to try one on your own.
There are several terms we need to define carefully before we proceed. These include data structure, data object, data type and data representation. These four terms have no standard meaning in computer science circles, and they are often used interchangeably. A data type is a term which refers to the kinds of data that variables may "hold" in a programming language.
With every programming language there is a set of built-in data types. This means that the language allows variables to name data of that type and file: Some data types are easy to provide because they are already built into the computer's machine language instruction set.
Integer and real arithmetic are examples of this. Other data types require considerably more effort to implement. In some languages, there are features which allow one to construct combinations of the built-in types. However, it is not necessary to have such a mechanism.
All of the data structures we will see here can be reasonably built within a conventional programming language. Data object is a term referring to a set of elements, say D. Thus, D may be finite or infinite and if D is very large we may need to devise special ways of representing its elements in our computer.
The notion of a data structure as distinguished from a data object is that we want to describe not only the set of objects, but the way they are related. Saying this another way, we want to describe the set of operations which may legally be applied to elements of the data object.
This implies that we must specify the set of operations and show how they work. To be more precise lets examine a modest example. The following notation can be used: SUCC stands for successor. The rules on line 8 tell us exactly how the addition operation works. For example if we wanted to add two and three we would get the following sequence of expressions: In practice we use bit strings which is a data structure that is usually provided on our computers. But however the ADD operation is implemented, it must obey these rules.
Hopefully, this motivates the following definition. A data structure is a set of domains , a designated domain , a set of functions and a file: The triple www. The form in which we choose to write the axioms is important. Our goal here is to write the axioms in a representation independent way.
Then, we discuss ways of implementing the functions using a conventional programming language. An implementation of a data structure d is a mapping from d to a set of other data structures e. This mapping specifies how every object of d is to be represented by the objects of e.
Secondly, it requires that every function of d must be written using the functions of the implementing data structures e. Thus we say that integers are represented by bit strings, boolean is represented by zero and one, an array is represented by a set of consecutive words in memory. In current parlance the triple is referred to as an abstract data type. It is called abstract precisely because the axioms do not imply a form of representation. Another way of viewing the implementation of a data structure is that it is the process of refining an abstract data type until all of the operations are expressible in terms of directly executable functions.
But at the first stage a data structure should be designed so that we know what it does, but not necessarily how it will do it. This division of tasks, called specification and implementation, is useful because it helps to control the complexity of the entire process. Though some of these are more preferable than others, the choice of a specific language leaves us with many difficulties.
First of all, we wish to be able to write our algorithms without dwelling on the idiosyncracies of a given language. Secondly, some languages have already provided the mechanisms we wish to discuss. Thus we would have to make pretense to build up a capability which already exists. Finally, each language has its followers and its detractors.
We would rather not have any individual rule us out simply because he did not know or, more particularly, disliked to use the language X. Furthermore it is not really necessary to write programs in a language for which a compiler exists. Instead we choose to use a language which is tailored to describing the algorithms we want to write. Most importantly, the language we use will be close enough to many of the languages mentioned before so that a hand translation will be relatively easy to accomplish.
Moreover, one can easily program a translator using some existing, but more primitive higher level language as the output see Appendix A. Nevertheless, computerniks still try to attach a meaning.
Several cute ideas have been suggested, such as Structured Programming: The way to assign values is by the assignment statement variable expression. In addition to the assignment statement, SPARKS includes statements for conditional testing, iteration, input-output, etc. Several such statements can be combined on a single line if they are separated by a semi-colon.
Expressions can be either arithmetic, boolean or of character type. In the boolean case there can be only one of two values, true or false. In order to produce these values, the logical operators and, or, not are provided, plus the relational operators file: If S1 or S2 contains more than one statement, these will be enclosed in square brackets.
Brackets must be used to show how each else corresponds to one if. The meaning of this statement is given by the flow charts: We will assume that conditional expressions are evaluated in "short circuit" mode; given the boolean expression cond1 or cond2 , if condl is true then cond2 is not evaluated; or, given condl and cond2 , if cond1 is false then cond2 is not evaluated. To accomplish iteration, several statements are available. One of them is while cond do S end where cond is as before, S is as S1 before and the meaning is given by It is well known that all "proper" programs can be written using only the assignment, conditional and while statements.
This result was obtained by Bohm and Jacopini. Though this is very interesting from a theoretical viewpoint, we should not take it to mean that this is the way to program. On the contrary, the more expressive our languages are, the more we can accomplish easily. So we will provide other statements such as a second iteration statement, the repeat-until, repeat S file: Another iteration statement is loop S forever which has the meaning As it stands, this describes an infinite loop! However, we assume that this statement is used in conjunction with some test within S which will cause an exit.
One way of exiting such a loop is by using a go to label statement which transfers control to "label. A more restricted form of the go to is the command exit which will cause a transfer of control to the first statement after the innermost loop which contains it.
This looping statement may be a while, repeat, for or a loop-forever. A variable or a constant is a simple form of an expression. The semantics is easily described by the file: The else clause is optional. The expr may be omitted in which case a return is made to the calling procedure.
The execution of an end at the end of procedure implies a return. A procedure may be invoked by using a call statement call NAME parameter list Procedures may call themselves, direct recursion, or there may be a sequence resulting in indirect recursion.
Though recursion often carries with it a severe penalty at execution time, it remains all elegant way to describe many computing processes. This penalty will not deter us from using recursion.
Many such programs are easily translatable so that the recursion is removed and efficiency achieved. All procedures are treated as external, which means that the only means for communication between them is via parameters. This may be somewhat restrictive in practice, but for the purpose of exposition it helps to list all variables explicitly, as either local or parameter. The association of actual to formal parameters will be handled using the call by reference rule.
This means that at run time the address of each parameter is passed to the called procedure. Parameters which are constants or values of expressions are stored into internally generated words whose addresses are then passed to the procedure. We avoid the problem of defining a "format" statement as we will need only the simplest form of input and output.
The command stop halts execution of the currently executing procedure. Comments may appear anywhere on a line enclosed by double slashes, e. An n-dimensional array A with lower and upper bounds li, ui, 1 i n may be declared by using the syntax declare A l1: We have avoided introducing the record or structure concept.
These are often useful features and when available they should be used. However, we will persist in building up a structure from the more elementary array concept. Since most of the SPARKS programs will be read many more times than they will be executed, we have tried to make the code readable. This is a goal which should be aimed at by everyone who writes programs. The SPARKS language is rich enough so that one can create a good looking program by applying some simple rules of style.
Avoid sentences like ''i is increased by one. See the book The Elements of Programming Style by Kernighan and Plauger for more examples of good rules of programming.
This method uses the philosophy: Surprisingly, this method is in wide use today, with the result that an average programmer on an average job turns out only between five to ten lines of correct code per day.
We hope your productivity will be greater. But to improve requires that you apply some discipline to the process of creating programs.
To understand this process better, we consider it as broken up into five phases: Make sure you understand the information you are given the input and what results you are to produce the output. Try to write down a rigorous description of the input and output which covers all cases.
You are now ready to proceed to the design phase. Designing an algorithm is a task which can be done independently of the programming language you eventually plan to use. In fact, this is desirable because it means you can postpone questions concerning how to represent your data and what a particular statement looks like and concentrate on the order of processing.
You may have several data objects such as a maze, a polynomial, or a list of names. For each object there will be some basic operations to perform on it such as print the maze, add two polynomials, or find a name in the list. Assume that these operations already exist in the form of procedures and write an algorithm which solves the problem according to the requirements.
Use a notation which is natural to the way you wish to describe the order of processing. Can you think of another algorithm? If so, write it down. Next, try to compare these two methods. It may already be possible to tell if one will be more desirable than the other. If you can't distinguish between the two, choose one to work on for now and we will return to the second version later.
You must now choose representations for your data objects a maze as a two dimensional array of zeros and ones, a polynomial as a one dimensional array of degree and coefficients, a list of names possibly as an array and write algorithms for each of the operations on these objects.
The order in which you do this may be crucial, because once you choose a representation, the resulting algorithms may be inefficient. Modern pedagogy suggests that all processing which is independent of the data representation be written out first. By postponing the choice of how the data is stored we can try to isolate what operations depend upon the choice of data representation. You should consider alternatives, note them down and review them later.
Finally you produce a complete version of your first program. It is often at this point that one realizes that a much better program could have been built. Perhaps you should have chosen the second design alternative or perhaps you have spoken to a friend who has done it better. This happens to industrial programmers as well. If you have been careful about keeping track of your previous work it may not be too difficult to make changes. One of the criteria of a good design is file: It is usually hard to decide whether to sacrifice this first attempt and begin again or just continue to get the first version working.
Different situations call for different decisions, but we suggest you eliminate the idea of working on both at the same time. If you do decide to scrap your work and begin again, you can take comfort in the fact that it will probably be easier the second time. In fact you may save as much debugging time later on by doing a new version now.
This is a phenomenon which has been observed in practice. The graph in figure 1. For each compiler there is the time they estimated it would take them and the time it actually took.
For each subsequent compiler their estimates became closer to the truth, but in every case they underestimated. Unwarrented optimism is a familiar disease in computing. But prior experience is definitely helpful and the time to build the third compiler was less than one fifth that for the first one. Verification consists of three distinct aspects: Each of these is an art in itself.
Before executing your program you should attempt to prove it is correct. Proofs about programs are really no different from any other kinds of proofs, only the subject matter is different. If a correct proof can be obtained, then one is assured that for all possible combinations of inputs, the program and its specification agree.
Testing is the art of creating sample data upon which to run your program. If the program fails to respond correctly then debugging is needed to determine what went wrong and how to correct it. One proof tells us more than any finite amount of testing, but proofs can be hard to obtain. Many times during the proving process errors are discovered in the code.
The proof can't be completed until these are changed. This is another use of program proving, namely as a methodology for discovering errors. Finally there may be tools available at your computing center to aid in the testing process. One such tool instruments your source code and then tells you for every data set: As a minimal requirement, the test data you construct should force every statement to execute and every condition to assume the value true and false at least once.
One thing you have forgotten to do is to document. But why bother to document until the program is entirely finished and correct? Because for each procedure you made some assumptions about its input and output.
If you have written more than a few procedures, then you have already begun to forget what those assumptions were. If you note them down with the code, the problem of getting the procedures to work together will be easier to solve.
The larger the software, the more crucial is the need for documentation. The previous discussion applies to the construction of a single procedure as well as to the writing of a large software system. Let us concentrate for a while on the question of developing a single procedure which solves a specific task.
This shifts our emphasis away from the management and integration of the file: The design process consists essentially of taking a proposed solution and successively refining it until an executable program is achieved. The initial solution may be expressed in English or some form of mathematical notation.
At this level the formulation is said to be abstract because it contains no details regarding how the objects will be represented and manipulated in a computer.
If possible the designer attempts to partition the solution into logical subtasks.
Each subtask is similarly decomposed until all tasks are expressed within a programming language. This method of design is called the top-down approach. Inversely, the designer might choose to solve different parts of the problem directly in his programming language and then combine these pieces into a complete program. This is referred to as the bottom-up approach. Experience suggests that the top-down approach should be followed when creating a program.
However, in practice it is not necessary to unswervingly follow the method. A look ahead to problems which may arise later is often useful.
Underlying all of these strategies is the assumption that a language exists for adequately describing the processing of data at several abstract levels. Let us examine two examples of top-down program development. Suppose we devise a program for sorting a set of n given by the following 1 distinct integers. One of the simplest solutions is "from those integers which remain unsorted, find the smallest and place it next in the sorted list" This statement is sufficient to construct a sorting program.
However, several issues are not fully specified such as where and how the integers are initially stored and where the result is to be placed. One solution is to store the values in an array in such a way that the i-th integer is stored in the i-th array position, A i 1 i n.
We are now ready to give a second refinement of the solution: There now remain two clearly defined subtasks: This latter problem can be solved by the code file: Eventually A n is compared to the current minimum and we are done.
Let us develop another program. We assume that we have n 1 distinct integers which are already sorted and stored in the array A 1: By making use of the fact that the set is sorted we conceive of the following efficient method: There are three possibilities. Continue in this way by keeping two pointers, lower and upper, to indicate the range of elements not yet tested. This method is referred to as binary search. Note how at each stage the number of elements in the remaining set is decreased by about one half.
As we enter this loop and as long as x is not found the following holds: This, combined with the above assertion implies that x is not present. Unfortunately a complete proof takes us beyond our scope but for those who wish to pursue program proving they should consult our references at the end of this chapter. Recursion We have tried to emphasize the need to structure a program to make it easier to achieve the goals of readability and correctness. Actually one of the most useful syntactical features for accomplishing this is the procedure.
Given a set of instructions which perform a logical operation, perhaps a very complex and long operation, they can be grouped together as a procedure. The procedure name and its parameters file: Given the input-output specifications of a procedure, we don't even have to know how the task is accomplished, only that it is available. This view of the procedure implies that it is invoked, executed and returns control to the appropriate place in the calling procedure. What this fails to stress is the fact that procedures may call themselves direct recursion before they are done or they may call other procedures which again invoke the calling procedure indirect recursion.
These recursive mechanisms are extremely powerful, but even more importantly, many times they can express an otherwise complex process very clearly. For these reasons we introduce recursion here. Most students of computer science view recursion as a somewhat mystical technique which only is useful for some very special class of problems such as computing factorials or Ackermann's function. This is unfortunate because any program that can be written using assignment, the if-then-else statement and the while statement can also be written using assignment, if-then-else and recursion.
Of course, this does not say that the resulting program will necessarily be easier to understand. However, there are many instances when this will be the case.
When is recursion an appropriate mechanism for algorithm exposition? One instance is when the problem itself is recursively defined. Given a set of n 1 elements the problem is to print all possible permutations of this set.
It is easy to see that given n elements there are n! A simple algorithm can be achieved by looking at the case of four elements a,b,c,d. The answer is obtained by printing i a followed by all permutations of b,c,d ii b followed by all permutations of a,c,d iii c followed by all permutations of b,a,d iv d followed by all permutations of b,c,a The expression "followed by all permutations" is the clue to recursion.
It implies that we can solve the problem for a set with n elements if we had an algorithm which worked on n - 1 elements. A is a character string e.
Then try to do one or more of the exercises at the end of this chapter which ask for recursive procedures. We will see several important examples of such structures, especially lists in section 4. Another instance when recursion is invaluable is when we want to describe a backtracking procedure. But for now we will content ourselves with examining some simple, iterative programs and show how to eliminate the iteration statements and replace them by recursion.
This may sound strange, but the objective is not to show that the result is simpler to understand nor more efficient to execute. The main purpose is to make one more familiar with the execution of a recursive procedure. Suppose we start with the sorting algorithm presented in this section.
To rewrite it recursively the first thing we do is to remove the for loops and express the algorithm using assignment, if-then-else and the go-to statement.
Every place where a ''go to label'' appears, we replace that statement by a call of the procedure associated with that label. This gives us the following set of three procedures. Procedure MAXL2 is also directly reculsive. These two procedures use eleven lines while the original iterative version was expressed in nine lines; not much of a difference.
Notice how in MAXL2 the fourth parameter k is being changed. The effect of increasing k by one and restarting the procedure has essentially the same effect as the for loop.
Now let us trace the action of these procedures as they sort a set of five integers When a procedure is invoked an implicit branch to its beginning is made. Thus a recursive call of a file: The parameter mechanism of the procedure is a form of assignment. In section 4. Also in that section are several recursive procedures, followed in some cases by their iterative equivalents. Rules are also given there for eliminating recursion.
There are many criteria upon which we can judge a program, for instance: The above criteria are all vitally important when it comes to writing software, most especially for large systems. Though we will not be discussing how to reach these goals, we will try to achieve them throughout this book with the programs we write. Hopefully this more subtle approach will gradually infect your own program writing habits so that you will automatically strive to achieve these goals.
There are other criteria for judging programs which have a more direct relationship to performance. These have to do with computing time and storage requirements of the algorithms. Performance evaluation can be loosely divided into 2 major phases: Both of these are equally important. First consider a priori estimation. We would like to determine two numbers for this statement.
The first is the amount of time a single execution will take; the second is the number of times it is executed. The product of these numbers will be the total time taken by this statement. The second statistic is called the frequency count, and this may file: One of the hardest tasks in estimating frequency counts is to choose adequate samples of data.
It is impossible to determine exactly how much time it takes to execute any command unless we have the following information: It is possible to determine these figures by choosing a real machine and an existing compiler.
Another approach would be to define a hypothetical machine with imaginary execution times , but make the times reasonably close to those of existing hardware so that resulting figures would be representative. Neither of these alternatives seems attractive. In both cases the exact times we would determine would not apply to many machines or to any machine. Also, there would be the problem of the compiler, which could vary from machine to machine.
Moreover, it is often difficult to get reliable timing figures because of clock limitations and a multi-programming or time sharing environment. Finally, the difficulty of learning another machine language outweighs the advantage of finding "exact" fictitious times.
All these considerations lead us to limit our goals for an a priori analysis. Instead, we will concentrate on developing only the frequency count for all statements. The anomalies of machine configuration and language will be lumped together when we do our experimental studies. Parallelism will not be considered. Consider the three examples of Figure 1.
Three simple programs for frequency counting. Then its frequency count is one. In program b the same statement will be executed n times and in program c n2 times assuming n 1. In our analysis of execution we will be concerned chiefly with determining the order of magnitude of an algorithm. This means determining those statements which may have the greatest frequency count. To determine the order of magnitude, formulas such as often occur.
In the program segment of figure 1. The Fibonacci sequence starts as 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, Each new term is obtained by taking the sum of the two previous terms. The program on the following page takes any non-negative integer n and prints the value Fn. A complete set would include four cases: Below is a table which summarizes the frequency counts for the first three cases. None of them exercises the program very much.
Notice, though, how each if statement has two parts: These may have different execution counts. At this point the for loop will actually be entered. Steps 1, 2, 3, 5, 7 and 9 will be executed once, but steps 4, 6 and 8 not at all. Both commands in step 9 are executed once. Now, for n 2 how often is step 10 executed: Thus, steps 11, 12, 13 and 14 will be executed n - 1 times but step 10 will be done n times. We can summarize all of this with a table. Step Frequency Step Frequency 1 9 2 2 1 10 n 3 1 11 n-1 4 0 12 n-1 5 1 13 n-1 6 0 14 n-1 7 1 15 1 file: Execution Count for Computing Fn Each statement is counted once, so step 9 has 2 statements and is executed once for a total of 2.
Clearly, the actual time taken by each statement will vary. The for statement is really a combination of several statements, but we will count it as one. We will often write this as O n , ignoring the two constants 5. This notation means that the order of magnitude is proportional to n. When we say that the computing time of an algorithm is O g n we mean that its execution takes no more than a constant times g n.
For example n might be the number of inputs or the number of outputs or their sum or the magnitude of one of them. We write O 1 to mean a computing time which is a constant.
O n is called linear, O n2 is called quadratic, O n3 is called cubic, and O 2n is called exponential. If an algorithm takes time O log n it is faster, for sufficiently large n, than if it had taken O n. Similarly, O n log n is better than O n2 but not as good as O n. These seven computing times, O 1 , O log n , O n , O n log n , O n2 , O n3 , and O 2n are the ones we will see most often throughout the book. If we have two algorithms which perform the same task, and the first has a computing time which is O n and the second O n2 , then we will usually take the first as superior.
The reason for this is that as n increases the time for the second algorithm will get far worse than the time for the first. This shows why we choose the algorithm with the smaller order of magnitude, but we emphasize that this is not the whole story. For small data sets, the respective constants must be carefully determined. In practice these constants depend on many factors, such as the language and the machine one is using.
Thus, we will usually postpone the establishment of the constant until after the program has been written. Then a performance profile can be gathered using real time calculation. Figures 1. Notice how the times O n and O n log n grow much more slowly than the others. For large data sets, algorithms with a complexity greater than O n log n are often impractical. An algorithm which is exponential will work only for very small inputs.
Given an algorithm, we analyze the frequency count of each statement and total the sum. Another valid performance measure of an algorithm is the space it requires. Often one can trade space for time, getting a faster algorithm but using more space.
We will see cases of this in subsequent chapters. A magic square is an n x n matrix of the integers 1 to n2 such that the sum of every row, column and diagonal is the same.
When n is odd H. Coxeter has given a simple rule for generating a magic square: The statement i,j k,l is a shorthand way of writing i k; j l. It emphasizes that the variables are thought of as pairs and are changed as a unit. The file: The magic square is represented using a two dimensional array having n rows and n column. For this application it is convenient to number the rows and columns from zero to n - 1 rather than from one to n.
The while loop is governed by the variable key which is an integer variable initialized to 2 and increased by one each time through the loop. Thus each statement within the while loop will be executed no more than n2 - 1 times and hence the computing time for MAGIC is O n2.
Since there are n2 positions in which the algorithm must place a number, we see that O n2 is the best bound an algorithm could have. Fundamental Algorithms, by D. Knuth, vol. For a discussion of good programming techniques see Structured Programming by O.
Dahl, E. Dijkstra, and C. Hoare, Academic Press, The Elements of Programming Style by B. Kernighan and P. Plauger, McGraw-Hill, Programming, vol. For a discussion of tools and procedures for developing very large software systems see Practical Strategies for Developing Large Software Systems, by E. Horowitz, Addison-Wesley, May, For a discussion of the more abstract formulation of data structures see "Toward an understanding of data structures" by J.
Earley, CACM, vol. Mealy, Proc. For a further discussion of program proving see file: Floyd, Proc. Schwartz, ed. Good, R. London, W. Look up the word algorithm or its older form algorism in the dictionary. Consider the two statements: Both do not satisfy one of the five criteria of an algorithm.
Which criteria do they violate? Describe the flowchart in figure 1. Can you do this without using the go to? Now make it into an algorithm. Discuss how you would actually represent the list of name and telephone number pairs in a real machine. How would you handle people with the same last name. Can you think of a clever meaning for S. Concentrate on the letter K first. The rule is: Determine how many times each statement is executed.
Given n boolean variables x1, Determine when the second becomes larger than the first. What is the computing time of your method? If x occurs, then set j to its position in the array else set j to zero.
Try writing this without using the go to statement. If x, y are variables of type character, then we might like to implement the procedures: Strings x and y remain unchanged. String x is unchanged. Implement these procedures using the array facility. Represent your answer in the array ANS 1: Trace the action of the procedure below on the elements 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 searching for l, 3, 13 and Prove by induction: List as many rules of style in programming that you can think of that you would be willing to follow yourself.
Using the notation introduced at the end of section 1. NOT X:: Take any version of binary search, express it using assignment, if-then-else and go to and then give file: Analyze the computing time of procedure SORT as given in section 1.
Write a recursive procedure for computing the binomial coefficient where. Analyze the time and space requirements of your algorithm. Ackermann's function A m,n is defined as follows: This function is studied because it grows very fast for small values of m and n. Write a recursive procedure for computing this function. Then write a nonrecursive algorithm for computing Ackermann's function. Tower of Hanoi There are three towers and sixty four disks of different diameters placed on the first tower.
The disks are in order of decreasing diameter as one scans up the tower. Monks were reputedly supposed to move the disks from tower 1 to tower 3 obeying the rules: Write a recursive procedure which prints the sequence of moves which accomplish this task. Give an algorithm which finds the values a, b for which the range values are equal.
Given n, a positive integer determine if n is the sum of all of its divisors; i. If S is a set of n elements the powerset of S is the set of all possible subsets of S. Write a recursive procedure to compute powerset S. The array is often the only means for structuring data which is provided in a programming language. Therefore it deserves a significant amount of attention.
If one asks a group of programmers to define an array, the most often quoted saying is: This is unfortunate because it clearly reveals a common point of confusion, namely the distinction between a data structure and its representation.
It is true that arrays are almost always implemented by using consecutive memory, but not always. Intuitively, an array is a set of pairs, index and value. For each index which is defined, there is a value associated with that index.
In mathematical terms we call this a correspondence or a mapping. However, as computer scientists we want to provide a more functional definition by giving the operations which are permitted on this data structure. For arrays this means we are concerned with only two operations which retrieve and store values. Using our notation this object can be defined as: STORE is used to enter new index-value pairs.
Notice how the axioms are independent of any representation scheme. Also, i and j need not necessarily be integers, but we assume only that an EQUAL function can be devised.
If we interpret the indices to be n-dimensional, i1,i2, In section 2. If we consider an ordered list more abstractly, we say that it is either empty or it can be written as a1,a2,a3, There are a variety of operations that are performed on these lists. These operations include: It is not always necessary to be able to perform all of these operations; many times a subset will suffice.
In the study of data structures we are interested in ways of representing ordered lists so that these operations can be carried out efficiently. Perhaps the most common way to represent an ordered list is by an array where we associate the list element ai with the array index i.
This gives us the ability to retrieve or modify the values of random elements in the list in a constant amount of time, essentially because a computer memory has random access to any word. We can access the list element values in either direction by changing the subscript values in a controlled way. It is only operations v and vi which require real effort. Insertion and deletion using sequential allocation forces us to move some of the remaining elements so the sequential mapping is preserved in its proper form.
It is precisely this overhead which leads us to consider nonsequential mappings of ordered lists into arrays in Chapter 4. Let us jump right into a problem requiring ordered lists which we will solve by using one dimensional arrays.
This problem has become the classical example for motivating the use of list processing techniques which we will see in later chapters. Therefore, it makes sense to look at the problem and see why arrays offer only a partially adequate solution. The problem calls for building a set of subroutines which allow for the manipulation of symbolic polynomials. By "symbolic," we mean the list of coefficients and exponents which accompany a polynomial, e. We will also need input and output routines and some suitable format for preparing polynomials as input.
The first step is to consider how to define polynomials as a computer structure. For a mathematician a polynomial is a sum of terms where each term has the form axe; x is the variable, a is the coefficient and e is the exponent.
However this is not an appropriate definition for our purposes. When defining a data object one must decide what functions will be available, what their input is, what their output is and exactly what it is that they do. A complete specification of the data structure polynomial is now given. Notice the absense of any assumptions about the order of exponents, about nonzero coefficients, etc. These assumptions are decisions of representation. Suppose we wish to remove from P those terms having exponent one.
These axioms are valuable in that they describe the meaning of each operation concisely and without implying an implementation.
Note how trivial the addition and multiplication operations have become. Now we can make some representation decisions. Exponents should be unique and in decreasing order is a very reasonable first decision.
Now assuming a new function EXP poly exp which returns the leading exponent of poly, we can write a version of ADD which is expressed more like program, but is still representation independent.
The case statement determines how the exponents are related and performs the proper action. Since the tests within the case statement require two terms, if one polynomial gets exhausted we must exit and the remaining terms of the other can be copied directly into the result. With these insights, suppose we now consider the representation question more carefully.