## Looking for:

Data structures and algorithms by aho hopcroft and ullman free download

WebData structures algorithms in java with cdrom (mitchell waite signature) by robert lafore. . AdBrowse Data Structures and Algorithms Specializations. Sign up & Get Started Today! WebData Structures and Algorithms – Alfred V. Aho, John E. Hopcroft and Jeffrey D. .

###
Data structures and algorithms by aho hopcroft and ullman free download

WebData structures algorithms in java with cdrom (mitchell waite signature) by robert lafore. . AdBrowse Data Structures and Algorithms Specializations. Sign up & Get Started Today! WebData Structures and Algorithms – Alfred V. Aho, John E. Hopcroft and Jeffrey D. .

####
Data Structures and Algorithms – Alfred V. Aho, John E. Hopcroft and Jeffrey D. replace.me – References

About the authors Follow authors to get new release updates, plus improved recommendations. Alfred V. Brief content visible, double tap to read full content. Full content visible, double tap to read brief content.

Bio from Wikipedia, the free encyclopedia. See more on the author’s page. Jeffrey D. John E. Customer reviews. How are ratings calculated? Instead, our system considers things like how recent a review is and if the reviewer bought the item on Amazon.

It also analyzes reviews to verify trustworthiness. Top reviews Most recent Top reviews. Top reviews from Canada.

There was a problem filtering reviews right now. Please try again later. These people are among the greatest researchers and teachers in Computer Science, and this book is a great opportunity to ‘learn from the masters’.

As an introduction to the fascinating field of Data Structures and Algorithms, this is perhaps the best textbook you’ll find out there. Starting with the basics, the authors develop the concepts in a natural manner. Array, lists and stacks soon give way to binary trees, heaps and then more advanced data structures. All data structures are introduced with proper motivation in terms of the kind of problems that they are useful in solving. The basic algorithms in searching, sorting, and graphs are then presented in detail, followed by a chapter on algorithm analysis techniques, and one on design paradigms such as dynamic programming, backtracking, divide and conquer, greedy approach, and local search.

The book ends with chapters data structures and algorithms for external storage and memory management.

This is a textbook, and therefore you can expect a fair amount of maths in the analysis of algorithms, without which you can only do hand-waving. All algorithms are explained, with detailed examples and illustrations – this is one of the easiest books to follow in theoretical computer science. All algorithms are presented in pseudocode, which makes it easier to understand things at an abtract level without getting bogged down in language specific technical details, and the pseudocode is very clear and concise, making it an easy task to adapt it to any given language.

An additional plus-point is its size – weighing in at less than pages, this is a ‘backpack friendly’ book which you can easily carry around, unlike many others on the subject.

The only caveat is that the book is almost 20 years old, so you won’t find the more recent topics like red-black trees, skip lists etc. I’d suggest using this book for an introduction to the basics, with the book by Cormen et al if you want the maths or Sedgewick if you don’t want the maths as excellent supplements as well as advanced references. I must confess to having a weak spot for this book, since it introduced me to algorithms and i fell in love with the subject.

However, i think most people who’ve read it would agree that it is a classic among Computer Science textbooks which has stood the test of time. This book stands out among countless books written on the subject. It basically deals with the two ingrediants of programming , Data Structures and Algorithms.

The first part gives a wonderful introduction to the concept of Data Structures. It goes on to deal with several classes of Data Structures such as lists, stacks, queues, trees and graphs.. This book is profusely illustrated with examples, and lots of exercises for the student to expand upon the ideas. The next part goes on to deal with Algorithms. Beginning with the concept, approaches, and different metrics that quantify algorithms such as time and space complexity.

Common problems such as sorting , searching and the different algorithms are elaborated with the pros and cons. One nice thing about the text is the way the subject is presented to the reader. Data structures are created by giving names to aggregates of cells and optionally interpreting the values of some cells as representing connections e. The simplest aggregating mechanism in Pascal and most other programming languages is the one-dimensional array, which is a sequence of cells of a given type, which we shall often refer to as the celltype.

We can think of an array as a mapping from an index set such as the integers 1, 2,. A cell within an array can be referenced by giving the array name together with a value from the index set of the array. In Pascal the index set may be an enumerated type, such as north, east, south, west , or a subrange type, such as The values in the cells of an array can be of any one type.

