at the present time, do you know, What is **Time complexity and space complexity** in data structures and algorithms?

with this in mind, let me explain to you what do we mean by time complexity and space complexity

in reality, for analyzing an algorithm we usually use two parameters know as time and space to measure the efficiency of an algorithm

in essence, time and space are the scariest resources that we have

So, we should use it wisely to produce efficient results with speed

For example: Suppose you are given the task of transferring media files to some other country

**in sum**, **what will you do?**

You might say, you will transfer it through the internet but the problem is that the file is large (20TB) terabytes and you have internet with a bandwidth (transfer rate of 1000 Mbps)

to sum up, how much time will it take to transfer through the internet?

in reality, it will take 2days and 52 minutes, 2 seconds

If it is urgent and you don’t mind cost then it would be better to transfer files through aircraft

So, that it will be delivered within a few hours to the desired destination

**To sum up**: big O notation is a way to describe the algorithmic complexity of a function for a given n amount of input

**Time complexity** means if the time to run the function increases then it is called time complexity

**Space complexity** means if the memory to run the function increases then it is called space complexity

#### How to describe the runtime of an algorithm

in a word, we can describe the runtime of an algorithm by using a big O OR asymptotic notations

Remember in essence, Big O is an equation that describes how time changes to certain input

on the whole, **for internet transfer**: we can describe the runtime of a data transfer algorithm as **O(s)** where ‘s’ is the size of the media file

in short, the time to transfer media file increases linearly with the size of the file

**for aircraft transfer**: we can describe the runtime of a data transfer algorithm as **O(1)** because the time will remain constant no matter how much the size increase

**Note**: you should not have to use O(n) in every equation that, you write, instead you can use other variables such as O(s)

```
for (int i=0;i<b;;i++)
{
sum +=a;
}
```

**For example**, for the loop shown above, we can write runtime of it as **O(b)** instead of O(n)

Always the linear will surpass the constant at some point even the constant so big or how slow might the linear growth

#### Why we use time and space complexity

- Time and space complexity gives you an estimation so, that you won’t get shocked when the result is produced
- It produces a choice to select one of the two ways of doing something
- You will know, which part of the code should be optimized to utilize the resources efficiently

#### How to express time and space complexity

in brief, we can express time and space complexity by using a function called f(n)

without a doubt, where n is the size of the input

to sum up, the function f(n) is expressed by using the big O notation

At the same time, with time and space complexity, we would also like to predict the complexity of the algorithm as input size increases

at this point, the below graph show’s the rate of growth of some famous big O times

So, that means O(1) has the least complexity while O(x!) has the highest complexity

#### Space complexity

in a word, space complexity means the space or memory which is required by the algorithm to run efficiently

in contrast, the space complexity is a parallel concept to time complexity

For example: to iterate an array from 0 to n it will take time complexity **O(n)** and space complexity **O(n)** space

For two dimensional array such as a matrix of order nxn it will take time complexity of **O()** and space complexity ** O()** space

#### What causes a space complexity in a function?

- Variables
- Data structures
- Function call
- Memory allocations

#### Some basics rules of big O

#### #Rule 1: Add different statements runtime

in this situation, to find the runtime of a code snippet we should add all the statements runtime

#### #Rule 2:Neglect the constant

to begin with, consider the two code snippet mentioned below for finding the min and max element in an array

this time, the first code snipped contain two loops for doing the same work as the second code snipped

on this occasion, the Second code snipped contain only one loop with two instruction to the same job as first code snipped

So, what do you think?

at this time, which code snipped will run faster?

on the contrary, do you know, both the code snippets will take the same time to run the code

**But**, **why?**

on one hand, always remember you should neglect the constant for finding the run time of the algorithm

You might say the first code snipped contain two loops so, it will take run time as O(n)+O(n)=O(2n)

to sum up, No!, it won’t!

we neglect the constant and write O(n) instead of O(2n) also don’t think that by writing O(2n) you are more precise

in this situation, No!, you are not!

#### #Rule 3:Neglect the non-dominant terms

at this time, let us consider, a code snippet as mentioned below

by all means, the first part of the code is to find a max element in an array which take O(n) run times

by and large, the second part of the code is to iterate an arrays by a and b and print all the elements O()

