Computational Cost#
Analyzing running time#
In computer science, data structures and algorithms are fundamental concepts used to efficiently store and manipulate data. Data structures are ways of organizing and storing data in a computer’s memory, while algorithms are step-by-step procedures for performing specific tasks. The computational cost of data structures and algorithms refers to the amount of time and resources required to execute a particular operation or task.
When designing or choosing data structures and algorithms, it’s important to consider their computational cost. Some operations may require a lot of time and resources, which can impact the overall performance of a system. The computational cost can be analyzed using various measures such as time complexity, space complexity, and big-O notation.
Time complexity measures the number of operations required to perform a task as a function of the input size. Space complexity measures the amount of memory required to store data structures and algorithms. Big-O notation is used to express the upper bound of the computational cost in terms of the input size.
By understanding the computational cost of data structures and algorithms, developers can make informed decisions about which ones to use in a particular context. For example, if the system needs to perform a lot of searches, a data structure with fast search time complexity like a hash table may be a better choice than a binary search tree. Similarly, if memory usage is a concern, a data structure with low space complexity like an array may be a better choice than a linked list.
Run algorithm
Measure actual time
Analyze algorithm
Develop Model
Theoretical Models#
High-level analysis
no need to implement
Independent of hardware / software
Based on counts of basic instructions
additions, multiplications, comparisons,etc
exact definition not important but must be relevant to the problem
In order to use a formal framework, we will make certain assumptions
count basic instructions: additions, multiplications, comparisons, assignments
each instruction takes one time unit
instructions are executed sequentially
infinite memory
Focus on analyzing running time
Examples#
What to count?
only relevant instructions?
all instructions?
1for (int i = 0; i < n; i++) {
2 std::cout << (2 * i);
3}
Time Complexity & Why…
The time complexity of this code is \(O(n)\), because the operation within the loop is done n
times.
#include <iostream>
using namespace std;
int main()
{
int i, n = 8;
for (i = 1; i <= n; i++) {
cout << "Hello World !!!\n";
}
return 0;
}
Output
Hello World !!!
Hello World !!!
Hello World !!!
Hello World !!!
Hello World !!!
Hello World !!!
Hello World !!!
Hello World !!!
- Time Complexity
In the above code “Hello World !!!” is printed only
n
times on the screen, as the value ofn
can change. So, the time complexity is linear: \(O(n)\) i.e. every time, a linear amount of time is required to execute code.- Auxiliary Space
\(O(1)\)
1for (int i = 0; i < n; i++) {
2 for (int j = 0; j < n; j++) {
3 std::cout << (i * j);
4 }
5}
Time Complexity & Why…
This code has a time complexity of \(O(n^2)\). This is because it has nested loops that are both iterating over n
elements, so the total number of operations increases quadratically with the size of the input.
1for (int i = 0; i < n; i++) {
2 for (int j = 0; j < n * n; j++) {
3 std::cout << (i * j);
4 }
5}
Time Complexity & Why…
The time complexity of this code is \(O(n^3)\). This is because there are two nested for loops, each of which has a time complexity of \(O(n)\), resulting in a time complexity of \(O(n^2)\). The total time complexity is \(O(n^3)\) because the inner loop is iterating \(n * n\) times.
1for (int i = 0; i < n; i++) {
2 for (int j = 0; j < i; j++) {
3 sum = sum * j;
4 }
5}
Time Complexity & Why…
This code has a time complexity of \(O(n^2)\). This is because there is an outer loop that runs n
times and an inner loop that runs i times, where i
is the value of the outer loop’s iterator. Since i
starts at 0
and increases each iteration of the loop, the inner loop will run 0
times on the first iteration, 1
time on the second iteration, 2
times on the third iteration, and so on, up to n
times on the last iteration. This results in a total of \(n(n+1)/2\) iterations of the inner loop, which is equivalent to \(O(n^2)\).
1for (int i = 0; i < n; i++) {
2 for (int j = 0; j < n; j++) {
3 for (int k = 0; k < n; k++) {
4 //count 1 instruction
5 }
6 }
7}
Time Complexity & Why…
The time complexity of this code is \(O(n^3)\) because there are three nested loops, each of which runs n
times. This results in \(n^3\) total iterations of the innermost loop.
1for (int i = 0; i < n; i++) {
2 for (int j = 0; j < i * i; j++) {
3 for (int k = 0; k < j; k++) {
4 // count 1 instruction
5 }
6 }
7}
Time Complexity & Why…
This code has a time complexity of \(O(n^4)\). This is because the first loop runs in \(O(n)\) time, the second loop runs in \(O(n^2)\) time, and the third loop runs in \(O(n)\) time. Therefore, the total time complexity is the product of all of these which is \(O(n^4)\).
Some rules…#
essentially, \(total\ iterations\ \times total\ instructions\ performed\ per\ iteration\)
count instructions inside out
careful with the range of the loop
when possible, multiplications can be used for counts from each loop
just add the counts
consider the branch with the highest count
Computational cost#
Number of basic instructions required by the algorithm to process an input of a certain size \(n\)
basic instructions are always relevant to the problem
ex: find max in an array
# of comparisons
ex: sum elements in an array
# of additions
Comparing computational cost#
\(find (x,y,z)s.t.x+y+z = k\) |
\(find (x,y)s.t.x+y = k\) |
\(find\ x = k\) |
|
---|---|---|---|
Size of input |
\(n^3\) |
\(n^2\) |
\(n\) |
\(n = 1\) |
1 |
1 |
1 |
\(n = 10\) |
1000 |
100 |
10 |
\(n = 100\) |
1000000 |
10000 |
100 |
\(n = 1000\) |
1000000000 |
1000000 |
1000 |
\(n = 10000\) |
1000000000000 |
100000000 |
10000 |
\(n = 100000\) |
1000000000000000 |
10000000000 |
100000 |
\(n = 1000000\) |
1000000000000000000 |
1000000000000 |
1000000 |
\(n = 10000000\) |
1000000000000000000000 |
100000000000000 |
10000000 |
Growth Rate#
\(Constant\) |
\(Log-Logarithmic\) |
\(Logarithmic\) |
\(Linear\) |
\(Linearithmic\) |
\(Quadratic\) |
\(Cubic\) |
\(Exponential\) |
---|---|---|---|---|---|---|---|
\(n\) |
\(log\ log\ n\) |
\(log\ n\) |
\(n\) |
\(n\ log\ n\) |
\(n^2\) |
\(n^3\) |
\(2^n\) |
\(16\) |
\(2\) |
\(4\) |
\(2^4\) |
\(4 \times 2^4 = 2^6\) |
\(2^8\) |
\(2^{12}\) |
\(2^{16}\) |
\(256\) |
\(3\) |
\(8\) |
\(2^8\) |
\(8 \times 2^8 = 2^{11}\) |
\(2^{16}\) |
\(2^{24}\) |
\(2^{256}\) |
\(1024\) |
\(\approx 3.3\) |
\(10\) |
\(2^{10}\) |
\(10 \times 2^{10} = 2^{13}\) |
\(2^{20}\) |
\(2^{32}\) |
\(2^{1024}\) |
\(64K\) |
\(4\) |
\(16\) |
\(2^{16}\) |
\(16 \times 2^{16} = 2^{20}\) |
\(2^{32}\) |
\(2^{48}\) |
\(2^{64K}\) |
\(1M\) |
\(\approx 4.3\) |
\(20\) |
\(2^{20}\) |
\(20 \times 2^{20} = 2^{24}\) |
\(2^{40}\) |
\(2^{60}\) |
\(2^{1M}\) |
\(1G\) |
\(\approx 4.9\) |
\(30\) |
\(2^{30}\) |
\(30 \times 2^{30} = 2^{35}\) |
\(2^{60}\) |
\(2^{90}\) |
\(2^{1G}\) |