C++ For Mathematicians (2006) [eng]
.pdf226 |
C++ for Mathematicians |
Program 11.2: Program file for Permutation class.
1 #include "Permutation.h"
2#include "uniform.h"
3
4 Permutation::Permutation() {
5 data = new long[2];
6n = 1;
7data[1] = 1;
8}
9
10Permutation::Permutation(long nels) {
11if (nels <= 0) {
12nels = 1;
13}
14data = new long[nels+1];
15n = nels;
16for (long k=1; k<=n; k++) {
17data[k] = k;
18}
19}
20
21Permutation::Permutation(long nels, long* array) {
22if (nels <= 0) {
23nels = 1;
24data = new long[2];
25data[1] = 1;
26return;
27}
28n = nels;
29data = new long[n+1];
30for (long k=1; k<=n; k++) data[k] = array[k];
31if (!check()) reset();
32}
33
34Permutation::Permutation(const Permutation& that) {
35n = that.n;
36data = new long[n+1];
37for (long k=1; k<=n; k++) data[k] = that.data[k];
38}
39
40Permutation::˜Permutation() {
41delete[] data;
42}
43
44void Permutation::reset() {
45for (long k=1; k<=n; k++) data[k] = k;
46}
47
48void Permutation::swap(long i, long j) {
49if ( (i<1) || (i>n) || (j<1) || (j>n) || (i==j) ) return;
50long a = data[i];
51long b = data[j];
52data[i] = b;
53data[j] = a;
54}
Permutations |
227 |
55
56void Permutation::randomize() {
57for (long k=1; k<n; k++) {
58long j = unif(n-k+1)-1+k;
59long tmp = data[j];
60data[j] = data[k];
61data[k] = tmp;
62}
63}
64
65bool Permutation::check() const {
66long* temp;
67
68temp = new long[n+1];
69for (long k=1; k<=n; k++) {
70if ( (data[k] < 1) || (data[k] > n)) {
71delete[] temp;
72return false;
73}
74temp[k] = data[k];
75}
76sort(temp+1, temp+n+1);
77for (long k=1; k<=n; k++) {
78if (temp[k] != k) {
79delete[] temp;
80return false;
81}
82}
83delete[] temp;
84return true;
85}
86
87long Permutation::of(long k) const {
88if ( (k<1) || (k>n) ) return k;
89return data[k];
90}
91
92Permutation Permutation::operator=(const Permutation& that) {
93delete[] data;
94n = that.n;
95data = new long[n+1];
96for (long k=1; k<=n; k++) data[k] = that.data[k];
97return *this;
98}
99
100Permutation Permutation::operator*(const Permutation& that) const {
101long nmax = (n > that.n) ? n : that.n;
102long* tmp = new long[nmax+1];
103
104for (long k=1; k<=n; k++) {
105tmp[k] = of(that(k));
106}
107
108Permutation ans(nmax,tmp);
109delete[] tmp;
110return ans;
228 |
C++ for Mathematicians |
111 }
112
113Permutation Permutation::operator*=(const Permutation& that) {
114(*this) = (*this) * that;
115return *this;
116}
117
118Permutation Permutation::inverse() const {
119Permutation ans(n);
120for (long k=1; k<=n; k++) ans.data[data[k]] = k;
121return ans;
122}
123
124bool Permutation::operator==(const Permutation& that) const {
125if (n != that.n) return false;
126
127for (long k=1; k<=n; k++) {
128if (data[k] != that.data[k]) return false;
129}
130return true;
131}
132
133bool Permutation::operator!=(const Permutation& that) const {
134return !( (*this)==that );
135}
136
137bool Permutation::operator<(const Permutation& that) const {
138if (n < that.n) return true;
139if (n > that.n) return false;
140
141for (long k=1; k<=n; k++) {
142if (data[k] < that.data[k]) return true;
143if (data[k] > that.data[k]) return false;
144}
145return false;
146}
147
148bool Permutation::isIdentity() const {
149for (int k=1; k<=n; k++) {
150if (data[k] != k) return false;
151}
152return true;
153}
154
155ostream& operator<<(ostream& os, const Permutation& P) {
156long n = P.getN();
157bool* done = new bool[n+1];
158for (long k=1; k<=n; k++) done[k] = false;
159
160for (long k=1; k<=n; k++) {
161if (!done[k]) {
162os << "(" << k;
163done[k] = true;
164long j = P(k);
165while (j!=k) {
166os << "," << j;
Permutations |
229 |
167done[j] = true;
168j = P(j);
169}
170os << ")";
171}
172}
173delete[] done;
174return os;
175}
11.3Finding monotone subsequences
We now return to Ulam’s problem: what is the expected length of a longest increasing subsequence of a random permutation? The Permutation class includes a randomize method, so we are able to generate permutations uniformly at random in Sn. Given a permutation, how do we find the length of a longest increasing (or decreasing) subsequence?
The idea for the algorithm comes from the proof of the Erdos˝–Szekeres Theorem (Theorem 11.1). Given a permutation π Sn, we define the values ui (respectively, di) to be the length of a longest increasing (respectively, decreasing) subsequence of π starting at position i. To find the values ui and di we work from right to left; that is, we start with i = n and work our way down.
Clearly un = dn = 1 because the longest increasing (decreasing) sequence that starts from the last position has length exactly one.
Now consider un−1 and dn−1. If π(n − 1) < π(n), then the longest increasing subsequence starting at position n −1 has length 2 and the longest decreasing subsequence has length 1. On the other hand, if π(n −1) > π(n), then the longest increasing subsequence starting at position n − 1 has length 1 and the longest decreasing subsequence has length 2. In other words,
(un−1,dn−1) = |
((1,2) |
if π(n |
−1) > π(n). |
and |
|
(2,1) |
if π(n |
1) < π(n), |
−
Suppose we have established the values ui and di for i = k + 1 through i = n. To find uk, we compare π(k) sequentially with π(k + 1), π(k + 2), and so on, up to π(n). Among all indices j > k for which π(k) < π( j), find the one for which uj is largest. We then set uk = uj + 1. If there are no indices j with π(k) < π( j), then we set uk = 1.
The procedure for finding dk is analogous. Among all indices j > k for which π(k) > π( j), we select the j for which dj is largest and set dk = dj + 1. If no such index j exists, we set dk = 1.
230 |
C++ for Mathematicians |
Let’s consider an example. For the permutation π = [1,4,7,2,5,3,6], suppose we have calculated ui and di for all i ≥ 3. We now want to find u2 and d2. This is what we know so far.
Index i |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
π(i) |
1 |
4 |
7 |
2 |
5 |
3 |
6 |
ui |
? |
? |
1 |
3 |
2 |
2 |
1 |
di |
? |
? |
3 |
1 |
2 |
1 |
1 |
To find u2, note that for indices j = 3, 5, and 7 we have π(2) < π( j). Note that u3 = 1, u5 = 2, and u7 = 1, so u5 is largest. We therefore set u2 = u5 + 1 = 3. Indeed, the longest increasing subsequence starting at position 2 is [4,5,6].
Finding d2 is analogous. For indices j = 4 and 6 we have π(2) > π( j). Note that d4 = 1 and d6 = 1, so we set d2 = 2.
We create a procedure named monotone that takes a Permutation as its argument and returns a pair of long integers giving the lengths of the longest increasing and decreasing subsequences in the Permutation. Here is the header file, which we call monotone.h.
Program 11.3: Header file monotone.h.
1 #ifndef MONOTONE_H
2#define MONOTONE_H
3
4 #include "Permutation.h"
5#include <utility>
6
7pair<long,long> monotone(const Permutation& P);
8
9#endif
Note that we have #include <utility> because the monotone procedure returns a pair. The utility header file is needed to define the pair class.
The code for this procedure is housed in a file named monotone.cc that we present next.
Program 11.4: A program to find the length of the longest monotone subsequences of a permutation.
1#include "monotone.h"
2
3pair<long,long> monotone(const Permutation& P) {
4long* up;
5long* dn;
6long n = P.getN();
7
8 up = new long[n+1];
9dn = new long[n+1];
10
11for (long k=1; k<=n; k++) {
12up[k] = dn[k] = 1;
Permutations |
231 |
13 |
} |
14 |
|
15for (long k=n-1; k>=1; k--) {
16for (long j=k+1; j<=n; j++) {
17if (P(k) > P(j)) {
18if (dn[k] <= dn[j]) {
19dn[k] = dn[j]+1;
20}
21}
22else {
23if (up[k] <= up[j]) {
24up[k] = up[j]+1;
25}
26}
27}
28}
29
30long up_max = 1;
31long dn_max = 1;
32for (long k=1; k<=n; k++) {
33if (up_max < up[k]) up_max = up[k];
34if (dn_max < dn[k]) dn_max = dn[k];
35}
36
37delete[] up;
38delete[] dn;
39
40return make_pair(up_max,dn_max);
41}
The arrays up and dn are used to hold the sequences ui and di.
Finally, we need a main to generate random permutations repeatedly and calculate the average lengths of longest increasing and decreasing subsequences. Here is such a program.
Program 11.5: A program to illustrate Ulam’s problem.
1 #include "Permutation.h"
2 #include "uniform.h"
3 #include "monotone.h"
4 #include <iostream>
5using namespace std;
6
7 int main() {
8long n;
9long reps;
10 |
seed(); |
11 |
|
12cout << "Enter n (size of permutation) --> ";
13cin >> n;
14 |
cout << "Enter number of repetitions --> "; |
15 |
cin >> reps; |
16 |
|
17 |
Permutation P(n); |
18 |
|
232 |
C++ for Mathematicians |
19long sum_up = 0;
20long sum_dn = 0;
21
22for (long k=0; k<reps; k++) {
23P.randomize();
24pair<long,long> ans;
25ans = monotone(P);
26sum_up += ans.first;
27sum_dn += ans.second;
28}
29
30cout << "Average length of longest increasing subsequence is "
31<< double(sum_up)/double(reps) << endl;
32
33cout << "Average length of longest decreasing subsequence is "
34<< double(sum_dn)/double(reps) << endl;
35
36return 0;
37}
|
|
Here is the result of running this program for permutations in Sn for n equal to |
|
|
||
|
|
10,000 for one hundred iterations. |
|
|
|
|
|
|
Enter n |
(size of permutation) --> |
10000 |
|
|
|
|
|||||
|
|
Enter number of repetitions --> |
100 |
|
|
|
|
|
Average |
length of longest increasing subsequence is 192.31 |
|
|
|
|
|
Average |
length of longest decreasing subsequence is 192.94 |
|
|
|
|
|
|
|
|
For this case, we observe E(Ln)/√n ≈ 1.9. In fact,
lim E√(Ln) = 2.
n→∞ n
11.4Exercises
11.1Devise a procedure or method to find the order of a permutation. (For a permutation π, the order of π is the least positive integer k so that πk is the identity.)
11.2Create a class name Counted that can keep track of the number of objects of type Counted which exist at any point in the program. That is, the Counted class should include a static method named count that returns the number of Counted objects currently in memory.
For example, consider the following main().
#include "Counted.h" #include <iostream> using namespace std;
int main() {
Permutations
Counted X, Y; Counted* array;
array = new Counted[20];
cout << "There are " << Counted::count()
<< " Counted objects in memory" << endl;
delete[] array;
cout << "And now there are " << Counted::count() << " Counted objects in memory" << endl;
return 0;
}
This should produce the following output.
There are 22 Counted objects in memory
And now there are 2 Counted objects in memory
233
(It’s hard to imagine a mathematical reason we might want to keep track of how many objects of a given sort are being held in memory, but this is a useful debugging trick to see if there is a memory leak.)
11.3Redo Exercise 8.5 (page 152) without using the container classes of the Standard Template Library. That is, the parts of the partition should be held in a conventional C++ array and maintained in sorted order. This change requires you to create a copy constructor, an assignment operator, and a destructor.
Replace the get_parts method by an operator[]; if P is a Partition, then the expression P[k] should give the kth part of the partition.
11.4Let n be a positive integer. Suppose that n points are placed in the unit cube [0,1]3. Write a procedure to find the size s of a largest subset of these n points
such that their coordinates satisfy
x1 ≤ x2 ≤ x3 ≤ ··· ≤ xs y1 ≤ y2 ≤ y3 ≤ ··· ≤ ys z1 ≤ z2 ≤ z3 ≤ ··· ≤ zs
Suppose the points are placed in the unit cube independently and uniformly at random. Use your procedure to conjecture the expected size of the largest such set.
11.5Create a class SmartArray that behaves as a C++ array on long values, but that allows any integer subscript. That is, a SmartArray is declared like this:
SmartArray X(100);
This creates an object X that has the same behavior as if it were declared long X[100]; but allows indexing beyond the range 0 to 99. An index of
234 |
C++ for Mathematicians |
k outside this range is replaced by k mod 100. In this way, X[-1] refers to the last element of the array (in this case X[99]).
Hint: The hard part for this problem is enabling the expression X[k] to appear on the left side of an assignment. Suppose you declare the indexing operator like this:
long operator[](long k);
Then you cannot have an expression such as X[2]=4;. The trick is to declare the operator like this:
long& operator[](long k);
11.6Create a class to represent linear fractional transformations. These are functions of the form
f (z) = az + b cz + d
where z,a,b,c,d C.
Be sure to include an operator() for evaluating a linear fractional transformation at a given complex number and an operator* for the composition of two transformations.
11.7Create a Path class that represents a polygonal path in the plane (i.e., an ordered sequence of Points in R2). Include the following methods:
•A default constructor that creates an empty path.
•A one-argument constructor that creates a path containing a single point.
•An operator+ that concatenates two paths, or concatenates a path and a point. (Be sure to take care of both Path+Point and Point+Path.)
•An operator[] to get the kth point on the path.
Chapter 12
Polynomials
In this chapter we create a C++ class to represent polynomials. The coefficients of these polynomials can be from any field K such as the real numbers, the complex numbers, or Zp. One way to do this is to create several different polynomial classes depending on the coefficient field. A better solution is to learn how to use C++ templates.
12.1Procedure templates
Imagine that we often need to find the largest of three quantities in our programming. We create a procedure named max_of_three like this:
long max_of_three(long a, long b, long c) { if (a<b) {
if (b<c) return c; return b;
}
if (b<c) return c; return b;
}
Then, the expression max_of_three(3,6,-2) evaluates to 6.
If we also want to find the maximum of three real values, we create another version of max_of_three:
double max_of_three(double a, double b, double c) { if (a<b) {
if (b<c) return c; return b;
}
if (b<c) return c; return b;
}
Pythagorean triples (see Chapter 7) can also be compared by <, so if we want to compare PTriple objects, we create yet another version of max_of_three:
PTriple max_of_three(PTriple a, PTriple b, PTriple c) { if (a<b) {
if (b<c) return c;
235