As we said, we should neglect the constants but what if we have an expression like this O( +n), then here n is not exactly a constant

as a matter of fact, what will you do?

You should also neglect the non-dominant terms in a big O expression

When it comes to big O notation, we usually care about significant terms that have the most impact, and also, remember, as the input grows larger then at that instant only the fastest growing terms matter most than constants

Consider some examples

- O() gives O()

- O(n+log n) gives O(n)

- O() gives O()

- O() gives O()

**Note:** the expression O() cannot be reduced until you have some specific knowledge about it

#### #Rule 4: different inputs gives different variables

at this point, consider the two code snippets first and second as mentioned below

Most people would get confused here, at this instant, they would say both, they find codes snipped are similar so, therefore it is O()

undoubtedly, No, it is not!

**Remember** when n is the size of the array, if the code snipped have two different arrays then it does not mean the length of the entire array

#### Constant time

Suppose, we are having a simple C program to print Hi there! Then how time much will it take to execute the print statement, it will take constant time to execute the print statement

in sum, the constant time does not depend on the input size

```
#include <stdio.h>
int main()
{
printf("Hi there!\n");
return 0;
}
```

to clarify, the above program is having **O(1)**

at this time, where O is called “order of”

#### Linear loops (Single loop)

Suppose, we have an Array of size 10 and we would like to iterate all the elements of a given array, then, in that case, how much time will it take to iterate all the elements of an Array?

```
for(i=0;i<9;i++)
{
printf("%d\n",arr[i]);
}
```

It usually, depends upon, how many elements are there in an array suppose, if we have n elements in an array then, in that case, the time taken will be **O(n)**

```
for(i=0;i<9;i+=2)
{
printf("%d\n",arr[i]);
}
```

On the other hand, if we iterate half of the loop then at that instant, the time taken will be **O(n/2)**

after all, with every iteration in linear loops you can ADD or SUB

#### Logarithmic loops

after that, with every iteration in Logarithmic loops, you can Divide or Multiply

**Case 1**

```
for(i=1;i<9;i=i*2)
{
printf("%d\n",arr[i]);
}
```

**Case 2**

```
for(i=9;i>=1;i=i/2)
{
printf("%d\n",arr[i]);
}
```

at this instant, the time taken by logarithmic loops will be **O(log n)**

#### Nested loops

in a word, a loop that contains loops inside it, is called a nested loop

#### Linear logarithmic loops

```
for(i=0;i<10;i++)
for(j=1;j<10;j*=2)
{
statements;
}
```

this time, every iteration of the inner loop is multiplied by 2

so far, the time taken by linear logarithmic loops to executed the statements is given as **O(n log n)**

#### Quadratic loop

to sum up, in a quadratic loop, every iteration of the outer loop is equal to every iteration of the inner loop

```
for(i=0;i<10;i++)
for(j=0;j<10;j++)
{
statements;
}
```

so far, the time taken by the quadratic loop is given by **O()**

#### Dependent quadratic loop

thereafter, in a dependent quadratic loop, the inner loop will be dependent on the outer loop

```
for(i=0;i<10;i++)
for(j=0;j<=1;j++)
{
statements
}
```

thus, the time taken by the dependent quadratic is given by **O(n+1)/2**

#### Cubic loop

**Case 1**

```
for(int i = 0; i < 10; i++)
for(int j = 0; j < 10; j = j + +)
for(int k = 0; k < 10; k = k + +)
{
statements;
}
```

**Case 2**

```
for(int i = 0; i < 10; i++)
for(int j = 0; j < 10 + 5; j = j + 2)
for(int k = 0; k < 10; k = k + 3)
{
statements;
}
```

similarly, the time taken by the three loops will be given by **O()**

#### O(n!)

Let’s talk about a big O that you won’t likely encounter in case, if you are writing some code and you have O(n!) complexity then that means you are doing something wrong

You probably won’t encounter it but it exists

```
void nFacRuntimeFunc(int n){
for(int i=0; i<n; i++){
nFacRuntimeFunc(n-1);
}
}
```

**Array sorting Algorithms**

#### Some BIG O calculation examples

at this point, to do some BIG O calculations let’s use some JavaScript examples as mentioned below

Suppose, we have a function called one that has two statements that are of O(1) and, O (1), and also a loop that contains another function called two that is outside the function one

