Big-O notation is used to describe the time complexity of an algorithm.
- Big-O measures how the execution time grows with input size. It mainly describes the upper bound (worst-case growth) of the running time.
- Constant factors and smaller terms are ignored to simplify the analysis.
- The time complexity T(N) represents the number of operations executed for input size N.
Let us now explore different examples of the Big-O Notation.
Linear Time Complexity Using Loops
A simple loop that runs N times has O(N) time complexity since each iteration takes constant time O(1). If another independent loop runs M times, the total complexity becomes O(N + M).
#include <iostream>
using namespace std;
int main()
{
int a = 0, b = 0;
int N = 4, M = 4;
// This loop runs for N time
for (int i = 0; i < N; i++) {
a = a + 10;
}
// This loop runs for M time
for (int i = 0; i < M; i++) {
b = b + 40;
}
cout << a << ' ' << b;
return 0;
}
class GFG
{
public static void main(String[] args)
{
int a = 0, b = 0;
int N = 4, M = 4;
// This loop runs for N time
for (int i = 0; i < N; i++)
{
a = a + 10;
}
// This loop runs for M time
for (int i = 0; i < M; i++)
{
b = b + 40;
}
System.out.print(a + " " + b);
}
}
if __name__=="__main__":
a = 0
b = 0
N = 4
M = 4
# This loop runs for N time
for i in range(N):
a = a + 10
# This loop runs for M time
for i in range(M):
b = b + 40
print(a,b)
using System;
class GfG
{
public static void Main(string[] args)
{
int a = 0, b = 0;
int N = 4, M = 4;
// This loop runs for N time
for (int i = 0; i < N; i++)
{
a = a + 10;
}
// This loop runs for M time
for (int i = 0; i < M; i++)
{
b = b + 40;
}
Console.Write(a + " " + b);
}
}
// Driver code
let a = 0, b = 0;
let N = 4, M = 4;
// This loop runs for N times
for (let i = 0; i < N; i++) {
a = a + 10;
}
// This loop runs for M times
for (let i = 0; i < M; i++) {
b = b + 40;
}
console.log(a, b);
Output
40 160
Quadratic Time Complexity Using Nested Loops
In nested loops, if the outer loop runs N times and the inner loop runs M times for each iteration, the inner loop statements execute N × M times in total. Therefore, the overall time complexity is O(N × M).
#include <iostream>
using namespace std;
int main()
{
int a = 0, b = 0;
int N = 4, M = 5;
// Nested loops
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
a = a + j;
// Print the current
// value of a
cout << a << ' ';
}
cout << endl;
}
return 0;
}
class GfG {
public static void main(String[] args)
{
int a = 0;
int b = 0;
int N = 4;
int M = 5;
// Nested loops
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
a = a + j;
// Print the current
// value of a
System.out.print(a + " ");
}
System.out.println();
}
}
}
if __name__=="__main__":
a = 0
b = 0
N = 4
M = 5
# Nested loops
for i in range(N):
for j in range(M):
a = a + j
# Print the current
# value of a
print(a, end = " ")
print()
using System;
public class GFG {
public static void Main()
{
int a = 0;
// int b = 0;
int N = 4;
int M = 5;
// Nested loops
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
a = a + j;
// Print the current
// value of a
Console.Write(a + " ");
}
Console.WriteLine();
}
}
}
// Driver code
let a = 0;
let N = 4, M = 5;
for (let i = 0; i < N; i++) {
let row = "";
for (let j = 0; j < M; j++) {
a = a + j;
row += a + " ";
}
console.log(row.trim());
}
Output
0 1 3 6 10 10 11 13 16 20 20 21 23 26 30 30 31 33 36 40
Logarithmic Time O( log N )Time Complexity
An algorithm has O(log N) time complexity when the problem size is reduced by a constant factor in each step (such as repeatedly dividing by 2). Because the input shrinks quickly, the number of steps grows logarithmically with N.
#include <iostream>
using namespace std;
int main() {
// input size
int N = 100;
for (int i = 1; i <= N; i = i * 2) {
// runs ~log2(N) times: 1,2,4,8,...
cout << i << " ";
}
return 0;
}
public class GfG {
public static void main(String[] args) {
// input size
int N = 100;
for (int i = 1; i <= N; i = i * 2) {
// runs ~log2(N) times: 1,2,4,8,...
System.out.print(i + " ");
}
}
}
# input size
N = 100
i = 1
while i <= N:
# runs ~log2(N) times: 1,2,4,8,...
print(i, end=" ")
i *= 2
using System;
class GfG {
static void Main() {
// input size
int N = 100;
for (int i = 1; i <= N; i = i * 2) {
// runs ~log2(N) times: 1,2,4,8,...
Console.Write(i + " ");
}
}
}
// Driver code
let N = 100;
for (let i = 1; i <= N; i = i * 2) {
// runs ~log2(N) times
console.log(i);
}
Linear Logarithmic Time Complexity
The outer loop runs about N/2 times, which is O(N), while the inner loop doubles each iteration, giving O(log N) complexity. Combining them results in an overall time complexity of O(N log N).
#include <iostream>
using namespace std;
int main()
{
int N = 8, k = 0;
// First loop run N/2 times
for (int i = N / 2; i <= N; i++) {
// Inner loop run log N
// times for all i
for (int j = 2; j <= N;
j = j * 2) {
// Print the value k
cout << k << ' ';
k = k + N / 2;
}
}
return 0;
}
class GfG {
public static void main (String[] args)
{
int N = 8, k = 0;
// First loop run N/2 times
for (int i = N / 2; i <= N; i++) {
// Inner loop run log N
// times for all i
for (int j = 2; j <= N;
j = j * 2) {
// Print the value k
System.out.print(k + " ");
k = k + N / 2;
}
}
}
}
if __name__=="__main__":
N = 8
k = 0
# First loop run N/2 times
for i in range(N//2, N+1):
# Inner loop run log N
# times for all i
j = 2
while j <= N:
j = j * 2
# Print the value k
print(k, end = ' ')
k = k + N // 2
using System;
using System.Linq;
public class GfG{
public static void Main ()
{
int N = 8, k = 0;
// First loop run N/2 times
for (int i = N / 2; i <= N; i++) {
// Inner loop run log N
// times for all i
for (int j = 2; j <= N;
j = j * 2) {
// Print the value k
Console.Write(k + " ");
k = k + N / 2;
}
}
}
}
// Driver code
let N = 8, k = 0;
// First loop runs about N/2 times
for (let i = Math.floor(N / 2); i <= N; i++) {
// Inner loop runs log N times
for (let j = 2; j <= N; j = j * 2) {
// Print the value of k
process.stdout.write(k + " ");
k = k + N / 2;
}
}
Output
0 4 8 12 16 20 24 28 32 36 40 44 48 52 56
Solving Time Complexity Using Recurrence Expansion
Time complexity can also be determined using a recurrence relation, where the running time is expressed in terms of smaller inputs. By expanding the recurrence, identifying a pattern, and generalizing it for N steps, we derive the final time complexity. Please refer Analyzing Recursive Functions for details.