0% found this document useful (0 votes)
1 views21 pages

Time Complexity Explained

The document explains time complexity and asymptotic notations, detailing various types of time complexities such as constant, logarithmic, linear, and exponential, along with their respective examples. It also covers the concept of algorithms, recursion, and provides code examples for calculating factorial, Fibonacci sequence, and Tower of Hanoi using recursion. Additionally, it differentiates between tail and non-tail recursion, providing examples for both types.

Uploaded by

adiekraeides
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
1 views21 pages

Time Complexity Explained

The document explains time complexity and asymptotic notations, detailing various types of time complexities such as constant, logarithmic, linear, and exponential, along with their respective examples. It also covers the concept of algorithms, recursion, and provides code examples for calculating factorial, Fibonacci sequence, and Tower of Hanoi using recursion. Additionally, it differentiates between tail and non-tail recursion, providing examples for both types.

Uploaded by

adiekraeides
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 21

Time Complexity Explained

explain all type of time complexity and asymptotic


notations

Time Complexity and Asymptotic Notations Explained


Time complexity is a way of measuring the efficiency of an algorithm based on the number
of operations it performs as a function of input size (n). It helps in understanding how an
algorithm’s execution time grows with increasing input size.

1. Types of Time Complexity


1.1 Constant Time – O(1)
The execution time remains the same regardless of input size.

Example:
dart

int getFirstElement(List<int> arr) {


return arr[0]; // Always takes constant time
}

Best case for algorithms like hash table lookups.

1.2 Logarithmic Time – O(log n)


The execution time increases logarithmically with input size, meaning it grows slowly.

Often seen in divide-and-conquer algorithms.

Example: Binary Search

dart

int binarySearch(List<int> arr, int target) {


int left = 0, right = arr.length - 1;

1/21
while (left <= right) {
int mid = left + (right - left) ~/ 2;
if (arr[mid] == target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}

Used in efficient searching and tree-based algorithms.

1.3 Linear Time – O(n)


The execution time increases proportionally with the input size.

Example: Linear Search

dart

int linearSearch(List<int> arr, int target) {


for (int i = 0; i < arr.length; i++) {
if (arr[i] == target) return i;
}
return -1;
}

Used in simple loops, searching in an unsorted array.

1.4 Linearithmic Time – O(n log n)


The execution time is a combination of linear and logarithmic factors.

Example: Merge Sort

dart

List<int> mergeSort(List<int> arr) {


if (arr.length <= 1) return arr;

2/21
int mid = arr.length ~/ 2;
List<int> left = mergeSort(arr.sublist(0, mid));
List<int> right = mergeSort(arr.sublist(mid));
return merge(left, right);
}

Seen in efficient sorting algorithms like Merge Sort, Quick Sort (average case).

1.5 Quadratic Time – O(n²)


The execution time grows quadratically as input size increases.

Example: Bubble Sort

dart

void bubbleSort(List<int> arr) {


for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}

Seen in naive sorting algorithms like Bubble Sort, Insertion Sort.

1.6 Cubic Time – O(n³)


The execution time increases with the cube of the input size.

Example:
dart

3/21
void printTriples(List<int> arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
for (int k = 0; k < arr.length; k++) {
print('${arr[i]}, ${arr[j]}, ${arr[k]}');
}
}
}
}

Occurs in some brute-force algorithms for matrix operations.

1.7 Exponential Time – O(2ⁿ)


The execution time doubles with each additional input element.

Example: Recursive Fibonacci

dart

int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}

Seen in recursive problems like the Fibonacci sequence, subsets, and backtracking.

1.8 Factorial Time – O(n!)


The execution time grows extremely fast.

Example: Generating all permutations of a string

dart

void permute(String str, String chosen) {


if (str.isEmpty) {

4/21
print(chosen);
return;
}
for (int i = 0; i < str.length; i++) {
String newStr = str.substring(0, i) + str.substring(i + 1);
permute(newStr, chosen + str[i]);
}
}

Seen in brute-force problems like traveling salesman problem (TSP).

2. Asymptotic Notations
Asymptotic notations describe the upper, lower, and tight bounds of an algorithm’s time
complexity.

2.1 Big-O Notation (O) – Worst Case


Represents the upper bound of an algorithm’s time complexity.

Example: O(n²) for Bubble Sort means the worst-case runtime is proportional to n².

2.2 Omega Notation (Ω) – Best Case


Represents the lower bound of an algorithm’s time complexity.

Example: Ω(n) for Bubble Sort means the best case (already sorted) takes linear time.

2.3 Theta Notation (Θ) – Average Case


Represents the tight bound, meaning both upper and lower bounds are the same.

