Use semantics to prove that the postcondition is true following the execution of the program assuming the precondition is true - compilation

I am trying to study for a test in my programming language concepts class.
I am trying to understand how to solve this problem. Our professor said we don't need to use formal notation to prove the problem as long as he can understand what we are saying.
I missed the lecture where he solved the problem and I'm having a very hard time finding resources to help me solve it on my own.
Would be so thankful for an explanation.
Problem
Use axiomatic semantics to prove that the postcondition is true following the execution of the program assuming the precondition is true
Precondition: n ≥ 0 and A contains n elements indexed from 0
bound = n;
while (bound > 0) {
t = 0;
for (i = 0; i < bound-1; i++) {
if (A[i] > A[i+1]) {
swap = A[i];
A[i] = A[i+1];
A[i+1] = swap;
t = i+1;
}
}
bound = t;
}
Postcondition: A[0] ≤ A[1] ≤ ...≤ A[n-1]

Lets number the lines for reference:
1. bound = n;
2. while (bound > 0) {
3. t = 0;
4. for (i = 0; i < bound-1; i++) {
5. if (A[i] > A[i+1]) {
6. swap = A[i];
7. A[i] = A[i+1];
8. A[i+1] = swap;
9. t = i+1;
10. }
11. }
12. bound = t;
13. }
Consider the following assertions:
Before entering 12
t < bound
Before entering 11
A[i] <= A[t] for all i such that 0 <= i < t
Before entering 13
A[k] <= A[j] for all indexes k and j such that bound <= k <= j <= n-1
After leaving 12
bound has decreased
Let's see now why the assertions are true
This is true because t=0 before the loop and if set inside the if it is
t = i + 1 < (bound - 1) + 1 = bound.
This is true because otherwise a swap would have happened.
This is true because of 2 and because the for doesn't change entries with indexes j from bound to n-1.
This is true because of 1.
From assertion 4 we deduce that the while loop, and so the algorithm, finishes in n steps at most, when bound = 0.
The postcondition now follows from assertion 3 for bound = 0.

Related

How to work out the time complexity in terms of the number of operations

so I was wondering how would I work out the time complexity (T(n)) of a piece of code, for example, the one below, in terms of the number of operations.
for( int i = n; i > 0; i /= 2 ) {
for( int j = 1; j < n; j *= 2 ) {
for( int k = 0; k < n; k += 2 ) {
... // constant number of operations
}
}
}
I'm sure its simple but this concept wasn't taught very well by my lecturer and I really want to know how to work out the time complexity!
Thank you in advance!
To approach this, one method is to breakdown the complexity of your three loops individually.
A key observation we can make is that:
(P): The number of steps in each of the loop does not depend on the value of the "index" of its parent loop.
Let's call
f(n) the number of operations aggregated in the outer loop (1)
g(n) in the intermediate inner loop (2)
h(n) in the most inner loop (3).
for( int i = n; i > 0; i /= 2 ) { // (1): f(n)
for( int j = 1; j < n; j *= 2 ) { // (2): g(n)
for( int k = 0; k < n; k += 2 ) { // (3): h(n)
// constant number of operations // => (P)
}
}
}
Loop (1)
Number of steps
i gets the values n, n/2, n/4, ... etc. until it reaches n/2^k where 2^k is greater than n (2^k > n), such that n/2^k = 0, at which point you exit the loop.
Another way to say it is that you have step 1 (i = n), step 2 (i = n/2), step 3 (i = n/4), ... step k - 1 (i = n/2^(k-1)), then you exit the loop. These are k steps.
Now what is the value of k? Observe that n - 1 <= 2^k < n <=> log2(n - 1) <= k < log2(n) <= INT(log2(n - 1)) <= k <= INT(log2(n)). This makes k = INT(log2(n)) or loosely speaking k = log2(n).
Cost of each step
Now how many operations do you have for each individual step?
At step i, it is g(i) = g(n) according to the notations we chose and the property (P).
Loop (2)
Number of steps
You have step (1) (j = 1), step (2) (j = 2), step (3) (j = 4), etc. until you reach step (p) (j = 2^p) where p is defined as the smallest integer such that 2^p > n, or loosely speaking log2(n).
Cost of each step
The cost of step j is h(j) = h(n) according to the notations we chose and the property (P).
Loop (3)
Number of steps
Again, let's count the steps: (1):k = 0, (1):k = 2, (2):k = 4, ..., k = n - 1 or k = n - 2. This amounts to n / 2 steps.
Cost of each step
Because of (P), it is constant. Let's call this constant K.
All loops altogether
The number of aggregated operations is
T(n) = f(n) = sum(i = 0, i < log2(n), g(i))
= sum(i = 0, i < log2(n), g(n))
= log2(n).g(n)
= log2(n).sum(j = 0, j < log2(n), h(j))
= log2(n).log2(n).h(n)
= log2(n).log2(n).(n/2).K
So T(n) = (K/2).(log2(n))^2.n
Write a method, add a counter, return the result:
int nIterator (int n) {
int counter = 0;
for( int i = n; i > 0; i /= 2 ) {
for( int j = 1; j < n; j *= 2 ) {
for( int k = 0; k < n; k += 2 ) {
++counter;
}
}
}
return counter;
}
Protocol for fast increasing N and document in a readable manner the results:
int old = 0;
for (int i = 0, j=1; i < 18; ++i, j*=2) {
int res = nIterator (j);
double quote = (old == 0) ? 0.0 : (res*1.0)/old;
System.out.printf ("%6d %10d %3f\n", j, res, quote);
old=res;
}
Result:
1 0 0,000000
2 2 0,000000
4 12 6,000000
8 48 4,000000
16 160 3,333333
32 480 3,000000
64 1344 2,800000
128 3584 2,666667
256 9216 2,571429
512 23040 2,500000
1024 56320 2,444444
2048 135168 2,400000
4096 319488 2,363636
8192 745472 2,333333
16384 1720320 2,307692
32768 3932160 2,285714
65536 8912896 2,266667
131072 20054016 2,250000
So n is increasing by factor 2, the counter increases in the beginning with more than 2², but then decreases rapidly towards something, not much higher than 2. This should help you find the way.

