Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C++ For Mathematicians (2006) [eng]

.pdf
Скачиваний:
193
Добавлен:
16.08.2013
Размер:
31.64 Mб
Скачать

326

C++ for Mathematicians

44// Open the plotter

45if (P.openpl() < 0) {

46cerr << "Unable to open plotter" << endl;

47return 1;

48}

49

50// Set up my coordinate system

51P.fspace(0.,0.,1.,1.);

52

53// Set drawing parameters

54P.pencolorname("black");

55

56// Draw the corners

57draw_point(P,A);

58draw_point(P,B);

59draw_point(P,C);

60

61// Start X at A

62X = A;

63

64for (int k=0; k<NPOINTS; k++) {

65switch(unif(3)) {

66case 1:

67X = midpoint(A,X);

68break;

69case 2:

70X = midpoint(B,X);

71break;

72case 3:

73X = midpoint(C,X);

74break;

75default:

76cerr << "This can’t happen" << endl;

77}

78draw_point(P,X);

79}

80

81// Close the image

82if (P.closepl() < 0) {

83cerr << "Unable to close plotter" << endl;

84return 1;

85}

86

87return 0;

88}

When the program is run we again see Sierpinski’s triangle! See Figure 14.4.

14.8.5 Drawing Paley graphs

Our final drawing example is a program to draw Paley graphs. In this context, a graph is a pair (V,E) where V is a finite set of vertices and E is a set of unordered pairs of distinct vertices; elements of E are called edges. The edge {u,v} is said to join its end points, u and v; we call such vertices adjacent. Note that is adjacent

Strings, Input/Output, and Visualization

327

 

 

Figure 14.4: An image based on a random process in a triangle.

to is a symmetric relation because the pairs in E are unordered. Furthermore, selfloops are not allowed (edges that join a vertex to itself). Such graphs are often called simple graphs.

It is useful to visualize graphs by drawings. The vertices are drawn as dots and edges as line segments (or curves) joining their end point’s dots. In our program, we draw the vertices as small circles.

The Paley graphs form an interesting class of graphs. Let n ≥ 3 be an integer. The vertices of Gn are the integers {0,1,2,...,n}. Two vertices, u and v, of Gn are adjacent (joined by an edge) if and only if u − v is a perfect square (quadratic residue) in Zn. Because the is adjacent to relation is symmetric, we require that −1 be a perfect square in Zn; otherwise Gn is undefined.

For example, let n = 5. The vertices of G5 are {0,1,2,3,4}. The (nonzero) quadratic residues in Z5 are 1 and 4. Therefore, the edges of G5 are {0,1}, {1,2}, {2,3}, {3,4}, and {0,5}. In other words, G5 is a 5-cycle. However, G7 is undefined because −1 ≡ 6 is not a perfect square in Z7.

Our program to draw Paley graphs reads the parameter n from the command line (see lines 11–25 of Program 14.14) and then stores all squares in Zn in a set object named squares (lines 31–35). After checking that −1 is a quadratic residue (lines 40–43), we create a table of x and y values for the vertex locations. We evenly place the vertices on a circle of radius n centered at the origin.

Next we declare and initialize a PSPlotter object (lines 57–71) that sends its

328

C++ for Mathematicians

output to a file named paley.eps (lines 8, 58, and 59). We choose a vector graphic format (eps) because our drawing consists of mathematically defined curves and not a field of points. We are ready to begin drawing.

We first draw the edges (lines 74–83) as line segments. Then we modify the pen so that enclosed regions (such as circles) are filled with white ink. This is accomplished in two steps: a call to the fillcolorname method (line 86) to set the fill color to white and then P.filltype(1); (line 87) to activate region filling. The vertices are now drawn as circles of radius 12 (lines 88–90).

Note that if we had not activated region filling, then the circles would have been transparent and we would see the full lengths of the line segments meeting at the centers of the circles; it is more esthetically pleasing to cover that region. Furthermore, it is important that we draw the edges before the vertices. If we were to reverse the order, then the edges would be drawn on top of the circles, and we would see the portion of the line segment that ought to be hidden.

The rest of the program releases the memory allocated for the arrays holding the coordinates of the vertices and closes the PSPlotter (line 93 to the end).

Here is the program.

Program 14.14: A program to draw Paley graphs.

1 #include <iostream>

2 #include <fstream>

3#include <set>

4 #include "plotter.h"

5#include "Mod.h"

6using namespace std;

7

8const char* IMAGE_FILE = "paley.eps";

9