Example: Θ(n log n) for Merge Sort means its time complexity remains the same in all
cases.

3. Summary Table

5/21
Complexity Name Example Algorithm

O(1) Constant Accessing an array index

O(log n) Logarithmic Binary Search

O(n) Linear Linear Search

O(n log n) Linearithmic Merge Sort, Quick Sort (average case)

O(n²) Quadratic Bubble Sort, Selection Sort

O(n³) Cubic Triple nested loops

O(2ⁿ) Exponential Recursive Fibonacci

O(n!) Factorial Permutations, Traveling Salesman Problem

4. Choosing the Right Algorithm


If n is small, O(n²) or even O(n³) might be acceptable.

If n is large, prefer O(n log n) or O(log n) solutions.

Constant time (O(1)) and logarithmic time (O(log n)) are the best.

Would you like a real-world example of time complexity comparisons? 🚀


what is algorithms and make algorithm to find find factorial
of a number

What is an Algorithm?
An algorithm is a step-by-step procedure or a set of well-defined instructions designed to
solve a specific problem or perform a task. It takes an input, processes it, and produces an
output in a finite amount of time.

Algorithm to Find Factorial of a Number

6/21
Factorial of a number n (denoted as n!) is the product of all positive integers from 1 to n:

n! = n × (n − 1) × (n − 2) × ... × 1

Example:

5! = 5 × 4 × 3 × 2 × 1 = 120

Algorithm (Step-by-Step)
1. Start

2. Input: Read an integer n

3. If n is 0 or 1, return 1 (since 0! = 1! = 1 )

4. Otherwise, initialize fact = 1

5. Loop from i = 2 to n

Multiply fact = fact * i

6. Output the value of fact

7. End

Factorial Algorithm in Dart (Iterative Approach)

dart

int factorial(int n) {
int fact = 1;
for (int i = 2; i <= n; i++) {
fact *= i;
}
return fact;
}

void main() {
int num = 5;

7/21
print("Factorial of $num is ${factorial(num)}");
}

Time Complexity: O(n) (since we loop from 2 to n)

Factorial Using Recursion

dart

int factorialRecursive(int n) {
if (n == 0 || n == 1) return 1;
return n * factorialRecursive(n - 1);
}

void main() {
int num = 5;
print("Factorial of $num is ${factorialRecursive(num)}");
}

Time Complexity: O(n)


Space Complexity: O(n) (due to recursive calls)

Let me know if you want an optimized version using memoization or BigInteger for large
numbers! 🚀
what is recursion and make a code of finding factorial ,
fibonacci sequence and tower hanoi using recursion

What is Recursion?
Recursion is a programming technique where a function calls itself to solve a smaller
subproblem of the original problem. It continues calling itself until it reaches a base case,
which stops the recursion.

8/21
1. Factorial Using Recursion
Factorial of a number n! is calculated as:

n! = n × (n − 1)!

with the base case:

0! = 1

Dart Code for Factorial


dart

int factorial(int n) {
if (n == 0 || n == 1) return 1; // Base case
return n * factorial(n - 1); // Recursive case
}

void main() {
int num = 5;
print("Factorial of $num is ${factorial(num)}");
}

Time Complexity: O(n)


Space Complexity: O(n) (due to recursive function calls)

2. Fibonacci Sequence Using Recursion


The Fibonacci sequence is defined as:

F (n) = F (n − 1) + F (n − 2)

with base cases:

F (0) = 0, F (1) = 1

Dart Code for Fibonacci

dart

9/21
int fibonacci(int n) {
if (n == 0) return 0; // Base case
if (n == 1) return 1; // Base case
return fibonacci(n - 1) + fibonacci(n - 2); // Recursive case
}

void main() {
int num = 6;
print("Fibonacci($num) is ${fibonacci(num)}");
}

Time Complexity: O(2ⁿ) (Exponential)


Space Complexity: O(n) (Recursion stack)

⚡ Optimization: Use memoization (Dynamic Programming) to improve efficiency.

3. Tower of Hanoi Using Recursion


The Tower of Hanoi problem involves moving n disks from a source peg to a destination
peg, using an auxiliary peg, following these rules:

1. Move only one disk at a time.

2. A larger disk cannot be placed on a smaller disk.

3. Use an auxiliary peg to help with transfers.

Dart Code for Tower of Hanoi

dart

void towerOfHanoi(int n, String source, String auxiliary, String destination) {


if (n == 1) {
print("Move disk 1 from $source to $destination");
return;
}
// Move (n-1) disks from source to auxiliary using destination
towerOfHanoi(n - 1, source, destination, auxiliary);

// Move the nth disk from source to destination

10/21
print("Move disk $n from $source to $destination");

// Move (n-1) disks from auxiliary to destination using source


towerOfHanoi(n - 1, auxiliary, source, destination);
}