Run time Complexity

I believe that the following code is big theta of n^3, is this correct?
for (int i = 0; i < n; i ++)
{ // A is an array of integers
if (A[i] == 0) {
for (int j = 0; j <= i; j++) {
if (A[i] == 0) {
for (int k = 0; k <= j; k++) {
A[i] = 1;
}
}
}
}
}
And that the following is big theta of nlog(n)
for (int i = 1; i < n; i *= 2)
{
func(i);
}
void func(int x) {
if (x <= 1) return;
func(x-1);
}
because the for loop would run log(n) times, and func runs at most n recursive calls.
Thanks for the help!
Your intuition looks correct. Note that for the first bit if the input contains non-zero elements the time complexity drops down to big-theta(n). If you remove the checks it would definitely be big-theta(n^3).
You are correct about the second snippet, however the first is not Big-Theta(n^3). It is not even O(n^3)! The key observation is: for each i, the innermost loop will execute at most once.
Obviously, the worst-case is when the array contains only zeros. However, A[i] will be set to 1 in the first pass of the inner-most loop, and all subsequent checks of if (A[i] == 0) for the same i will be evaluated to false and the innermost loop will not be executed anymore until i increments. Therefore, there are total of 1 + 2 + 3 + .. + n = n * (n + 1) / 2 iterations, so the time complexity of the first snippet is O(n^2).
Hope this helps!

Number of ways to take k steps on a path of length N