10int main(int argc, char** argv) {

11// check that user specified number of points

12if (argc < 2) {

13cerr << "Usage: " << argv[0] << " n" << endl;

14return 1;

15}

16

17 int n; // number of vertices in the graph and the modulus

18

19// convert 2nd arg to an integer; make sure it’s positive

20n = atoi(argv[1]);

21if (n < 3) {

22cerr << "Usage: " << argv[0] << " n" << endl;

23cerr << "where n is an integer greater than 2" << endl;

24return 1;

25}

26

27// We’ll only be working in Z_n

28Mod::set_default_modulus(n) ;

29

30// Find all perfect squares in Z_n

31set<Mod> squares;

32for(int k=0; k<n; k++) {

Strings, Input/Output, and Visualization

329

33Mod S(k*k);

34squares.insert(S);

35}

36

37// Check if -1 is a perfect square

38Mod neg1(-1);

39

40if (squares.find(neg1) == squares.end()) {

41cerr << "-1 is not a perfect square in Z_" << n << endl;

42return 1;

43}

44

45// Create tables of the x and y coordinates of the vertex centers

46float* x = new float[n];

47float* y = new float[n];

48

49 double theta = 2*M_PI/n;

50

51for (int i=0; i<n; i++) {

52x[i] = n * sin(i*theta);

53y[i] = n * cos(i*theta);

54}

55

56// Construct the plotter

57PlotterParams params;

58ofstream pout(IMAGE_FILE);

59PSPlotter P(cin, pout, cerr, params);

60

61// Open the plotter

62if (P.openpl() < 0) {

63cerr << "Unable to open plotter" << endl;

64return 1;

65}

66// Set drawing parameters

67P.flinewidth(0.05);

68P.pencolorname("black");

69

70// Set up my coordinate system

71P.space(-n-1, -n-1, n+1, n+1);

72

73// Draw the edges

74for (int i=0; i<n-1; i++) {

75for (int j=i+1; j<n; j++) {

76// if i-j is a perfect square ...

77Mod diff(i-j);

78if (squares.find(diff) != squares.end()) {

79// ... then we draw the edge

80P.fline(x[i],y[i],x[j],y[j]);

81}

82}

83}

84

85// Draw the vertices

86P.fillcolorname("white");

87P.filltype(1);

88for (int i=0; i<n; i++) {

330

C++ for Mathematicians

89P.fcircle(x[i], y[i], 0.5);

90}

91

92// Release allocated arrays for x and y

93delete[] x;

94delete[] y;

95

96// Close the image

97if (P.closepl() < 0) {

98cerr << "Unable to close plotter" << endl;

99return 1;

100}

101

102return 0;

103}

When the program is run with n = 17, the picture in Figure 14.5 is produced. The Paley graph G17 is interesting because it does not contain four vertices that are pairwise adjacent (a clique of size 4) and it does not contain four vertices that are pairwise nonadjacent (an independent set of size 4). All graphs on 18 vertices (or more) must contain either a clique or an independent set of size 4.

Figure 14.5: The Paley graph on 17 vertices.

14.9Exercises

14.1Suppose a string variable contains the decimal representation of an integer. For example, string s = "7152";. Explain how to convert the string to an int.

Strings, Input/Output, and Visualization

331

14.2A string variable named file_name contains the name of a file. We want to read data from that file using an ifstream object named fin. Show how fin should be declared.

14.3A programmer wants to write the 26 lowercase letters a to z on the computer’s screen. The following code does not work.

for (int k=0; k<26; k++) { cout << ’a’ + k;

}

What is wrong and how can this code be repaired?

14.4A free group is the set of all formal products one can create using a set of generators {a,b,c,...} and their inverses. If a generator and its inverse are adjacent in the product, then they can be removed. The empty product serves as the identity element. For example,

abc−1b · b−1cba

simplifies to abba or ab2a.

Create a class named Free that represents elements in a free group. Use a string variable to hold the group element using lowercase letters for the

generators and the corresponding uppercase letters for their inverses (i.e., A for a−1).

Include an operator* for the group product and an inverse() method. Also provide a << operator for printing Free objects to the screen.

There are several procedures declared in the cctype header that you can use for this exercise. These can be used to examine and modify the cases of single letter characters. See Appendix C.6.3 (starting on page 411) for a description of isupper, toupper, and so on. Read the introductory material carefully as toupper does not behave precisely as you might expect.

14.5Write a program that reads a text file and reports the frequency of each of the 26 letters. Run the program on various texts and see if you can distinguish languages.

You can find books in plain text format at http://www.gutenberg.org.

14.6Polygrams. In Exercise 14.5 we analyzed writing by looking at the frequency of single letters. In this exercise we consider a more sensitive analysis. For a positive integer n, an n-gram is a sequence of n letters. For example, the word banana contains the 2-grams ba, an, and na.

Create a program to count the n-grams found in a text and report the ten most frequent n-grams found for each of n = 2, 3, and 4.

332

C++ for Mathematicians

14.7Koch curve. The Koch curve is a fractal formed by the following iterative process. Begin with a horizontal line segment, say from (0,0) to (1,0). Draw an equilateral triangle using the middle third of this segment as one side, and then erase the side that was part of the original segment. The new path consists of four segments:

(0,0) →

3 ,0

2 ,

 

 

 

!

3 ,0

→ (1,0)

6

 

 

1

 

 

1

3

 

2

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Now repeat this basic step again on each of the four segments to create a polygonal path with 16 segments. The first two steps of this process are shown in the figure.

The Koch curve is the result of carrying out this process ad infinitum.

Write a program to draw (an approximate version of) the Koch curve to a userspecified number of iterations of the basic step.