As we know, always a for loop will be linear, so, O(n) while, the function called two is dependent upon the input size so, O(n)

Next, consider a random statement (**let stranger =true;**)that will get executed as many times as the input size so, O(n) then the statement **a++;** is also in the loop so, O(n)

```
function one(input) {
let a=10; //O(1)
a=60+33; // O(1)
for(let i=0; i<input.length; i++){ //O(n)
functiontwo(); //O(n)
let stranger =true; //O(n)
a++; //O(n)
}
return a; // O(1)
}
one() //function call
```

O(1)+ O(1)+ O(1)+ O(n)+ O(n)+ O(n)+ O(n)

3+4n

BIG O(3+4n) =n

So, BIG O(n)

```
function one(input){
let a=20; //O(1)
let b=75; //O(1)
for(let i=0; i < input; i++){
let x=i+2; //O(n)
let y=i+4; //O(n)
let z=i+2; //O(n)
}
for(let j=0; j<input; j++){
let c= j*2; //O(n)
let d= j*3; //O(n)
}
let status ="random"; //O(1)
}
```

BIG O(3+5n)=n

So, BIG O(n)

```
function sketch(items){
console.log(item[0]);
var middleIndex =Math.floor(items.length/2);
var index =0;
while (index< middleIndex){
console.log(items[index]);
index++;
}
for (var i=0; i< 100; i++){
console.log('hi');
}
}
```

The above function sketch first, print the first item in the array so, O(1) then, print half of the array items by using a while loop so, O(n/2) next, we are not looping all the items in the array but instead, we loop only 100 items by using a for loop

O(1)+O(n/2)+O(100)

O(1+n/2+100)

O(n/2+100)

BIG O(n)

```
function printAllNumbersThenAllPairSums(numbers) {
console.log('these are the numbers:');
numbers.forEach(function(number) { //O(n)
console.log(number);
});
console.log('and these are their sums:');
numbers.forEach(function(firstNumber) { //O(n)
numbers.forEach(function(secondNumber) { //O(n)
console.log(firstNumber + secondNumber);
});
});
}
printAllNumbersThenAllPairSums([1,2,3,4,5])
```

O(n)+O(n) X O(n)

O(n)+O()

O(n+)

Use the rule #3

BIG O()

Let us suppose, we have a function called print all numbers then all pair sums

In the first part of the code, print all the numbers in an array that is of O(n)

The second part of the code, contains two for loops that are of O() also it add the numbers in an array in serial order such as [1+1, 2+1, 3+1, 4+1, 5+1][2+1, 2+2, 3+2, 4+2, 5+2].. etc.

The big O notation of the function called print all numbers then all pair sums is O(n+)

The purpose of the rule#3 is to neglect the non dominant terms which mean we care only about the most important term, in this case, we neglect the O(n)

**But**, **Why?**

Because as the input increases the size of the is more important than O(n)

So, that means we always keep the most dominant term

Let’s see, another example to make the rule #3 clear

for instance, suppose, we have a BIG O(+5n+200+n/2)

on this occasion, how do we simplify the above BIG O by using rule #3?

As we know, the rule#3 says we only worry about the most dominant term because the term increases most rapidly than any other term in the BIG O

So, we can write BIG O() by dropping the all non- dominant terms

#### Some space complexity examples

Let’s suppose, we have a function called Demo that prints out “hi there” 5 times then the time complexity of that function will be O(n) and space complexity will be O(1)

But, why the space complexity is O(1) here?

Because The only thing the demo function is doing is creating a variable called let i=0;

finally, that’s it, We are not adding any additional items to the memory

```
function demo(n) {
for (let i = 0; i < n; i++) { //O(1) space and //O(n) Time
console.log('hi there');
}
}
demo(5); // function call with parameter
```

Let’s consider another simple function called Demo that prints “hi” 6 times then the time complexity will be O(n) and space complexity will be O(n)

But, why the space complexity is O(n) here?

Because we created an array and fill it with the item “hi” and we also created a variable but as per our big O rule #2 we neglect the constants so, it will be O(n)

```
function Demo(n) {
var array = []; //O(n) space
for (let i = 0; i < n; i++) { // O(n) Time
array[i] = 'hi';
}
console.log( array);
}
Demo(6) //function call
```

## Leave a Reply