Thus, the declaration. Incidentally, Pascal is somewhat unusual in its richness of index types. Many languages allow only subrange types finite sets of consecutive integers as index types. For example, to index an array by letters in Fortran, one must simulate the effect by using integer indices, such as by using index 1 to stand for ‘A’, 2 to stand for ‘B’, and so on.

Another common mechanism for grouping cells in programming languages is the record structure. A record is a cell that is made up of a collection of cells, called fields, of possibly dissimilar types.

Records are often grouped into arrays; the type defined by the aggregation of the fields of a record becomes the “celltype” of the array. For example, the Pascal declaration. A third grouping method found in Pascal and some other languages is the file. The file, like the one-dimensional array, is a sequence of values of some particular type.

However, a file has no index type; elements can be accessed only in the order of their appearance in the file. In contrast, both the array and the record are “random-access” structures, meaning that the time needed to access a component of an array or record is independent of the value of the array index or field selector. The compensating benefit of grouping by file, rather than by array, is that the number of elements in a file can be time-varying and unlimited.

Pointers and CursorsIn addition to the cell-grouping features of a programming language, we can represent relationships between cells using pointers and cursors. A pointer is a cell whose value indicates another cell. When we draw pictures of data structures, we indicate the fact that cell A is a pointer to cell B by drawing an arrow from A to B. In Pascal, we can create a pointer variable ptr that will point to cells of a given type, say celltype, by the declaration. A postfix up-arrow is used in Pascal as the dereferencing operator, so the expression ptr denotes the value of type celltype in the cell pointed to by ptr.

A cursor is an integer-valued cell, used as a pointer to an array. As a method of connection, the cursor is essentially the same as a pointer, but a cursor can be used in languages like Fortran that do not have explicit pointer types as Pascal does.

By treating a cell of type integer as an index value for some array, we effectively make that cell point to one cell of the array. This technique, unfortunately, works only when cells of arrays are pointed to; there is no reasonable way to interpret an integer as a “pointer” to a cell that is not part of an array.

We shall draw an arrow from a cursor cell to the cell it “points to. The reader should observe that the Pascal pointer mechanism is such that cells in arrays can only be. The purpose of the field next in reclist is to point to another record in the array. For example, reclist[4]. Assuming record 4 is first, the next field of reclist orders the records 4, 1, 3, 2.

Note that the next field is 0 in record 2, indicating that there is no following record. It is a useful convention, one we shall adopt in this book, to use 0 as a “NIL pointer,” when cursors are being used. This idea is sound only if we also make the convention that arrays to which cursors “point” must be indexed starting at 1, never at 0.

The chain is pointed to by a variable named header, which is of type record-type; header points to an anonymous record of type recordtype. That record has a value 4 in its cursor field; we regard this 4 as an index into the array reclist. The record has a true pointer in field ptr to another anonymous record. The record pointed to has an index in its cursor field indicating position 2 of reclist; it also has a nil pointer in its ptr field.

On what basis should we choose? There are two often contradictory goals. We would like an algorithm that makes efficient use of the computer’s resources, especially, one that runs as fast as possible. When we are writing a program to be used once or a few times, goal 1 is most important.

The cost of the programmer’s time will most likely exceed by far the cost of running the program, so the cost to optimize is the cost of writing the program. When presented with a problem whose solution is to be used many times, the cost of running the program may far exceed the cost of writing it, especially, if many of the program runs are given large amounts of input.

Then it is financially sound to implement a fairly complicated algorithm, provided that the resulting program will run significantly faster than a more obvious program.

Even in these situations it may be wise first to implement a simple algorithm, to determine the actual benefit to be had by writing a more complicated program. In building a complex system it is often desirable to implement a simple prototype on which measurements and simulations can be performed, before committing oneself to the final design.

It follows that programmers must not only be aware of ways of making programs run fast, but must know when to apply these techniques and when not to bother. The fact that running time depends on the input tells us that the running time of a program should be defined as a function of the input.

Often, the running time depends not on the exact input but only on the “size” of the input. A good example is the process known as sorting, which we shall discuss in Chapter 8. In a sorting problem, we are given as input a list of items to be sorted, and we are to produce as output the same items, but smallest or largest first. For example, given 2, 1, 3, 1, 5, 8 as input we might wish to produce 1, 1, 2, 3, 5, 8 as output. The latter list is said to be sorted smallest first.

