Skip to content

Latest commit

 

History

History
178 lines (129 loc) · 6.34 KB

File metadata and controls

178 lines (129 loc) · 6.34 KB

$\huge\color{cadetblue}{\text{Problem 2}}$


$\Large{\color{rosybrown}\text{Prob 2.1: }}{\color{darkseagreen}{{\space \mathcal{O}(N)}}}$


int s = 0;
for (int i = N; i > 0; i /= 2) {
  for (int j = i; j > 0; j--) {
    s += i*i;
  }
}

The outer loop runs $\log(N)$ times, since $i$ starts at $N$ and is then halved after each iteration. The inner loop runs $i$ times for each value of $i$, which is $N/2^k$ for $k \in \lbrace 0, 1, \dots, \lceil \log(N) \rceil \rbrace$. The total number of iterations of the inner loop is therefore given by:

$$ \begin{align*} & \quad \sum_{k=0}^{\lceil \log(N) \rceil} \frac{N}{2^k} \\ = & \quad N \sum_{k=0}^{\lceil \log(N) \rceil} \frac{1}{2^k} \\ = & \quad N \left( \frac{\frac{1}{2^{\lceil \log(N) \rceil + 1}} -1}{\frac{1}{2} - 1} \right) &\color{peru}{(1)} \\ = & \quad N \left(1 - \frac{1}{2^{\lceil \log(N) \rceil}} \right) \\ = & \quad \mathcal{O}(N) \end{align*} $$

In $\color{peru}{(1)}$ we use the formula for the sum of a geometric series, with $a = 1/2$ and $n = \lceil \log(N) \rceil$:

$$ \sum_{k=0}^n a^k = \frac{a^{n+1} - 1}{a - 1} \quad \text{for} \quad a \neq 1 $$

From the above, we therefore have that the fragment's time complexity is in $\mathcal{O}(N)$.

Note that we could have run an argument for a complexity in $\mathcal{O}(N \log(N))$ by stating that the outer loop runs in logarithmic time and the inner loop runs $\color{orchid}{\text{at most}}$ $N$ times (or $N$ times in the worst case), so that the nested loop's total complexity becomes linearithmic in $N$. This is not wrong, but an overestimate, as the tight bound is $\mathcal{O}(N)$. It is testomony to the fact that rough reasoning does not always yield the tightest bound. The same situation arises in Ex5 2013, and Ex5 2018 resit.



$\Large{\color{rosybrown}\text{Prob 2.2: }}{\color{darkseagreen}{{\space \mathcal{O}(N \log (N))}}}$


int s = 0;
for (int i = N; i > 0; i--) {
  for (int j = i; j > 0; j /= 2) {
    s += i*i;
  }
}

The fragment is similar to the previous one, but the loops are nested in the opposite order. The outer loop runs $N$ times, and the inner loop runs $\log(i)$ times for each value of $i$. The total number of iterations of the inner loop is therefore given by:

$$ \begin{align*} \sum_{i=N}^1 \log(i)\quad = & \quad \sum_{i=1}^N \log(i) \\ = & \quad \log \left( \prod_{i=1}^N i \right) \\ = & \quad \log(N!) \\ = & \quad \mathcal{O}(N\log(N)) \end{align*} $$

Therefore, the fragment's time complexity is in $\mathcal{O}(N\log(N))$.



$\Large{\color{rosybrown}\text{Prob 2.3: }}{\color{darkseagreen}{{\space \mathcal{O}(N^2)}}}$


int s = 0;
for (int i = N; i > 0; i--) {
  for (int j = i; j > 0; j -= 2) {
    s += i*i;
  }
}

The outer loop runs $N$ times, and the inner loop runs $i/2$ times for each value of $i$. The total number of iterations of the inner loop is therefore given by:

$$ \begin{align*} \sum_{i=N}^1 \frac{i}{2}\quad = & \quad \frac{1}{2} \sum_{i=1}^N i \\ = & \quad \frac{1}{2} \cdot \frac{N(N+1)}{2} \\ = & \quad \mathcal{O}(N^2) \end{align*} $$

Therefore, the fragment's time complexity is in $\mathcal{O}(N^2)$.



$\Large{\color{rosybrown}\text{Prob 2.4: }}{\color{darkseagreen}{{\space \mathcal{O}(\sqrt N)}}}$


int s = 0;
for (int i = 0; i < N/i; i++) {
  s += i;
}

The loop condition is $i &lt; N/i$, which is equivalent to $i^2 &lt; N \Leftrightarrow i &lt; \lfloor \sqrt{N} \rfloor$. Therefore, the loop runs about $\sqrt{N}$ times, and the fragment's time complexity is in $\mathcal{O}(\sqrt{N})\space$.
To be fair, though, the program fragment will actually result in undefined behavior as the initial value of the loop condition is undefined, with $i$ being initialized to 0.



$\Large{\color{rosybrown}\text{Prob 2.5: }}{\color{darkseagreen}{{\space \mathcal{O}(\log (N))}}}$


int s = 0, i = N;
while (i >= 0) {
  int d = 1 + i % 5;
  s += d;
  i = (i % d == 0 ? i - 1 : i/d);
}

The variable $i$ is initialized to $N$, and is either decremented by $1$ or divided by $d$ in each iteration. The value of $d$ is always between $1$ and $5$. Only when $i$ is evenly divisible by $d$ is it decremented by $1$, otherwise it is integer divided by $d$. This means that each iteration has a one in five chance that $i$ is decremented by $1$, and a four in five chance that $i$ is divided by $d$. Clearly, the division is the dominant operation. The fragment's time complexity is therefore in $\mathcal{O}(\log(N))$.



$\Large{\color{rosybrown}\text{Prob 2.6: }}{\color{darkseagreen}{{\space \mathcal{O}(N)}}}$


int j = 0, s = 0;
for (i = 1; i < N; i *= 2) {
  while (j < i) {
    s += (i + j);
    j++;
  }
}

The outer loop runs $\log(N)$ times, since $i$ is doubled after each iteration. This means that $i$ will always be a power of two. For each iteration of the outer loop, the inner one makes $j$ catch up to the current value of $i$. This is because $j$ is not reset to 0, but its end value from the previous iteration (a power of two preceding the current power of two held by $i$) is instead incremented until it equals $i$ again. This means that the inner loop runs $i - j$ times for each value of $i$. The total number of iterations of the inner loop is therefore given by:

$$ \begin{align*} & \quad \sum_{k=1}^{\lceil \log(N) \rceil} (2^k - 2^{k-1}) \\ = & \quad \sum_{k=1}^{\lceil \log(N) \rceil} 2^{k-1} \\ = & \quad \sum_{k=0}^{\lceil \log(N) \rceil - 1} 2^k &\color{peru}{(1)}\\ = & \quad 2^{\lceil \log(N) \rceil} - 1 &\color{darkkhaki}{(2)}\\ \leq & \quad 2^{\log(N) + 1} - 1 \\ = & \quad 2N - 1 \\ = & \quad \mathcal{O}(N) \end{align*} $$

In $\color{peru}{(1)}$ we rewrite the sum, so that the index $k$ starts at $0$ instead of $1$. This allows us to use the formula for the sum of a geometric series in $\color{darkkhaki}{(2)}$, with $a = 2$ and $n = \lceil \log(N) \rceil - 1$:

$$ \sum_{k=0}^n a^k = \frac{a^{n+1} - 1}{a - 1} \quad \text{for} \quad a \neq 1 $$

From the above, we therefore have that the fragment's time complexity is in $\mathcal{O}(N)$.