Unit - 4 DS
Unit - 4 DS
Linear search is a simple searching algorithm used to find an element in a list or array. It works by
sequentially checking each element until the desired element is found or the end of the list is reached.
#### **Algorithm**
1. **Input**: Array `arr[]` of size `n` and target `x`.
2. **Output**: Index of `x` if found, else `-1`.
3. **Steps**:
```
for i = 0 to n-1 do
if arr[i] == x then
return i
return -1
```
#### **Advantages**
- Simple and easy to implement.
- Does not require the list to be sorted.
#### **Disadvantages**
- Inefficient for large datasets.
- Time complexity: \( O(n) \) (worst and average cases).
---
Binary search is a more efficient searching algorithm that works on sorted arrays. It repeatedly divides
the search range in half, eliminating half of the elements in each step.
#### **Algorithm**
1. **Input**: Sorted array `arr[]`, size `n`, and target `x`.
2. **Output**: Index of `x` if found, else `-1`.
3. **Steps**:
```
low = 0
high = n - 1
#### **Advantages**
- Highly efficient for large datasets.
- Time complexity: \( O(\log n) \).
#### **Disadvantages**
- Requires the array to be sorted beforehand.
- Slightly more complex to implement.
---
### **Comparison**
---
### **Example**
Sorting
Selection sort
Sorting is the process of arranging elements of a list or array in a specific order. The order can be
**ascending** (smallest to largest) or **descending** (largest to smallest). Sorting is a fundamental
operation in computer science, often used to organize data for easier searching, comparison, and
analysis.
---
2. **Non-Comparison-Based Sorting**:
- Algorithms like Counting Sort, Radix Sort, and Bucket Sort.
- These rely on mathematical properties rather than direct comparisons.
---
**Selection Sort** is a simple comparison-based sorting algorithm. It works by repeatedly finding the
smallest (or largest) element from the unsorted part of the list and moving it to the sorted part.
---
### **Algorithm**
---
### **Example**
2. **Second Pass**:
- Smallest element in `[25, 12, 22, 64]`: `12` → Swap with `25`.
- Result: `[11, 12, 25, 22, 64]`
3. **Third Pass**:
- Smallest element in `[25, 22, 64]`: `22` → Swap with `25`.
- Result: `[11, 12, 22, 25, 64]`
4. **Fourth Pass**:
- Smallest element in `[25, 64]`: `25` → No swap needed.
- Result: `[11, 12, 22, 25, 64]`
---
### **Advantages**
- Simple and easy to understand.
- Requires minimal additional memory (in-place sorting).
### **Disadvantages**
- Inefficient for large datasets due to \( O(n^2) \) complexity.
- Comparatively slower than other algorithms like Quick Sort or Merge Sort.
Bubble Sort
Bubble Sort is a simple sorting algorithm that repeatedly steps through the list, compares
adjacent elements, and swaps them if they are in the wrong order. The process continues until the
list is sorted.
The algorithm gets its name because the smaller elements "bubble" to the top (beginning of the
array) and the larger elements "sink" to the bottom (end of the array).
How It Works
css
Copy code
for i = 0 to n-1 do
swapped = false
for j = 0 to n-i-2 do
if arr[j] > arr[j+1] then
Swap arr[j] and arr[j+1]
swapped = true
if swapped == false then
break
Example
Input Array:
Sorting Steps:
1. Pass 1:
o Compare 64 and 34 → Swap → [34, 64, 25, 12, 22, 11, 90]
o Compare 64 and 25 → Swap → [34, 25, 64, 12, 22, 11, 90]
o Compare 64 and 12 → Swap → [34, 25, 12, 64, 22, 11, 90]
o Compare 64 and 22 → Swap → [34, 25, 12, 22, 64, 11, 90]
o Compare 64 and 11 → Swap → [34, 25, 12, 22, 11, 64, 90]
o Compare 64 and 90 → No Swap → [34, 25, 12, 22, 11, 64, 90]
2. Pass 2:
o Compare 34 and 25 → Swap → [25, 34, 12, 22, 11, 64, 90]
o Compare 34 and 12 → Swap → [25, 12, 34, 22, 11, 64, 90]
o Compare 34 and 22 → Swap → [25, 12, 22, 34, 11, 64, 90]
o Compare 34 and 11 → Swap → [25, 12, 22, 11, 34, 64, 90]
o Compare 34 and 64 → No Swap → [25, 12, 22, 11, 34, 64, 90]
3. Pass 3:
o Compare 25 and 12 → Swap → [12, 25, 22, 11, 34, 64, 90]
o Compare 25 and 22 → Swap → [12, 22, 25, 11, 34, 64, 90]
o Compare 25 and 11 → Swap → [12, 22, 11, 25, 34, 64, 90]
o Compare 25 and 34 → No Swap → [12, 22, 11, 25, 34, 64, 90]
4. Pass 4:
o Compare 12 and 22 → No Swap → [12, 22, 11, 25, 34, 64, 90]
o Compare 22 and 11 → Swap → [12, 11, 22, 25, 34, 64, 90]
o Compare 22 and 25 → No Swap → [12, 11, 22, 25, 34, 64, 90]
5. Pass 5:
o Compare 12 and 11 → Swap → [11, 12, 22, 25, 34, 64, 90]
o Compare 12 and 22 → No Swap → [11, 12, 22, 25, 34, 64, 90]
6. Pass 6:
o Compare 11 and 12 → No Swap → [11, 12, 22, 25, 34, 64, 90]
Insertion Sort
Insertion Sort is a simple and intuitive sorting algorithm that builds the final sorted array one
element at a time. It works by repeatedly picking an element from the unsorted part and placing
it in its correct position in the sorted part of the array.
How It Works
css
Copy code
for i = 1 to n-1 do
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key do
arr[j+1] = arr[j]
j = j - 1
arr[j+1] = key
Example
Input Array:
Sorting Steps:
Time Complexity
Time
Case Explanation
Complexity
When the array is already sorted, only one comparison per
Best Case O(n)O(n)
element is made.
When the array is sorted in reverse order, every element must be
Worst Case O(n2)O(n2)
compared with all others.
Average
O(n2)O(n2) Comparisons and shifts dominate.
Case
Space Complexity
Advantages
Simple to implement.
Efficient for small datasets or partially sorted data.
Stable sort (preserves the relative order of equal elements).
Disadvantages
Quick Sort
Quick Sort is a highly efficient and widely used sorting algorithm. It follows the divide-and-
conquer approach to sort elements by partitioning the array into smaller subarrays and
recursively sorting them.
How It Works
1. Choose a Pivot: Select an element as the pivot. This element will help partition the array.
2. Partition: Rearrange the elements such that:
o Elements smaller than the pivot are placed to the left.
o Elements larger than the pivot are placed to the right.
3. Recursive Sort:
o Recursively apply the above steps to the subarrays (left and right of the pivot).
o Continue until the subarrays are of size 0 or 1 (already sorted).
Algorithm
less
Copy code
function quickSort(arr, low, high):
if low < high then
pivotIndex = partition(arr, low, high)
quickSort(arr, low, pivotIndex - 1)
quickSort(arr, pivotIndex + 1, high)
Example
Input Array:
Sorting Steps:
1. Initial Array:
[10, 80, 30, 90, 40, 50, 70]
2. First Partition (Pivot = 70):
oRearrange so elements smaller than 70 are on the left, and larger ones are on the
right.
o Result: [10, 30, 40, 50, 70, 90, 80]
o Pivot position = 4.
3. Left Subarray: [10, 30, 40, 50]
o Pivot = 50.
o Result: [10, 30, 40, 50] (already sorted).
4. Right Subarray: [90, 80]
o Pivot = 80.
o Result: [80, 90] (sorted).
5. Final Sorted Array:
[10, 30, 40, 50, 70, 80, 90]
Time Complexity
Space Complexity
Advantages
Disadvantages
1. First Element: Simple but can lead to poor performance if the array is already sorted.
2. Last Element: Same issue as the first element.
3. Random Pivot: Reduces the likelihood of worst-case scenarios.
4. Median of Three: Selects the median of the first, middle, and last elements to improve
partitioning balance.
Merge Sort
Merge Sort is a highly efficient, stable sorting algorithm based on the divide-and-
conquer paradigm. It divides the array into smaller subarrays, sorts them, and then merges them
to produce the final sorted array.
How It Works
1. Divide: Split the array into two halves until each subarray contains a single element (or
no elements).
2. Conquer: Recursively sort the two halves.
3. Merge: Combine the two sorted halves into a single sorted array.
Algorithm
less
Copy code
function mergeSort(arr, left, right):
if left < right then
mid = (left + right) / 2
mergeSort(arr, left, mid)
mergeSort(arr, mid + 1, right)
merge(arr, left, mid, right)
Example
Input Array:
Sorting Steps:
1. Initial Array:
[38, 27, 43, 3, 9, 82, 10]
2. Divide Step:
o Split into [38, 27, 43, 3] and [9, 82, 10].
3. Recursive Divide:
o [38, 27, 43, 3] → [38, 27] and [43, 3]
o [9, 82, 10] → [9, 82] and [10]
4. Sort Subarrays:
o [38, 27] → [27, 38]
o [43, 3] → [3, 43]
o [9, 82] → [9, 82]
5. Merge Step:
o Merge [27, 38] and [3, 43] → [3, 27, 38, 43]
o Merge [9, 82] and [10] → [9, 10, 82]
6. Final Merge:
o Merge [3, 27, 38, 43] and [9, 10, 82] → [3, 9, 10, 27, 38, 43, 82]
Time Complexity
Space Complexity
Advantages
Disadvantages
What is a Heap?
A Heap is a specialized tree-based data structure that satisfies the Heap Property:
1. Max-Heap Property: For every node, the value of the parent node is greater than or
equal to the values of its children.
Example: Parent≥Child1,Child2Parent≥Child1,Child2
2. Min-Heap Property: For every node, the value of the parent node is less than or equal to
the values of its children.
Example: Parent≤Child1,Child2Parent≤Child1,Child2
Types of Heaps
1. Max-Heap: Used for priority queues where the largest element is prioritized.
2. Min-Heap: Used for priority queues where the smallest element is prioritized.
Characteristics of a Heap
A heap is always a complete binary tree, meaning all levels are completely filled except
possibly the last level, which is filled from left to right.
It is commonly represented using arrays for efficient access.
Heap Sort is a sorting algorithm that uses a binary heap to sort an array. It works by first
building a max-heap (or min-heap) and then repeatedly extracting the largest (or smallest)
element from the heap.
Algorithm
sql
Copy code
function heapSort(arr):
n = len(arr)
Example
Input Array:
[4, 10, 3, 5, 1]
Steps:
1. Build Max-Heap:
o Rearrange into a max-heap: [10, 5, 3, 4, 1].
2. Extract Elements:
o Swap 10 with 1: [1, 5, 3, 4, 10].
o Restore heap property for remaining elements: [5, 4, 3, 1, 10].
3. Repeat until sorted:
o [4, 1, 3, 5, 10]
o [3, 1, 4, 5, 10]
o [1, 3, 4, 5, 10]
[1, 3, 4, 5, 10]
Time Complexity
Space Complexity
Advantages
Disadvantages
1. Make a Greedy Choice: At each step, select the option that looks the best at the
moment.
2. Feasibility: Ensure the choice is valid for the problem constraints.
3. Optimal Substructure: Solve subproblems using the same greedy strategy.
1. Define the Problem: Understand the problem constraints and the optimization goal.
2. Identify the Greedy Choice: Determine how to make a choice at each step that seems
optimal.
3. Prove Correctness: Show that the greedy choice leads to an overall optimal solution
(using proof or mathematical induction).
4. Implement the Algorithm: Translate the greedy strategy into code.
Problem: Given nn activities with start and end times, select the maximum number of activities
that don't overlap.
Greedy Strategy: Always choose the activity with the earliest finish time.
2. Huffman Encoding
Problem: Generate an optimal prefix code for a set of characters with given frequencies.
Greedy Strategy: Combine the two least frequent nodes iteratively.
3. Kruskal's Algorithm
4. Dijkstra's Algorithm
Problem: Find the shortest path from a source vertex to all other vertices in a graph.
Greedy Strategy: Pick the vertex with the smallest tentative distance.
Advantages
Disadvantages
Problem: Given denominations of coins and a total amount, find the minimum number of coins
needed.
Greedy Algorithm:
Example:
Coins: {1,2,5,10}{1,2,5,10}
Total Amount: 2727
Steps:
Pick 1010, total becomes 1717.
Pick 1010, total becomes 77.
Pick 55, total becomes 22.
Pick 22, total becomes 00.
Result: {10,10,5,2}{10,10,5,2} (4 coins).
Conclusion
The greedy algorithm is a simple yet powerful approach for solving optimization problems.
However, it is essential to analyze whether the problem satisfies the Greedy Choice
Property and Optimal Substructure to ensure that a greedy solution is correct.
Divide-and-Conquer Algorithm
1. Divide: Break the problem into smaller subproblems, ideally of equal size.
2. Conquer: Solve each subproblem recursively. If a subproblem is small enough, solve it
directly.
3. Combine: Merge the solutions of the subproblems to form the solution of the original
problem.
Characteristics
T(n)=kT(nk)+f(n)T(n)=kT(kn)+f(n)
Where:
1. Merge Sort
2. Quick Sort
Divide: Choose a pivot and partition the array into two subarrays.
Conquer: Sort the subarrays recursively.
Combine: Combine the sorted subarrays.
Time Complexity: O(nlogn)O(nlogn) (average case).
3. Binary Search
Advantages of Divide-and-Conquer
Disadvantages of Divide-and-Conquer
Applications
Conclusion
The Divide-and-Conquer technique is a powerful tool for designing algorithms that break
problems into smaller, more manageable pieces. When combined with an efficient merging
strategy, it can significantly improve performance for a wide range of computational tasks.
1. Optimal Substructure:
A problem exhibits optimal substructure if its solution can be constructed efficiently from
the solutions of its subproblems.
Example: In the Fibonacci sequence, F(n)=F(n−1)+F(n−2)F(n)=F(n−1)+F(n−2).
2. Overlapping Subproblems:
A problem has overlapping subproblems if the same subproblem is solved multiple times
during computation.
Example: In the Fibonacci sequence, F(3)F(3) is computed when finding
both F(4)F(4) and F(5)F(5).
3. Memoization vs. Tabulation:
o Memoization (Top-Down Approach): Recursively solve the problem and store
intermediate results in a table (cache).
o Tabulation (Bottom-Up Approach): Solve the problem iteratively by filling up a
table starting from the smallest subproblems.
1. Define the Subproblem: Break the problem into smaller subproblems that solve part of
the larger problem.
2. Recursive Relation: Derive a formula that relates the solution of a subproblem to larger
subproblems.
3. Base Cases: Define the simplest cases that can be solved directly.
4. Memoization or Tabulation: Implement the solution using a top-down or bottom-up
approach.
1. Fibonacci Sequence
python
Copy code
def fibonacci(n):
dp = [0] * (n + 1)
dp[0], dp[1] = 0, 1
for i in range(2, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
Problem: Find the length of the longest subsequence common to two strings.
Tabulation Code:
python
Copy code
def lcs(X, Y):
m, n = len(X), len(Y)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if X[i - 1] == Y[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
return dp[m][n]
Problem: Given weights and values of nn items, find the maximum value that can be
obtained by selecting items without exceeding the weight limit.
Recursive
Relation:K(n,W)={0if n=0 or W=0K(n−1,W)if Wn>Wmax(K(n−1,W),Vn+K(n−1,W−W
n))otherwiseK(n,W)=⎩⎨⎧0K(n−1,W)max(K(n−1,W),Vn+K(n−1,W−Wn))if n=0 or W=0i
f Wn>Wotherwise
Base Case: K(0,W)=0K(0,W)=0.
Tabulation Code:
python
Copy code
def knapsack(weights, values, W):
n = len(weights)
dp = [[0] * (W + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for w in range(W + 1):
if weights[i - 1] <= w:
dp[i][w] = max(dp[i - 1][w], values[i - 1] + dp[i - 1][w
- weights[i - 1]])
else:
dp[i][w] = dp[i - 1][w]
return dp[n][W]
Applications
Conclusion
Dynamic Programming is a powerful algorithmic tool for solving problems with overlapping
subproblems and optimal substructure. By avoiding redundant calculations and using a
systematic approach, it ensures efficient solutions to a wide range of computational problems.