The natural size measure for inputs to a sorting program is the number of items to be sorted, or in other words, the length of the input list. In general, the length of the input is an appropriate size measure, and we shall assume that measure of size unless we specifically state otherwise. It is customary, then, to talk of T n , the running time of a program on inputs of size n. The units of T n will be left unspecified, but we can think of T n as being the number of instructions executed on an idealized computer.

In that case we define T n to be the worst case running time, that is, the maximum, over all inputs of size n, of the running time on that input. We also consider Tavg n , the average, over all inputs of size n, of the running time on that input. While Tavg n appears a fairer measure, it is often fallacious to assume that all inputs are equally likely. In practice, the average running time is often much harder to determine than the worst-case running time, both because the analysis becomes mathematically intractable and because the notion of “average” input frequently has no obvious meaning.

Thus, we shall use worst-case running time as the principal measure of time complexity, although we shall mention average-case complexity wherever we can do so meaningfully. Now let us consider remarks 2 and 3 above: that the running time of a program depends on the compiler used to compile the program and the machine used to execute it.

These facts imply that we cannot express the running time T n in standard time units such as seconds. Rather, we can only make remarks like “the running time of such-and-such an algorithm is proportional to n2. Big-Oh and Big-Omega NotationTo talk about growth rates of functions we use what is known as “big-oh” notation. For example, when we say the running time T n of some program is O n2 , read “big oh of n squared” or just “oh of n squared,” we mean that there are positive constants c and n0 such that for n equal to or greater than n0, we have T n cn2.

In what follows, we assume all running-time functions are defined on the nonnegative integers, and their values are always nonnegative, although not necessarily integers. We say that T n is O f n if there are constants c and n0 such that T n cf n whenever n n0. A program whose running time is O f n is said to have growth rate f n. We could also say that this T n is O n4 , but this would be a weaker statement than saying it is O n3.

As another example, let us prove that the function 3n is not O 2n. Suppose that there were constants n0 and c such that for all n n0, we had 3n c2n. When we say T n is O f n , we know that f n is an upper bound on the growth rate of T n. To specify a lower bound on the growth rate of T n we can use the notation T n is g n , read “big omega of g n ” or just “omega of g n ,” to mean that there exists a positive constant c such that T n cg n infinitely often for an infinite number of values of n.

The Tyranny of Growth RateWe shall assume that programs can be evaluated by comparing their running-time functions, with constants of proportionality neglected. Under this assumption a program with running time O n2 is better than one with running time O n3 , for example. Besides constant factors due to the compiler and machine, however, there is a constant factor due to the nature of the program itself. It is possible, for example, that with a particular compiler-machine combination, the first program takes n2 milliseconds, while the second takes 5n3 milliseconds.

Might not the 5n3 program be better than the n2 program? The answer to this question depends on the sizes of inputs the programs are expected to process.

Therefore, if the program is to be run mainly on inputs of small size, we would indeed prefer the program whose running time was O n3. Thus, as the size of the input increases, the O n3 program will take significantly more time than the O n2 program. If there are even a few large inputs in the mix of problems these two programs are designed to solve, we can be much better off with the program whose running time has the lower growth rate. Another reason for at least considering programs whose growth rates are as low as possible is that the growth rate ultimately determines how big a problem we can solve on a computer.

Put another way, as computers get faster, our desire to solve larger problems on them continues to increase. However, unless a program has a low growth rate such as O n or O nlogn , a modest increase in computer speed makes very little difference in the size of the largest problem we can solve in a fixed amount of time. Suppose we can afford seconds, or about 17 minutes, to solve a given problem. How large a problem can we solve? In seconds, each of the four algorithms can solve roughly the same size problem, as shown in the second column of Fig.

Suppose that we now buy a machine that runs ten times faster at no additional cost. Then for the same cost we can spend seconds on a problem where we spent seconds before. The maximum size problem we can now solve using each of the four programs is shown in the third column of Fig.

Additional factors of ten speedup in the computer yield an even smaller percentage increase in problem size. In effect, the O 2n program can solve only small problems no matter how fast the underlying computer. In the third column of Fig.