void main() {
int numDisks = 3;
towerOfHanoi(numDisks, "A", "B", "C");
}

Time Complexity: O(2ⁿ)


Space Complexity: O(n) (due to recursive function calls)

Summary
Time
Problem Base Case Recursive Formula Complexity

Factorial n = 0 or n = 1 n! = n × (n − 1)! O(n)

Fibonacci F (0) = F (n) = F (n − 1) + F (n − 2) O(2ⁿ)


0, F (1) = 1 (exponential)

Tower of n=1 Move n − 1 disks, then move the last disk, O(2ⁿ)
Hanoi then move n − 1 disks

Let me know if you need optimized versions! 🚀


what is tail and non-tail recursion give example for both

Tail Recursion vs. Non-Tail Recursion


Recursion can be categorized into tail recursion and non-tail recursion based on where the
recursive call occurs in the function.

1. Tail Recursion
A tail recursive function is when the recursive call is the last operation performed
before returning the result.

No extra computation is needed after the recursive call.

11/21
Tail recursion can be optimized by the compiler into an iterative approach to save stack
space (Tail Call Optimization, TCO).

Example: Tail Recursive Factorial in Dart

dart

int factorialTail(int n, int result) {


if (n == 0 || n == 1) return result; // Base case
return factorialTail(n - 1, n * result); // Tail recursive call
}

void main() {
int num = 5;
print("Factorial of $num is ${factorialTail(num, 1)}");
}

✅ Efficient because it does not maintain a growing recursive stack.


⏳ Time Complexity: O(n)
📦 Space Complexity: O(1) (if tail call optimization is supported)

2. Non-Tail Recursion
A non-tail recursive function is when the recursive call is not the last operation before
returning.

Additional operations (like multiplication, addition, etc.) are performed after the
recursive call.

Uses more stack memory because it needs to keep track of operations.

Example: Non-Tail Recursive Factorial in Dart

dart

int factorialNonTail(int n) {
if (n == 0 || n == 1) return 1; // Base case
return n * factorialNonTail(n - 1); // Non-tail recursive call (multiplication
happens after recursion)
}

12/21
void main() {
int num = 5;
print("Factorial of $num is ${factorialNonTail(num)}");
}

⚠️ Less efficient because each recursive call remains in memory until all multiplications
are resolved.
⏳ Time Complexity: O(n)
📦 Space Complexity: O(n) (due to recursive stack usage)

Key Differences
Feature Tail Recursion Non-Tail Recursion

Recursive Call Position Last operation before returning Not the last operation

Stack Usage Constant (O(1) if optimized) Increases with depth (O(n))

Performance Faster, can be optimized into loops Slower, requires more memory

Example factorialTail(n-1, n * result) n * factorialNonTail(n-1)

Example: Tail vs. Non-Tail Recursive Fibonacci

Non-Tail Recursive Fibonacci (Inefficient)

dart

int fibonacciNonTail(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
return fibonacciNonTail(n - 1) + fibonacciNonTail(n - 2); // Recursive calls
happen before return
}

void main() {
int num = 6;

13/21
print("Fibonacci($num) is ${fibonacciNonTail(num)}");
}

⚠️ Very inefficient (O(2ⁿ)) due to duplicate calculations.

Tail Recursive Fibonacci (Efficient)

dart

int fibonacciTail(int n, int a, int b) {


if (n == 0) return a;
if (n == 1) return b;
return fibonacciTail(n - 1, b, a + b); // Tail recursive call
}

void main() {
int num = 6;
print("Fibonacci($num) is ${fibonacciTail(num, 0, 1)}");
}

✅ Efficient because it avoids duplicate computations (O(n) time complexity).


✅ Reduces stack memory usage.

Conclusion
Use tail recursion when possible to optimize memory usage.

Non-tail recursion is easier to understand but can lead to higher memory usage.

Some languages optimize tail recursion into loops, but Dart does not support Tail Call
Optimization (TCO) yet.

🚀 Let me know if you need more examples or optimizations!


implementation of stack queue and circular queue usign
array in C.

14/21
Here’s the C implementation of Stack, Queue, and Circular Queue using arrays:

1. Stack Implementation Using Array


A stack follows the LIFO (Last In, First Out) principle.

Operations:
Push: Insert element at the top.

Pop: Remove element from the top.

Peek: Get the top element without removing it.

C Code for Stack Using Array


c

#include <stdio.h>
#define MAX 5