14.8Mandelbrot set. Let c C and define fc(z) = z2 + c. Consider the sequence of iterates

z0 = 0, zk+1 = fc(zk).

For some values of c this sequence tends to infinity, |zk| → ∞ as k → ∞, but for other values of c this sequence stays bounded.

The Mandelbrot set M is the set of all complex c such that the iterates of fc (starting at 0) stay bounded. Create a program to draw a picture of the Mandelbrot set.

One can show that if for some k we have |zk| > 2, then the iterates zn tend to infinity. So, to test if c M, one can compute (say) the first 500 iterates zk testing if any have magnitude greater than 2. If so, we know that c / M. However, if for all k ≤ 500 we have |z500| < 2, one might guess that c M. This guess might be incorrect, but this approach yields a reasonable approximation.

Furthermore, based on this criterion, any complex number c with |c| > 2 cannot be an element of M, so one need not check a complex number a + bi with |a| > 2 or |b| > 2.

Chapter 15

Odds and Ends

It is not the purpose of this book to give an exhaustive coverage of every feature of C++. We have, by design, omitted those parts of the language that (in our judgment) are not useful for mathematical work. In this final chapter we present an assortment of additional topics that may also be of interest and utility in your work.

15.1The switch statement

C++ provides a variety of control structures such as if, for, while, and do. Here we discuss an additional control structure: the switch statement.

The switch statement is used to control execution depending on the value held in an integer variable. Suppose that x holds an integer value. If x equals 1, we perform operation I, if it equals 2 we perform operation II, if it equals 3 or 4 we perform operation III, and if it holds any other value, we do operation IV. Here is how we would structure this situation using if statements.

if (x == 1) {

// Do operation I

}

else {

if (x == 2) {

// Do operation II

}

else {

if ((x == 3) || (x == 4)) { // Do operation III

}

else {

// Do operation IV

}

}

}

As you can see, this is difficult to read (and awkward to type). With switch, the code is much clearer:

switch(x) { case 1:

// Do operation I

333

334

C++ for Mathematicians

break; case 2:

// Do operation II break;

case 3: case 4:

// Do operation III break;

default:

// Do operation IV

}

The outer structure of the statement is switch(x) {...} where x is an integer type. Within the curly braces is a sequence of case statements. Each case is followed by a specific integer value. When a switch statement is executed, the computer looks for a case that matches the value in x. If it finds one, it executes whatever code it finds following the case statement until it finds a break; statement. If no case statement matches the value of x, the computer looks for a default statement and executes the code that follows. (If there is no default statement, then execution skips to the statements following the closing curly brace of the switch.)

Notice that two case statements can precede a section of code (for the values 3 and 4).

C++ does not require a break; statement between two sections of a switch statement. For example, if we were to delete the break; at the end of case 2, then (when x equals 2), first operation II would execute and then operation III would execute. This is known as falling through cases. In general, it is much better to balance every case with a matching break unless (as in the example) two cases execute the exact same code. The default case (because it appears at the end of the switch) does not require a break.

There is no way to specify a range of values in a case statement. So, for example, if you want different behaviors depending on whether x is negative, zero, or positive, you should use if statements.

The switch statement is useful for creating a menu of options for the user. A prompt is typed on the screen listing various options; the user specifies the desired option by typing a number or letter. The following program illustrates how this is done; in it, the switch statement plays a central role. The user specifies the desired option by typing a letter. We allow uppercase and lowercase letters and so two case statements are given for each option.

Program 15.1: A program to illustrate the switch statement.

1 #include <iostream>

2#include <string>

3 using namespace std;

4int main() {

5

6 bool again = true;

7 while(again) {

8cout << endl;

Odds and Ends

335

9cout << "What would you like to do?" << endl;

10cout << " (a) Prove Fermat’s little theorem" << endl;

11cout << " (b) Prove Fermat’s last theorem" << endl;

12cout << " (c) List some Fermat primes" << endl;

13cout << " (d) End this program" << endl;

14cout << "Type the character and hit return: ";

15

16string response;

17cin >> response;

18

19 if (response.empty()) continue;

20

21 char option = response[0];

22

23switch(option) {

24case ’a’:

25case ’A’:

26cout << endl

27<< "Apply Lagrange’s theorem to the group Z_p." << endl;

28break;

29

30case ’b’:

31case ’B’:

32cout << endl

33<< "I have a great proof for this but, alas, it will"

34<< endl << "not fit on this computer screen." << endl;

35break;

36

37case ’c’:

38case ’C’:

39cout << endl << "Here are the only ones I know:" << endl

40<< "3 5 17 257 25537." << endl;

41break;

42

43case ’d’:

44case ’D’:

45cout << endl << "Goodbye." << endl;

46again = false;

47break;

48

49default:

50cout << endl << "Sorry. Your response was not recognized."

51<< endl << "Please try again." << endl;

52}

53}

54return 0;

55}

Here is a sample run of the program.

 

What would you like to do?

(a)Prove Fermat’s little theorem

(b)Prove Fermat’s last theorem

(c)List some Fermat primes

(d)End this program

Type the character and hit return: a