These ratios will be maintained for additional increases in speed. As long as the need for solving progressively larger problems exists, we are led to an almost paradoxical conclusion. As computation becomes cheaper and machines become faster, as will most surely continue to happen, our desire to solve larger and more complex problems will continue to increase.

Thus, the discovery and use of efficient algorithms, those whose growth rates are low, becomes more rather than less important. A Few Grains of SaltWe wish to re-emphasize that the growth rate of the worst case running time is not the sole, or necessarily even the most important, criterion for evaluating an algorithm or program. Let us review some conditions under which the running time of a program can be overlooked in favor of other issues.

If a program is to be used only a few times, then the cost of writing and debugging dominate the overall cost, so the actual running time rarely affects the total cost. In this case, choose the algorithm that is easiest to implement correctly. If a program is to be run only on “small” inputs, the growth rate of the running time may be less important than the constant factor in the formula for running time. What is a “small” input depends on the exact running times of the competing algorithms.

There are some algorithms, such as the integer multiplication algorithm due to Schonhage and Strassen [], that are asymptotically the most efficient known for their problem, but have never been used in practice even on the largest problems, because the constant of proportionality is so large in comparison to other simpler, less “efficient” algorithms. A complicated but efficient algorithm may not be desirable because a person other than the writer may have to maintain the program later. It is hoped that by making the principal techniques of efficient algorithm design widely known, more complex algorithms may be used freely, but we must consider the possibility of an entire program becoming useless because no one can understand its subtle but efficient algorithms.

There are a few examples where efficient algorithms use too much space to be implemented without using slow secondary storage, which may more than negate the efficiency. In practice, however, determining the running time of a program to within a constant factor is usually not that difficult; a few basic principles suffice. Before presenting these principles, it is important that we learn how to add and multiply in “big oh” notation. To see why, observe that for some constants c1, c2, n1, and n2, if n n1 then T1 n c1f n , and if n n2 then T2 n c2g n.

The rule for sums given above can be used to calculate the running time of a sequence of program steps, where each step may be an arbitrary program fragment with loops and branches. Suppose that we have three steps whose running times are, respectively, O n2 , O n3 and O n log n.

Then the running time of the first two steps executed sequentially is O max n2, n3 which is O n3. The running time of all three together is O max n3, n log n which is O n3. In general, the running time of a fixed sequence of steps is, to within a constant factor, the running time of the step with the largest running time. In rare circumstances there will be two or more steps whose running times are incommensurate neither is larger than the other, nor are they equal.

For example, we could have steps of running times O f n and O g n , where. In such cases the sum rule must be applied directly; the running time is O max f n , g n , that is, n4 if n is even and n3 if n is odd.

The rule for products is the following. The reader should prove this fact using the same ideas as in the proof of the sum rule. It follows from the product rule that O cf n means the same thing as O f n if c is any positive constant. Before proceeding to the general rules for analyzing the running times of programs, let us take a simple example to get an overview of the process.

Consider the sorting program bubble of Fig. The net effect of each pass of the inner loop of statements 3 – 6 is to “bubble” the smallest element toward the front of the array. The number n of elements to be sorted is the appropriate measure of input size. The first observation we make is that each assignment statement takes some constant amount of time, independent of the input size. That is to say, statements 4 , 5 and 6 each take O 1 time.

Note that O 1 is “big oh” notation for “some constant amount. Now we must take into account the conditional and looping statements. The if- and for-statements are nested within one another, so we may work from the inside out to get the running time of the conditional group and each loop. For the if-statement, testing the condition requires O 1 time. We don’t know whether the body of the if-statement lines 4 – 6 will be executed.

Since we are looking for the worst-case running time, we assume the worst and suppose that it will. Thus, the if-group of statements 3 – 6 takes O 1 time.

Proceeding outward, we come to the for-loop of lines 2 – 6. The general rule for a loop is that the running time is the sum, over each iteration of the loop, of the time spent executing the loop body for that iteration.

We must, however, charge at least O 1 for each iteration to account for incrementing the index, for testing to see whether the limit has been reached, and for jumping back to the beginning of the loop. For lines 2 – 6 the loop body takes O 1 time for each iteration. The number of iterations of the loop is n-i, so by the product rule, the time spent in the loop of lines 2 – 6 is O n-i X 1 which is O n-i.