We have a path of length N. At a time we can only take a unit step. How many ways we can take K steps while remaining inside the path. Initially we are at the 0th position.
example N =5
|---|---|---|---|---|
0 1 2 3 4 5
if k = 3 then we move like -
0->1->2->1
0->1->0->1
0->1->2->3
Can you please give some directions/links on how to approach this problem?
It's likely to be solvable using combinatorial methods rather than computational methods. But since you're asking on stackoverflow, I assume you want a computational solution.
There's a recurrence relation defining the number of paths ending at i:
P[N, 0, i] = 1 if i==0 otherwise 0
P[N, K, i] = 0 if i<0 or i>N
P[N, K, i] = P[N, K-1, i-1] + P[N, K-1, i+1]
We can iteratively compute the array of P[N, K, i] for i=0..N for a given K from the array P[N, K-1, i] for i=0..N.
Here's some Python code that does this. It uses a small trick of having an extra 0 at the end of the array so that r[-1] and r[N+1] are both zero.
def paths(N, K):
r = [1] + [0] * (N+1)
for _ in xrange(K):
r = [r[i-1]+r[i+1] for i in xrange(N+1)] + [0]
return sum(r)
print paths(5, 3)
This runs in O(NK) time.
A different (but related) solution is to let M be the (N+1) by (N+1) matrix consisting of 1's at positions (i+1,i) and (i,i+1) for i=0..N+1, and 0's elsewhere -- that is, 1's on the subdiagonal and superdiagonal. Then M^K (that is, M raised to the Kth power) contains at position (i, j) the number of paths from i to j in K steps. So sum(M^K[0,i] for i=0..N) is the total number of all paths starting at 0 of length K. This runs in O(N^3logK) time, so is better than the iterative method only if K is much larger than N.
Java implementation of first approach in accepted answer -
for (int i = 0; i <= K; i++) {
for (int j = 1; j <= N; j++) {
if (i > 0)
dp1[i][j] = (dp1[i - 1][j - 1] + dp1[i - 1][j + 1]) % 1000000007;
else
dp1[i][j] = 1;
}
}
System.out.println(dp1[K][N-1])
Complexity O(KN)
Java DP implementation, it computes answers for all starting positions and values 1-N and 1-K -
for (int i = 0; i <= K; i++) {
for (int j = 1; j <= N; j++) {
for (int k = 1; k <= j; k++) {
if (i > 0)
dp[k][j][i] =
(dp[k - 1][j][i - 1] + dp[k + 1][j][i - 1]) % 1000000007;
else
dp[k][j][i] = 1;
}
}
}
System.out.println(dp[1][5][3]);
O(KN^2)

Nested loops, how many times run and complexity

I have these 2 codes, the question is to find how many times x=x+1 will run in each occasion as T1(n) stands for code 1 and T2(n) stands for code 2. Then I have to find the BIG O of each one, but I know how to do it, the thing is I get stuck in finding how many times ( as to n of course ) will x = x + 1 will run.
CODE 1:
for( i= 1; i <= n; i++)
{
for(j = 1; j <= sqrt(i); j++)
{
for( k = 1; k <= n - j + 1; k++)
{
x = x + 1;
}
}
}
CODE 2:
for(j = 1; j <= n; j++)
{
h = n;
while(h > 0)
{
for (i = 1; i <= sqrt(n); i++)
{
x = x+1;
}
h = h/2;
}
}
I am really stuck, and have read already a lot so I ask if someone can help me, please explain me analytically.
PS: I think in the code 2 , this for (i = 1; i <= sqrt(n); i++) will run n*log(n) times, right? Then what?
For code 1 you have that the number of calls of x=x+1 is:
Here we bounded 1+sqrt(2)+...+sqrt(n) with n sqrt(n) and used the fact that the first term is the leading term.
For code 2 the calculations are simpler:
The second loop actually goes from h=n to 0 by iterating h = h/2 but you can see that this is the same as going from 1 to log n. What we used is the fact the j, t, i are mutually independent (analogously just like we can write that sum from 1 to n of f(n) is just nf(n)).

Correctness of Hoare Partition

Hoare partition as given in cormen:
Hoare-Partition(A, p, r)
x = A[p]
i = p - 1
j = r + 1
while true
repeat
j = j - 1
until A[j] <= x
repeat
i = i + 1
until A[i] >= x
if i < j
swap( A[i], A[j] )
else
return j
when using this in Quick Sort, with {1,3,9,8,2,7,5} as input, after first partition getting {1,3,5,2,8,7,9}, which is not correct since, all elements smaller to pivot( here 5 ) should be on the left side. Can someone point out as to what I am missing?
The algorithm is correct. You're partitioning the subarray A[p..r] using A[p] as the pivot. So the pivot is 1 and not 5.
Hoare-Partition(A=[1,3,9,8,2,7,5], p=0, r=6)
results in:
x = A[p] = 1
i = -1
j = 7
repeat:
j = j - 1 = 6; A[j] = 5
j = j - 1 = 5; A[j] = 7
j = j - 1 = 4; A[j] = 2
...
j = j - 1 = 0; A[j] = 1
A[j] == x
repeat:
i = i + 1 = 0; A[i] = 1
A[i] == x
if i < j
i == j, therefore return j
In this case, no elements are swapped.
I don't have Cormen in front of me, but I'm pretty sure that the comparison in the first loop should be until A[j] < x - if it's <=, you'll move the pivot element to the left side and leave it there (followed by smaller elements), which is what happened in your example. Alternatively, the first comparison could be <= and the second could be > - just make sure that the pivot element won't get "caught" by both comparisons.

Resources