# Thuật toán Algorithms (Phần 3)

Chia sẻ: Tran Anh Phuong | Ngày: | Loại File: PDF | Số trang:10

0
64
lượt xem
8

## Thuật toán Algorithms (Phần 3)

Mô tả tài liệu

Tham khảo tài liệu 'thuật toán algorithms (phần 3)', khoa học tự nhiên, toán học phục vụ nhu cầu học tập, nghiên cứu và làm việc hiệu quả

Chủ đề:

Bình luận(0)

Lưu

## Nội dung Text: Thuật toán Algorithms (Phần 3)

1. expected to take on “typical” input data, and in the worst case, the amount of time a program would take on the worst possible input configuration. Many of the algorithms in this book are very well understood, to the point that accurate mathematical formulas are known for the average- and worst- case running time. Such formulas are developed first by carefully studying the program, to find the running time in terms of fundamental mathematical quantities and then doing a mathematical analysis of the quantities involved. For some algorithms, it is easy to hgure out the running time. For ex- ample, the brute-force algorithm above obviously requires min(u, VU)-gcd(u, V) iterations of the while loop, and this quantity dominates the running time if the inputs are not small, since all the other statements are executed either 0 or 1 times. For other algorithms, a substantial amount of analysis is in- volved. For example, the running time of the recursive Euclidean algorithm obviously depends on the “overhead” required for each recursive call (which can be determined only through detailed1 knowledge of the programming en- vironment being used) as well as the number of such calls made (which can be determined only through extremely sophisticated mathematical analysis). Several important factors go into this analysis which are somewhat out- side a given programmer’s domain of influence. First, Pascal programs are translated into machine code for a given computer, and it can be a challenging task to figure out exactly how long even one Pascal statement might take to execute (especially in an environment where resources are being shared, so that even the same program could have varying performance characteristics). Second, many programs are extremely sensitive to their input data, and per- formance might fluctuate wildly depending on the input. The average case might be a mathematical fiction that is not representative of the actual data on which the program is being used, and the worst case might be a bizarre construction that would never occur in practice. Third, many programs of interest are not well understood, and specific mathematical results may not be available. Finally, it is often the case that programs are not comparable at all: one runs much more efficiently on one particular kind of input, the other runs efficiently under other circumstances. With these caveats in mind, we’ll use rough estimates for the running time of our programs for purposes of classification, secure in the knowledge that a fuller analysis can be done for important programs when necessary. Such rough estimates are quite often easy to obtain via the old programming saw “90% of the time is spent in 10% of the code.” (This has been quoted in the past for many different values of “go%.“) The first step in getting a rough estimate of the running time of a program is to identify the inner loop. Which instructions in the program are executed most often? Generally, it is only a few instructions, nested deep within the
2. CHAPTER 1 control structure of a program, that absorb all of the machine cycles. It is always worthwhile for the programmer to be aware of the inner loop, just to be sure that unnecessary expensive instructions are not put there. Second, some analysis is necessary to estimate how many times the inner loop is iterated. It would be beyond the scope of this book to describe the mathematical mechanisms which are used in such analyses, but fortunately the running times many programs fall into one of a few distinct classes. When possible, we’ll give a rough description of the analysis of the programs, but it will often be necessary merely to refer to the literature. (Specific references are given at the end of each major section of the book.) For example, the results of a sophisticated mathematical argument show that the number of recursive steps in Euclid’s algorithm when u is chosen at random less than v is approximately ((12 In 2)/7r2) 1 n TJ. Often, the results of a mathematical analysis are not exact, but approximate in a precise technical sense: the result might be an expression consisting of a sequence of decreasing terms. Just as we are most concerned with the inner loop of a program, we are most concerned with the leading term (the largest term) of a mathematical expression. As mentioned above, most algorithms have a primary parameter N, usually the number of data items to be processed, which affects the running time most significantly. The parameter N might be the degree of a polyno- mial, the size of a file to be sorted or searched, the number of nodes in a graph, etc. Virtually all of the algorithms in this book have running time proportional to one of the following functions: 1 Most instructions of most programs are executed once or at most only a few times. If all the instructions of a program have this property, we say that its running time is constant. This is obviously the situation to strive for in algorithm design. log N When the running time of a program is logarithmic, the program gets slightly slower as N grows.This running time commonly occurs in programs which solve a big problem by transforming it into a smaller problem by cutting the size by some constant fraction. For our range of interest, the running time can be considered to be less than a Yarge” constant. The base of the logarithm changes the constant, but not by much: when N is a thousand, log N is 3 if the base is 10, 10 if the base is 2; when N is a million, 1ogN is twice as great. Whenever N doubles, log N increases by a constant, but log N doesn’t double until N increases to N2. N When the running time of a program is linear, it generally is the case that a small amount of processing is done on each input element. When N is a million, then so is the running time. Whenever N
3. PREVTEW 15 doubles, then so does the running time. This is the optimal situation for an algorithm that must process N inputs (or produce N outputs). NlogN This running time arises in algorithms which solve a problem by breaking it up into smaller subpr’oblems, solving them independently, and then combining the solutions. For lack of a better adjective (linearithmic?), we’ll say that th’e running time of such an algorithm is “N log N.” When N is a million, N log N is perhaps twenty million. When N doubles, the running time more than doubles (but not much more). N2 When the running time of an algorithm is quadratic, it is practical for use only on relatively small problems. Quadratic running times typically arise in algorithms which process all pairs of data items (perhaps in a double nested loop). When N is a thousand, the running time is a million. Whenever N doubles, the running time increases fourfold. N3 Similarly, an algorithm which prlocesses triples of data items (perhaps in a triple-nested loop) has a cubic running time and is practical for use only on small problems. VVhen N is a hundred, the running time is a million. Whenever N doubles, the running time increases eightfold. 2N Few algorithms with exponential running time are likely to be ap- propriate for practical use, though such algorithms arise naturally as “brute-force” solutions to problems. When N is twenty, the running time is a million. Whenever N doubles, the running time squares! The running time of a particular prlogram is likely to be some constant times one of these terms (the “leading term”) plus some smaller terms. The values of the constant coefficient and the terms included depends on the results of the analysis and on implementation details. Roughly, the coefficient of the leading term has to do with the number of instructions in the inner loop: at any level of algorithm design it’s prudent to limit the number of such instructions. For large N the effect of the leading term dominates; for small N or for carefully engineered algorithms, more terms may contribute and comparisions of algorithms are more difficult. In most cases, we’ll simply refer to the running time of programs as “linear,” “N log N, ” “cubic,” etc., with the implicit understanding that more detailed analysis or empirical studies must be done in cases where efficiency is very important. A few other functions do arise. For example, an algorithm with N2 inputs that has a running time that is cubic in N is more properly classed as an N3j2 algorithm. Also some algorithms have two stages of subproblem decomposition, which leads to a running time proportional to N(log N)2. Both
4. CHAPTER 1 of these functions should be considered to be much closer to N log N than to N2 for large N. One further note on the “log” function. As mentioned above, the base of the logarithm changes things only by a constant factor. Since we usually deal with analytic results only to within a constant factor, it doesn’t matter much what the base is, so we refer to “logN,” etc. On the other hand, it is sometimes the case that concepts can be explained more clearly when some specific base is used. In mathematics, the natz~ral logarithm (base e = 2.718281828.. .) arises so frequently that a special abbreviation is commonly used: log, N = In N. In computer science, the binary logarithm (base 2) arises so frequently that the abbreviation log, N = lg N is commonly used. For example, lg N rounded up to the nearest integer is the number of bits required to represent N in binary. Implementing Algorithms The algorithms that we will discuss in this book are quite well understood, but for the most part we’ll avoid excessively detailed comparisons. Our goal will be to try to identify those algorithms which are likely to perform best for a given type of input in a given application. The most common mistake made in the selection of an algorithm is to ignore performance characteristics. Faster algorithms are often more compli- cated, and implementors are often willing to accept a slower algorithm to avoid having to deal with added complexity. But it is often the case that a faster algorithm is really not much more complicated, and dealing with slight added complexity is a small price to pay to avoid dealing with a slow algorithm. Users of a surprising number of computer systems lose substantial time waiting for simple quadratic algorithms to finish when only slightly more complicated N log N algorithms are available which could run in a fraction the time. The second most common mistake made in the selection of an algorithm is to pay too much attention to performance characteristics. An N log N algorithm might be only slightly more complicated than a quadratic algorithm for the same problem, but a better N log N algorithm might give rise to a substantial increase in complexity (and might actually be faster only for very large values of N). Also, many programs are really run only a few times: the time required to implement and debug an optimized algorithm might be substantially more than the time required simply to run a slightly slower one. The programs in this book use only basic features of Pascal, rather than taking advantage of more advanced capabilities that are available in Pascal and other programming environments. Our purpose is to study algorithms, not systems programming nor advanced features of programming languages.
5. PREVIEW 17 It is hoped that the essential features of the algorithms are best exposed through simple direct implementations in a near-universal language. For the same reason, the programming style is somewhat terse, using short variable names and few comments, so that the control structures stand out. The “documentation” of the algorithms is the accompanying text. It is expected that readers who use these programs in actual applications will flesh them out somewhat in adapting them for a particular use.
6. 18 Exercises 1. Solve our initial problem by writing a Pascal program to reduce a given fraction x/y to lowest terms. 2. Check what values your Pascal system computes for u mod v when u and v are not necessarily positive. Which versions of the gcd work properly when one or both of the arugments are O? 3. Would our original gcd program ever be faster than the nonrecursive version of Euclid’s algorithm? 4. Give the values of u and v each time the recursive gcd is invoked after the initial call gcd(12345,56789). 5. Exactly how many Pascal statements are executed in each of the three gcd implementations for the call in the previous exercise? 6. Would it be more efficient to test for u>v in the recursive implementation of Euclid’s algorithm? 7. Write a recursive program to compute the largest integer less than log, N based on the fact that the value of this function for N div 2 is one greater than for N if N > 1. 8. Write an iterative program for the problem in the previous exercise. Also, write a program that does the computation using Pascal library sub- routines. If possible on your computer system, compare the performance of these three programs. 9. Write a program to compute the greatest common divisor of three integers u, v, and w. 10. For what values of N is 10NlgN > 2N2? (Thus a quadratic algorithm is not necessarily slower than an NlogN one.)