Now let us progress to the outer loop, which contains all the executable statements of the program. Statement 1 is executed n – 1 times, so the total running time of the program is bounded above by some constant times. The program of Fig. In Chapter 8, we shall give sorting programs whose. Before proceeding to some general analysis rules, let us remember that determining a precise upper bound on the running time of programs is sometimes simple, but at other times it can be a deep intellectual challenge.

There are no complete sets of rules for analyzing programs. We can only give the reader some hints and illustrate some of the subtler points by examples throughout this book. Now let us enumerate some general rules for the analysis of programs. The only permissible parameter for the running time of the whole program is n, the input size.

The running time of each assignment, read, and write statement can usually be taken to be O 1. The running time of a sequence of statements is determined by the sum rule. That is, the running time of the sequence is, to within a constant factor, the largest running time of any statement in the sequence. The running time of an if-statement is the cost of the conditionally executed statements, plus the time for evaluating the condition.

The time to evaluate the condition is normally O 1. The time for an if-then-else construct is the time to evaluate the condition plus the larger of the time needed for the statements executed when the condition is true and the time for the statements executed when the condition is false. The time to execute a loop is the sum, over all times around the loop, of the time to execute the body and the time to evaluate the condition for termination usually the latter is O 1.

Often this time is, neglecting constant factors, the product of the number of times around the loop and the largest possible time for one execution of the body, but we must consider each loop separately to make sure. The number of iterations around a loop is usually clear, but there are times when the number of iterations cannot be computed precisely. It could even be that the program is not an algorithm, and there is no limit to the number of times we go around certain loops.

Procedure CallsIf we have a program with procedures, none of which is recursive, then we can compute the running time of the various procedures one at a time, starting with those procedures that make no calls on other procedures. Remember to count a function invocation as a “call.

We can. We continue this process, evaluating the running time of each procedure after the running times of all procedures it calls have been evaluated. If there are recursive procedures, then we cannot find an ordering of all the procedures so that each calls only previously evaluated procedures.

What we must now do is associate with each recursive procedure an unknown time function T n , where n measures the size of the arguments to the procedure. We can then get a recurrence for T n , that is, an equation for T n in terms of T k for various values of k.

Techniques for solving many different kinds of recurrences exist; we shall present some of these in Chapter 9. Here we shall show how to analyze a simple recursive program. Figure 1. An appropriate size measure for this function is the value of n. Let T n be the running time for fact n. Thus, for some constants c and d,. We can then use 1. From 1. We should note that in this analysis we have assumed that the multiplication of two integers is an O 1 operation. In practice, however, we cannot use the program in Fig.

The general method for solving recurrence equations, as typified by Example 1. Often we must then sum a series or, if we cannot sum it exactly, get a close upper bound on the sum to obtain an upper bound on T n. Programs with GOTO’sIn analyzing the running time of a program we have tacitly assumed that all flow of control within a procedure was determined by branching and 1ooping constructs.

We relied on this fact as we determined the running time of progressively larger groups of statements by assuming that we needed only the sum rule to group sequences of statements together. Goto statments, however, make the logical grouping of statements more complex.

For this reason, goto statements should be avoided, but Pascal lacks break- and continue-statements to jump out of loops. The goto-statement is often used as a substitute for statements of this nature in Pascal.

We suggest the following approach to handling goto’s that jump from a loop to code that is guaranteed to follow the loop, which is generally the only kind of goto that is justified. As the goto is presumably executed conditionally within the loop, we may pretend that it is never taken. Because the goto takes us to a statement that will be executed after the loop completes, this assumption is conservative; we can never underestimate the worst case running time of the program if we assume the loop runs to completion.

However, it is a rare program in which ignoring the goto is so conservative that it causes us to overestimate the growth rate of the worst case running time for the program. Notice that if we were faced with a goto that jumped back to previously executed code we could not ignore it safely, since that goto may create a loop that accounts for the bulk of the running time.

We should not leave the impression that the use of backwards goto’s by themselves make running times unanalyzable. As long as the loops of a program have a reasonable structure, that is, each pair of loops are either disjoint or nested one within the other, then the approach to running time analysis described in this section will work.