int stack[MAX], top = -1;

// Push operation
void push(int value) {
if (top == MAX - 1) {
printf("Stack Overflow!\n");
return;
}
stack[++top] = value;
printf("%d pushed into stack\n", value);
}

// Pop operation
void pop() {
if (top == -1) {
printf("Stack Underflow!\n");
return;
}
printf("%d popped from stack\n", stack[top--]);
}

15/21
// Peek operation
void peek() {
if (top == -1) {
printf("Stack is empty!\n");
return;
}
printf("Top element: %d\n", stack[top]);
}

// Display stack
void display() {
if (top == -1) {
printf("Stack is empty!\n");
return;
}
printf("Stack elements: ");
for (int i = top; i >= 0; i--) {
printf("%d ", stack[i]);
}
printf("\n");
}

// Main function
int main() {
push(10);
push(20);
push(30);
display();
pop();
display();
peek();
return 0;
}

✅ Time Complexity:
Push, Pop, Peek: O(1)

16/21
2. Queue Implementation Using Array
A queue follows the FIFO (First In, First Out) principle.

Operations:
Enqueue: Insert element at the rear.

Dequeue: Remove element from the front.

Front: Get the first element without removing it.

C Code for Queue Using Array

#include <stdio.h>
#define MAX 5

int queue[MAX], front = -1, rear = -1;

// Enqueue operation
void enqueue(int value) {
if (rear == MAX - 1) {
printf("Queue Overflow!\n");
return;
}
if (front == -1) front = 0;
queue[++rear] = value;
printf("%d enqueued into queue\n", value);
}

// Dequeue operation
void dequeue() {
if (front == -1 || front > rear) {
printf("Queue Underflow!\n");
return;
}
printf("%d dequeued from queue\n", queue[front++]);
}

// Display queue
void display() {
if (front == -1 || front > rear) {

17/21
printf("Queue is empty!\n");
return;
}
printf("Queue elements: ");
for (int i = front; i <= rear; i++) {
printf("%d ", queue[i]);
}
printf("\n");
}

// Main function
int main() {
enqueue(10);
enqueue(20);
enqueue(30);
display();
dequeue();
display();
return 0;
}

✅ Time Complexity:
Enqueue, Dequeue: O(1)

Issue: Dequeue operation shifts front but does not reuse space.

3. Circular Queue Implementation Using Array


A circular queue fixes the linear queue issue by making the queue circular.

Operations:
Enqueue: Insert element at the rear.

Dequeue: Remove element from the front.

Front: Get the first element without removing it.

C Code for Circular Queue Using Array


c

18/21
#include <stdio.h>
#define MAX 5

int cqueue[MAX], front = -1, rear = -1;

// Check if queue is full


int isFull() {
return ((rear + 1) % MAX == front);
}

// Check if queue is empty


int isEmpty() {
return (front == -1);
}

// Enqueue operation
void enqueue(int value) {
if (isFull()) {
printf("Queue Overflow!\n");
return;
}
if (front == -1) front = 0;
rear = (rear + 1) % MAX;
cqueue[rear] = value;
printf("%d enqueued into circular queue\n", value);
}

// Dequeue operation
void dequeue() {
if (isEmpty()) {
printf("Queue Underflow!\n");
return;
}
printf("%d dequeued from circular queue\n", cqueue[front]);
if (front == rear) { // Reset when last element is removed
front = rear = -1;
} else {
front = (front + 1) % MAX;
}
}

19/21
// Display queue
void display() {
if (isEmpty()) {
printf("Queue is empty!\n");
return;
}
printf("Circular Queue elements: ");
int i = front;
while (1) {
printf("%d ", cqueue[i]);
if (i == rear) break;
i = (i + 1) % MAX;
}
printf("\n");
}

// Main function
int main() {
enqueue(10);
enqueue(20);
enqueue(30);
enqueue(40);
enqueue(50);
display();
dequeue();
enqueue(60);
display();
return 0;
}

✅ Time Complexity:
Enqueue, Dequeue: O(1)

Uses circular logic to efficiently utilize space.

Comparison Table

20/21
Data
Structure Principle Pros Cons

Stack LIFO Easy to implement, fast Limited capacity (fixed size)


operations

Queue FIFO Simple, sequential processing Wasted space after


dequeuing

Circular FIFO Efficient space usage Slightly complex logic


Queue (Circular)

Conclusion
Use Stack when you need LIFO operations (e.g., function calls, undo feature).

Use Queue when you need FIFO operations (e.g., scheduling, printing jobs).

Use Circular Queue to handle efficient space utilization in a queue.

🚀 Let me know if you need explanations or improvements!

21/21

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy