Do you think IIT Guwahati certified course can help you in your career?
No
Introduction
You may have at various points heard about Space Complexity in discussions related to the analysis of multiple algorithms. This is an essential topic for interview preparations and for building a better understanding of various algorithms.
This article will help you understand the importance of considering Space Complexity when designing or learning a new algorithm.
Space Complexity is the measure of memory consumed by a program to operate on the input of a given size. Hence, Space complexity is essentially the sum of Auxillary Memory used and the memory used by input. However, this definition isn’t popularly used for comparing algorithms; otherwise, the space complexity of bubble sort and merge sort would be the same as O(n).
This is why it’s best to say that certain algorithms like bubble sort requires O(1) extra memory, and merge sort require O(n) extra memory when the discussion is about the complexity analysis of algorithms.
Algorithm to Evaluate Space Complexity in Data Structures
Evaluating space complexity in data structures involves analyzing the amount of memory used by an algorithm as a function of the input size. The general steps in the algorithm are:
Identify Variables: Identify all the variables used in the algorithm. This includes primitive data types, objects, and any additional space for data structures like arrays or linked lists.
Categorize Space: Classify the memory usage into two categories:
Fixed Part: The memory needed for constants, simple variables, and fixed-size data structures that do not change with input size.
Variable Part: The memory required for dynamic structures like arrays, linked lists, and recursion, which may grow or shrink with input size.
Calculate Total Space: Sum the fixed and variable parts to determine the total space complexity. This is usually expressed using Big O notation (e.g., O(1), O(n), O(n²)).
Consider Auxiliary Space: If the algorithm uses extra space besides the input, this should be included in the space complexity calculation. Auxiliary space refers to the temporary space used for variables or data structures during execution.
By following these steps, you can effectively evaluate the space complexity of different data structures and algorithms.
Need to Evaluate Space Complexity in Data Structures
Evaluating space complexity is essential for several reasons:
Resource Management: Understanding space complexity helps programmers manage memory efficiently. It allows them to determine whether a given algorithm will run effectively within the available memory limits.
Performance Optimization: By evaluating space complexity, developers can identify algorithms that consume excessive memory. This insight enables them to optimize code, improving overall performance and speed.
Scalability: In applications that handle large amounts of data, knowing the space complexity helps in choosing the right data structures and algorithms. It ensures that the application can scale effectively without running into memory issues.
Comparative Analysis: Evaluating space complexity allows for comparing different algorithms. This analysis can guide developers in selecting the most efficient algorithm based on memory usage, which can be crucial for performance-sensitive applications.
Understanding space complexity is crucial for building efficient software systems that can handle varying loads and ensure optimal performance.
Methods to Calculate Space Complexity
There are several methods to calculate space complexity in data structures:
Count Basic Variables: Start by counting all the basic variables used in the algorithm. This includes integers, floats, characters, and any fixed-size data structures.
Analyze Data Structures: Examine the data structures used in the algorithm, such as arrays, lists, and trees. For each data structure, evaluate how the size changes with different input sizes.
Consider Function Call Stack: If the algorithm uses recursion, consider the space used in the call stack. Each function call takes up space, so the maximum depth of recursion contributes to the overall space complexity.
Evaluate Temporary Space: Identify any temporary or auxiliary data structures used during the execution of the algorithm. This includes additional arrays or lists created for processing data.
Summarize Results: Finally, sum all calculated spaces, including fixed and variable parts, to obtain the total space complexity. Use Big O notation to express your findings.
By employing these methods, you can systematically calculate space complexity, ensuring your algorithms are efficient and scalable.
Discussion and examples
Implementation
#include <bits/stdc++.h>
using namespace std;
int32_t main(){
int a = 1, b = 2;
int prod = a * b;
cout << prod << endl;
return 0;
}
You can also try this code with Online C++ Compiler
We create 3 variables in the above program and print them. The exact memory consumed by this program will be (assuming int = 4 bytes, it can be 2 bytes in some old 16-bit computers) will be equal to 3 * 4 = 12 bytes.
Implementation
#include <bits/stdc++.h>
using namespace std;
int32_t main(){
int n = 10;
int a[n];
for(int i = 0; i < n; i++){
// the loop will run 5 times
a[i] = i;
}
return 0;
}
You can also try this code with Online C++ Compiler
We create an array of integers “a” of size n. In addition to this, we create two variables, “i” and “n”, contributing 4 bytes each, and there’s also “a” that’s a pointer to the initial element of the array, consuming 4 bytes. In total, we get 4 * n + 12 bytes. The 12 byte part, however, is just a constant, and thus, the memory being consumed by the above program can be said to be O(n) in terms of big-O notation.
Implementation
#include <bits/stdc++.h>
using namespace std;
int stack_memory(int n){
if(n == 1) return 1;
return n + stack_memory(n / 2);
}
int32_t main(){
int n = 10;
cout << stack_memory(n) << endl;
return 0;
}
You can also try this code with Online C++ Compiler
This is a slightly different case. In this case, because of recursion, the previous states have to be stored onto the stack memory at every call of the function stack_memory(). The function has to be called floor(log2(n))+1 times in total. Hence, we are storing the floor(log2(n))+1 states onto the stack, rest of the other memory being consumed due to other temporary variables is a constant and therefore, the space complexity is O(log2(n)). As shown below, the same code can be implemented with a while loop with O(1) space complexity.
#include <bits/stdc++.h>
using namespace std;
int32_t main(){
int n = 10;
int ans = 1;
while(n != 1){
ans += n;
n /= 2;
}
cout << ans << endl;
}
You can also try this code with Online C++ Compiler
Recursion also does the same task with the same result. However, the iterative approach doesn’t consume extra space of O(log2(n)). There is also a catch here in the case of tail recursion; when recursive functions don’t store previous calls on to the stack memory, refer here for more about tail recursion.
Refers to the amount of memory space required by an algorithm as a function of the input size.
Refers to the amount of time taken by an algorithm to run as a function of the input size.
Measurement Unit
Measured in bytes or as a function (e.g., O(1), O(n), O(n²)).
Measured in terms of operations performed or as a function (e.g., O(1), O(n), O(n log n)).
Focus
Concentrates on memory usage during the execution of the algorithm.
Concentrates on the execution time of the algorithm.
Importance
Essential for resource management and ensuring efficient memory use.
Critical for performance optimization and ensuring quick execution.
Impact of Growth
Increases as the size of input data increases (e.g., more elements in an array).
Increases with the number of operations performed, often related to input size.
Examples
Storing data in arrays, lists, and trees; recursion depth contributes to space complexity.
Iterative loops, recursive calls; more iterations or deeper recursion lead to higher time complexity.
Optimization Focus
Focused on minimizing memory usage, particularly in memory-constrained environments.
Focused on reducing execution time to improve overall efficiency.
Understanding both space and time complexity is essential for evaluating the efficiency of algorithms, ensuring that they perform optimally in terms of both memory and execution time.
Frequently Asked Questions
List an example where increasing the Space complexity led to improvements in time complexity.
A famous example for this case is merge sort that consumes O(n) memory and O(n * log(n)) time, while bubble sort takes O(1) memory, but you have to instead pay in the form O(n2) time. This tradeoff allows a significant boost from an O(n2) time algorithm to an O(n * log(n)) time algorithm. There are an almost infinite number of such cases, for example, all dynamic programming algorithms.
What is Auxillary Memory?
Auxiliary memory refers to the cheap, slow and large memory units such as HDDs in our computer. Most of the information related to a program is stored for long term storage when not being used.
If a recursive approach is always slower and consumes memory, should one ever use it?
The recursive method is often slower and consumes stack memory on each consecutive function call compared to an iterative approach. It shines when you need to code faster and write smaller functions that are less prone to bugs, such as the case with a tower of Hanoi problem that would otherwise be difficult to solve without recursion. This may be your need in a contest with limited time. For example, sometimes, it’s straightforward to code a dynamic programming problem using recursion with fewer missed edge cases.
What is stack memory?
It’s a memory usage method that allows accessing and storing memory chunks temporarily in a last-in-first-out manner, i.e., one can access the last inserted chunk of data first.
What is Tail Recursion?
Tail recursion occurs when the last line to be executed in a function is the same function call itself. In such a case, there is no need to store the previous states of the functions, and as such, no extra stack memory is consumed (all this is taken care of by the compiler). Euclidean’s GCD algorithm’s recursive function code is an excellent example of tail recursion.
Conclusion
This article covers the basics of space complexity and discusses its importance and necessary tradeoffs between space and time complexity to achieve an algorithm for our needs. To understand the blog better, refer to the content here about time complexity analysis, and refer here for a quick revision on asymptotic notations.
Learn more about the C++ STL library from here if you have trouble understanding some part of the code. Visit the link here for carefully crafted courses on campus placement and interview preparation on coding ninjas.