We get the following Theorem. A simple algorithm for this problem could pick any input element k, partition the input into two — the first part being those input elements that are less than x and the second part consisting of input elements greater than x, identify the part that contains the element to be selected, and finally recursively perform an appropriate selection in the part containing the element of interest.

This algorithm can be shown to have an expected i. In general the run time of any divide- and-conquer algorithm will be the best if the sizes of the subproblems are as even as possible. In this simple selection algorithm, it may happen so that one of the two parts is empty at each level of recursion. So, even though this simple algorithm has a good average case run time, in the worst case it can be bad. We will be better off using the merge sort. Say we are given n numbers.

We group these numbers such that there are five numbers in each group. Find the median of each group. Find also the median M of these group medians.

Having found M, we partition the input into two parts X1 and X2. X1 consists of all the input elements that are less than M and X2 contains all the elements greater than M. We can also count the number of elements in X1 and X2 within the same time. This can be argued as follows. Let the input be partitioned into the groups G1 , G2 ,. Assume without loss of generality that every group has exactly n 5 elements. There are 10 groups such that their medians are less than M.

In each such group there are at least three elements that are less than M. Therefore, there 3 are at least 10 n input elements that are less than M. This in turn means that the 7 size of X2 can be at most 10 n.

Similarly, we can also show that the size of X1 is no 7 more than 10 n. Thus we can complete the selection algorithm by performing an appropriate se- lection in either X1 or X2 , recursively, depending whether the element to be selected is in X1 or X2 , respectively.

Then it takes T n5 time to identify the median of medians M. This can be proved by induction. Theorem 5. Three different measures can be conceived of: the best case, the worst case, and the average case. Typically, the average case run time of an algorithm is much smaller than the worst case. While computing the average case run time one assumes a distribution e.

Is it possible to achieve the average case run time without making any assump- tions on the input space? Randomized algorithms answer this question in the affir- mative. They make no assumptions on the inputs. The analysis done of randomized algorithms will be valid for all possible inputs. Randomized algorithms obtain such performances by introducing randomness into the algorithms themselves. Coin flips are made to make certain decisions in randomized algorithms.

A ran- domized algorithm with one possible sequence of outcomes for the coin flips can be thought of as being different from the same algorithm with a different sequence of outcomes for the coin flips. Thus a randomized algorithm can be viewed as a family of algorithms. It should be ensured that, for any input, the number of algorithms in the family bad on this input is only a small fraction of the total number of algo- rithms.

Realize that this probability is independent of the input distribution. Good performance could mean that the algorithm outputs the correct answer, or its run time is small, and so on. Different types of randomized algorithms can be conceived of depending on the interpretation. A Las Vegas algorithm is a randomized algorithm that always outputs the correct answer but whose run time is a random variable possibly with a small mean.

A Monte Carlo algorithm is a randomized algorithm that has a predetermined run time but whose output may be incorrect occasionally. We can modify asymptotic functions such as O. A randomized algorithm is said to use O f n amount of resource like time, space, etc. Illustrative Examples We provide two examples of randomized algorithms. The first is a Las Vegas algorithm and the second is a Monte Carlo algorithm.

The problem is to identify the repeated element. This fact can be proven as follows: Let the input be chosen by an adversary who has perfect knowledge about the algorithm used.

In other words, the algorithm has to examine at least one more element and hence the claim follows. We can design a simple O n time deterministic algorithm for this problem. Then search the individual parts for the repeated element.

Clearly, at least one of the parts will have at least two copies of the repeated element. Now we present a simple and elegant Las Vegas algorithm that takes only O log n time. This algorithm is comprised of stages. Two random numbers i and j are picked from the range [1, n] in any stage.

These numbers are picked independently with replacement. As a result, there is a chance that these two are the same. If so, the repeated element has been found.

If not, the next stage is entered. We repeat the stages as many times as it takes to arrive at the correct answer. Lemma 6. Since each stage takes O 1 time, the run time of the algorithm is O log n. Here also the input is an array a[ ] of n numbers.

The problem is to find an element of the array that is greater than the median. We can assume, without loss of generality, that the array numbers are distinct and that n is even. This sample is picked with replacement.

