Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 52 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
52
Dung lượng
3,78 MB
Nội dung
Arrays 81 declaration automatically disappear when the procedure in which they are defined exits; at that point, the memory they use is automatically freed to be used by other parts of the program. However, an array allocated using the new statement remains reserved until it is explicitly released. A block of memory allocated by the use of new must later be released with a delete[] statement like this: delete[] theSieve; There must be one and only one delete[] balancing each use of new. If the delete[] is missing, the array persists until the program exits. If one has repeated requests to new (without matching delete[]s), then more and more memory is tied up in arrays until the computer runs out of memory to honor the requests. This situation is known as a memory leak. On the other hand, if one tries to perform a delete[] on the same block of memory more than once, the gates of the underworld will open and demons will rule the earth. Or your program might crash. The sieve program follows. Program 5.7: The sieve procedure. 1 #include "sieve.h" 2 3 long sieve(long n, long * primes) { 4 5 if (n<2) return 0; // no primes unless n is at least 2. 6 7 char * theSieve; 8 9 theSieve = new char[n+1]; // hold the marks 10 11 // Names of marks to put in theSieve 12 const char blank = 0; 13 const char marked = 1; 14 15 // Make sure theSieve is blank to begin 16 for (long k=2; k<=n; k++) theSieve[k] = blank; 17 18 long idx = 0; // index into the primes array 19 20 for (long k=2; k<=n; k++) { 21 if (theSieve[k]==blank) { // we found an unmarked entry 22 theSieve[k] = marked; // mark it as a prime 23 primes[idx] = k; // record k in the primes array 24 idx++; 25 26 // Now mark off all multiples of k 27 for(long d=2 * k; d<=n; d+=k) theSieve[d] = marked; 28 } 29 } 30 delete[] theSieve; 31 return idx; 32 } 82 C++ for Mathematicians Line 9 allocates the array theSieve. The matching delete[] is on line 30. On lines 12 and 13 we create names for the marks we use in the array theSieve. We use two types of mark to distinguish the two types of cells: blank cells and marked cells. We could have simply used the values 0 and 1 in the program, but unnamed constants are to be shunned. By giving these names we make the code more understandable. The const qualifier in the declaration tells the compiler that these values, blank and marked, never change. This does two good things. It prevents us from accidentally writing code that would change these symbols and it enables the compiler to produce more efficient object code. Line 16 ensures that the array theSieve is entirely populated with blank (i.e., zero) before we begin. In a perfect world arrays are given to you filled with sensible default values (such as zero). However, it is folly to rely on this. An initial run through the array to make sure it is in the state we hope is quick and easy. The variable idx on line 18 is an index into the array primes. It refers to the next available cell in primes at all points in the program. At the end, it will have been incremented once for every prime we record, and so it will hold the number of primes found. That is why we use idx as the return value on line 31. The sieving takes place on lines 20–29. When we come to an entry k in theSieve that is blank it must be a prime. We mark that location, record the number k in primes (and increment idx). Then (line 27) we place a mark in every cell that is a multiple of k. Here is a main to test the sieve procedure. 2 Program 5.8: A program to test the sieve procedure. 1 #include "sieve.h" 2 #include <iostream> 3 using namespace std; 4 5 const long N = 10000000; // ten million 6 const long TABLE_SIZE = 800000; // prime number theorem overestimate 7 8 / ** 9 * A program to test the sieve procedure. 10 * / 11 12 int main() { 13 long primes[TABLE_SIZE]; 14 long np = sieve(N,primes); 15 16 cout << "We found " << np << " primes" << endl; 17 18 cout << "The first 10 primes we found are these: " << endl; 2 Note: On Windows computers this program might crash because of line 13. Some computers place a limit on the maximum size array one can declare. The solution is to allocate large arrays dynamically. That is, replace line 13 with this: long * primes; primes = new long[TABLE SIZE]; Remember to delete[] primes; before the end of the program. Arrays 83 19 for (long k=0; k<10; k++) cout << primes[k] << " "; 20 cout << endl; 21 22 cout << "The largest prime we found is " << primes[np-1] << endl; 23 24 return 0; 25 } Finding all the primes up to ten million is quick. The output from the program appeared on my screen in under four seconds. ✞ ☎ We found 664579 primes The first 10 primes we found are these: 2 3 5 7 11 13 17 19 23 29 The largest prime we found is 9999991 ✝ ✆ 5.6 A faster totient With a table of primes at our disposal, we can calculate ϕ(n) without first factoring n; here’s how. For each prime p in the table, we check if p divides n. If so, we replace n by (n/p)(p −1). In the end, we have calculated n p 1 −1 p 1 p 2 −1 p 2 ··· p t −1 p t which, by Theorem 5.4, is ϕ(n). The procedure we create has two arguments. The declaration of this function (in the file totient.h) looks like this: long totient(long n, const long * primes); Ignore the const keyword for a moment. The first argument is n: the number for which we wish to calculate ϕ. The second argument is a table of primes. The type of this argument is long * which indicates that primes holds the starting position of an array of long integers. The table of primes is not duplicated; what we pass to the totient procedure is the address of the table. The procedure returns a long: the totient of n. Now we consider the extra word const in the declaration. When we pass an array to a procedure it is possible for the procedure to change the values held in the array. Indeed, we relied on that fact when we created the sieve procedure. Recall that sieve is declared as long sieve(long n, long * primes);. The sieve procedure receives the array address primes and can then populate that array with the desired values. 84 C++ for Mathematicians In the case of our new totient procedure, we use the values housed in the array primes , but we do not alter them. The const qualifier asserts that the procedure totient does not modify any element in the array primes. Although the procedure totient would work equally well without the const qualifier, it is a good habit to declare arguments as const when appropriate. If (by mistake) the code in your procedure is capable of changing elements in the array, the compiler will complain and help you spot the error. The code for the new totient procedure is this: Program 5.9: A faster totient procedure that employs a table of primes. 1 #include "totient.h" 2 3 long totient(long n, const long * primes) { 4 if (n<=0) return 0; 5 6 long ans = n; 7 for (long k=0; primes[k] <= n; k++) { 8 if (n%primes[k]==0) { 9 ans /= primes[k]; 10 ans * = primes[k]-1; 11 } 12 } 13 return ans; 14 } The program is fairly straightforward. We make a copy of n in a variable named ans. (This isn’t necessary, but improves clarity.) In lines 7–12 we consider all primes that are less than or equal to n. If such a prime p is a factor of n, we modify ans by dividing out p and then multiplying by p −1 (lines 9–10). In the end, ans holds ϕ(n). The only caveat is that we must be sure that the array primes contains all the prime factors of n. One way to do this is to generate all primes up to n using sieve. For example, the following main tests the faster totient procedure. #include "totient.h" #include "sieve.h" #include <iostream> using namespace std; / ** * A main to test the faster version of Euler’s totient on * the integers from 1 to 100. * / int main() { const int N = 100; // testing up to N long primes[10 * N]; // table of primes sieve(10 * N, primes); Arrays 85 for (long k=1; k<=N; k++) { cout << k << "\t" << totient(k,primes) << endl; } } The output is a two-column table. The first column contains the integers from 1 to 100, and the second column contains Euler’s totient of these. 5.7 Computing p n for large n Recall from the beginning of this chapter that we can calculate p n by the following formula, p n = 1 n 2 −1 +2 n ∑ k=1 ϕ(k) . In this section we write a program to calculate p n for n equal to one million. The main part of the program adds ϕ(k) as k goes from 1 to one million. Because we know p n is around 0.6, we expect the final sum to be around 0.6 ×10 12 which is larger than a long on a system where sizeof(long) is 4 bytes. (A 32-bit integer can store values up to about two billion, but not in the trillions.) So we need to use a long long (or int64); fortunately on my computer this is an 8-byte quantity and can hold values that are nearly 10 19 . This is more than adequate to the task. Calculating this sum takes many minutes (but not many hours). We can request the program to report its progress along the way. In the program we present, we report p k whenever k is a multiple of 10 5 . Here is the program. Program 5.10: A program to calculate p n for n equal to one million. 1 #include "totient.h" 2 #include "sieve.h" 3 #include <iostream> 4 #include <iomanip> 5 using namespace std; 6 7 / ** 8 * A program to calculate the probability that two integers chosen in 9 * {1,2, ,n} are relatively prime. This probability is calculated 10 * for values of n up to ten million. 11 * / 12 13 int main() { 14 15 const long N = 1000000; // one million 16 const long TABLE_SIZE = 200000; // prime number th’m overestimate 17 18 // set up the table of primes 86 C++ for Mathematicians 19 long * primes; 20 primes = new long[TABLE_SIZE]; 21 long np; 22 np = sieve(2 * N,primes); 23 24 long long count=0; // sum of phi(d) from 1 to n 25 26 cout << setprecision(20); 27 for (long k=1; k<=N; k++) { 28 count += totient(k, primes); 29 if (k%100000==0) { 30 cout << k/1000 << " thousand \t"; 31 cout << double(2 * count-1) / (double(k) * double(k)) << endl; 32 } 33 } 34 return 0; 35 } Notice there is a new header included at line 4. The iomanip header provides devices to change the output style. In our case, we want to print out more digits of p n than usual. This occurs on line 26. The cout << setprecision(20); statement modifies cout so that it prints up to 20 decimal digits for double real numbers (trailing zeros, if any, are not printed). The iomanip header is needed to define setprecision. (See Section 14.6 for more information on adjusting output format.) Lines 19–22 are used to generate the table of primes. The variable count, declared on line 24, is used to accumulate the sum of ϕ(k). As discussed, this needs to be type long long because the final sum exceeds the maximum value a long can hold. The core of the program is on lines 27–33. This is a for loop that increments count by ϕ(k) as k goes from one to one million. On line 29 we check if k is divisible by 100 thousand; if so, we report p k at that time. Here is the output of the program (which took about 25 minutes to run on my computer). ✞ ☎ 100 thousand 0.6079301507 200 thousand 0.60792994587500004 300 thousand 0.60792774407777783 400 thousand 0.60792759136874996 500 thousand 0.607928317404 600 thousand 0.6079276484527778 700 thousand 0.60792730424285712 800 thousand 0.60792796007343752 900 thousand 0.6079273649074074 1000 thousand 0.60792710478300005 ✝ ✆ Arrays 87 5.8 The answer It certainly appears that p n is converging and that the limit, to six decimal places, is 0.607927. The next step, necessarily, takes us beyond C++; we need to recognize this number to formulate (and prove!) a conjecture. Fortunately, there are good tools for this step. Neil Sloane’s On-Line Encyclo- pedia of Integer Sequences is a remarkable resource that takes us directly to the answer. Visit http://www.research.att.com/˜njas/sequences/ and enter the sequence of digits into the sequence search engine: 6 0 7 9 2 7 and press the SEARCH button. After a brief delay, the answer emerges: 1 ζ (2) = 6 π 2 = 0.6079271018540 is the number we seek. (The site also gives several references to this well-known problem.) We close this chapter with a sketch of the proof. Sweeping all worries about convergence under the rug, consider two large integers. What is the probability they are not both even (a necessary condition for the numbers to be relatively prime)? Each has a 1 2 chance of being even, so the probability neither has a factor of 2 is 1 − 1 4 . More generally, the probability neither has a prime p as a common factor is 1 −1/p 2 . So the limit of p n is ∏ p 1 − 1 p 2 where the product is over all primes. Recall that ζ (2) is given by ζ (2) = ∞ ∑ n=1 1 n 2 . This can be expressed as a product. The idea is to factor n 2 into even powers of its prime divisors. The product representation is ζ (2) = ∏ p 1 + 1 p 2 + 1 p 4 + 1 p 6 + ··· where the product is over primes p. To see why this works, consider the term (in the sum) 1/120 2 . We factor 120 as 2 3 ×3 ×5. Expanding the product representation, the term 1/60 2 appears by taking 1/2 6 from the first factor, 1/3 2 from the second, 1/5 2 from the third, and 1 from all the other factors. 88 C++ for Mathematicians Notice that the factors in the product representation are geometric series. There- fore ζ (2) = ∏ p 1 1 − 1 p 2 and so 1 ζ (2) = ∏ p 1 − 1 p 2 as desired. 5.9 Exercises 5.1 Calculate ϕ(100), ϕ(2 9 ), and ϕ(5!). 5.2 What is wrong with this program and how can the mistake be repaired? #include <iostream> using namespace std; int main() { int n; cout << "Enter n: "; cin >> n; int vals[n]; // do stuff with the vals array return 0; } 5.3 Solve the pair of congruences x ≡3 (mod 20) and x ≡5 (mod 9). 5.4 Write a procedure to solve problems such as Exercise 5.3. It may be declared like this: long crt(long a1, long n1, long a2, long n2); (The name crt stands for Chinese Remainder Theorem.) The procedure should be designed to solve the pair of recurrences x ≡ a 1 (mod n 1 ) x ≡ a 2 (mod n 2 ) where n 1 and n 2 are relatively prime positive integers. The return value is the solution mod n 1 n 2 . How should the procedure handle a situation in which n 1 and n 2 are not of this form? Arrays 89 5.5 In Program 5.7 we defined two values named blank and marked and used them to populate an array named theSieve . For the marks we used char type values (and the array was declared to be char * ). However, it would be more logical to use bool values because each cell in theSieve takes only one of two possible values and the Boolean true or false makes perfect sense in this context. Why did we use char type values instead of bool? 5.6 Write a program that fills an array with Fibonacci numbers F 0 through F 20 and then prints them out in a chart. 5.7 Write a program that fills two arrays a and b with integers according to this recurrence: a 0 = b 0 = 1 a n = b n−1 b n = a n−1 + 2b n−1 . The program should then print out a table in which each row is of the form k a k b k a k b k . Conjecture a value for lim n→∞ a n /b n . And, of course, prove your conjecture. 5.8 Write a procedure to find the maximum value in an array of long integers. The inputs to the procedure should be the array and the number of elements in the array. The output should be the maximum value in the array. 5.9 Write a procedure that generates an array of Fibonacci numbers as its return value. The input to the procedure should be an integer n ≥ 2 that specifies the desired size of the array. Here is how such a procedure would appear in a main(): int main() { long * fibs; fibs = make_fibs(10); for (int k=0; k<10; k++) cout << fibs[k] << " "; cout << endl; return 0; } This code should produce the output: ✞ ☎ 1 1 2 3 5 8 13 21 34 55 ✝ ✆ In addition, there is a subtle bug in the main(). What is it? 5.10 Create a procedure long fibs(int n) to return the nth Fibonacci number. The procedure should work as follows. The first time the procedure is called, it creates a table of Fibonacci numbers holding F n through F 40 . Then it returns the value held in the table it created. On all subsequent calls, it does not need 90 C++ for Mathematicians to recompute any Fibonacci numbers, but simply returns the value in the table it built during its first invocation. If the input parameter is out of range (either less than 0 or greater than 40) the procedure should return −1. 5.11 What happens when new asks for more memory than your computer can pro- vide? Write a program that repeatedly requests new for large blocks of mem- ory without ever releasing those blocks with delete[] . That is, your program should have a severe, deliberate memory leak. 5.12 A computational experiment yields the following result: 5.8598744820. Pro- pose a conjecture. [...]... is cout (and not a copy of cout) Therefore, after the first . thousand 0.607 930 1507 200 thousand 0.60792994587500004 30 0 thousand 0.607927744077777 83 400 thousand 0.60792759 136 874996 500 thousand 0.60792 831 7404 600 thousand 0.6079276484527778 700 thousand. 0.60792 730 424285712 800 thousand 0.6079279600 734 3752 900 thousand 0.60792 736 49074074 1000 thousand 0.6079271047 830 0005 ✝ ✆ Arrays 87 5.8 The answer It certainly appears that p n is converging and. procedure handle a situation in which n 1 and n 2 are not of this form? Arrays 89 5.5 In Program 5.7 we defined two values named blank and marked and used them to populate an array named theSieve . For