Find and output the maximum element of S. The claim is that the output of this algorithm is correct with high probability. The basic idea of parallel computing is to partition the given problem into several subproblems, assign a subproblem to each processor, and put together the partial solutions obtained by the individual processors.

If P processors are used to solve a problem, then there is a potential of reducing the run time by a factor of up to P. If S is the best known sequential run time i. If not, we can simulate the parallel algorithm using a single processor and get a run time better than S which will be a contradiction.

P T is referred to as the work done by the parallel algorithm. In this section we provide a brief introduction to parallel algorithms.

In the RAM model, we assume that each of the basic scalar binary operations such as addition, multiplication, etc. We have assumed this model in our discussion thus far. In contrast, there exist many well-accepted parallel models of computing.

In any such parallel model an individual processor can still be thought of as a RAM. Variations among different architectures arise in the ways they implement interprocessor communications. In this article we categorize parallel models into shared memory models and fixed connection machines.

If processor i has to communicate with processor j it can do so by writing a message in memory cell j which can then be read by processor j. Conflicts for global memory access could arise. Depending on how these conflicts are resolved, a PRAM can further be classified into three.

In the case of a CRCW PRAM, we need an additional mechanism for handling write conflicts, since the processors trying to write at the same time in the same cell may have different data to write and a decision has to be made as to which data gets written.

Concurrent reads do not pose such problems, since the data read by different processors will be the same. A fixed connection machine can be represented as a directed graph whose nodes represent processors and whose edges represent communication links. If there is an edge connecting two processors, they can communicate in one unit of time. If two processors not connected by an edge want to communicate they can do so by sending a message along a path that connects the two processors.

We can think of each processor in a fixed connection machine as a RAM. Examples of fixed connection machines are the mesh, the hypercube, the star graph, etc. Our discussion on parallel algorithms is confined to PRAMs owing to their sim- plicity. The input bits are stored in common memory one bit per cell. Every processor is assigned an input bit. We employ a common memory cell M that is initialized to zero. All the processors that have ones try to write a one in M in one parallel write step.

The result is ready in M after this write step. Using a similar algorithm, we can also compute the boolean AND of n bits in O 1 time.

Lemma 7. Any model in the sequence is strictly less powerful than any to its right, and strictly more powerful than any to its left. Partition the processors so that there are n processors in each group. Let the input be k1 , k2 ,. Processor i is assigned the key ki. Gi is in-charge of checking if ki is the maximum. In one parallel step, processors of group Gi compare ki with every input key. The bits bi1 , bi2 ,. This can be done in O 1 time.

If Gi computes a one in this step, then one of the processors in Gi outputs ki as the answer. This is as basic as any arithmetic operation in sequential computing. Given a sequence of n elements k1 , k2 ,. The results themselves are called prefix sums.

We can use the following algorithm. If not, the input elements are partitioned into two halves. Solve the prefix com- putation problem on each half recursively assigning n2 processors to each half. This modification can be done in O 1 time using n 2 processors. Let T n be the time needed to perform prefix computation on n elements using n processors.

Each processor is assigned log n input elements. Let xi1 , xi2 ,. This takes O log n time. This also takes O log n time. In all the parallel algorithms we have seen so far, we have assumed that the number of processors is a function of the input size. But the machines available in the market may not have these many processors. Fortunately, we can simulate these algorithms on a parallel machine with less number of processors preserving the asymptotic work done.

Let A be an algorithm that solves a given problem in time T using P processors. A discus- sion on standard data structures such as red-black trees can be found in Algorithms texts also. See e. There exist numerous wonderful texts on Algorithms also. The technique of randomization was popularized by Rabin [19]. One of the prob- lems considered in [19] was primality testing.

In an independent work, at around the same time, Solovay and Strassen [25] presented a randomized algorithm for primality testing.

The idea of randomization itself had been employed in Monte Carlo simu- lations a long time before. The sorting algorithm of Frazer and McKellar [6] is also one of the early works on randomization. Randomization has been employed in the sequential and parallel solution of nu- merous fundamental problems of computing. Several texts cover randomized algo- rithms at length. The texts [10], [22], [13], and [8, 9] cover parallel algorithms. For a survey of sorting and selection algorithms over a variety of parallel models see [20].

References [1] A. Aho, J. Hopcroft, and J. Berman and J. Brassard and P.