Algorithm Lab Experiments: Sorting, Searching, Greedy, DP and String Matching
Experiment 1: Insertion Sort
Aim: To implement Insertion Sort.
Theory: Insertion sort builds the sorted array one element at a time, like arranging playing cards in your hand. Each element (key) is inserted into its correct place in the already sorted left part by shifting larger elements right. It is stable, in-place and works best on small or nearly sorted data.
Algorithm:
- Start.
- Read
nand the arrayA. - For
i = 2ton, setkey = A[i]andj = i - 1. - While
j ≥ 1andA[j] > key, shiftA[j]toA[j+1]and decreasej. - Put the key at
A[j+1]. - Display the sorted array.
- Stop.
Time Complexity: Best O(n), Average O(n²), Worst O(n²). Space O(1).
Conclusion: Insertion sort was implemented successfully. It is simple and efficient for small or nearly sorted data but slow for large inputs.
Experiment 2: Merge Sort
Aim: To implement Merge Sort.
Theory: Merge sort uses the Divide and Conquer strategy. The array is divided into two halves, each half is sorted recursively, and then the two sorted halves are merged into one. It is stable and always takes O(n log n) time, but needs extra space for merging.
Algorithm:
- Start.
- Read
nand the arrayA. - If the array has one element or none, it is already sorted, so return.
- Find the middle:
mid = (low + high) / 2. - Recursively sort the left half
A[low..mid]. - Recursively sort the right half
A[mid+1..high]. - Merge the two halves: compare the front elements of both, copy the smaller one into a temporary array, and repeat. Then copy any remaining elements.
- Copy the temporary array back to
Aand display it. - Stop.
Time Complexity: Best, Average and Worst all O(n log n). Space O(n).
Conclusion: Merge sort was implemented successfully. It gives guaranteed O(n log n) performance, which makes it suitable for large data sets.
Experiment 3: Binary Search
Aim: To implement Binary Search.
Theory: Binary search finds an element in a sorted array. It compares the key with the middle element. If they are equal, the search ends. If the key is smaller, it searches the left half, otherwise the right half. The search space halves at every step.
Algorithm:
- Start.
- Read the sorted array
Aand the key to search. - Set
low = 0andhigh = n - 1. - Repeat while
low ≤ high:- Find
mid = (low + high) / 2. - If
A[mid] = key, print the position and stop. - If
key < A[mid], sethigh = mid - 1. - Otherwise, set
low = mid + 1.
- Find
- If the loop ends, print “Element not found”.
- Stop.
Time Complexity: Best O(1), Average O(log n), Worst O(log n). Space O(1) (iterative).
Conclusion: Binary search was implemented successfully. It is much faster than linear search but needs the array to be sorted.
Experiment 4: Dijkstra’s Algorithm
Aim: To implement the Single Source Shortest Path problem using the Greedy strategy (Dijkstra’s algorithm).
Theory: Dijkstra’s algorithm finds the shortest path from one source vertex to all other vertices in a weighted graph with non-negative edge weights. It is greedy: at each step it picks the unvisited vertex with the smallest known distance, then relaxes (updates) the distances of its neighbours.
Algorithm:
- Start.
- Read the graph and the source vertex
s. - Set
dist[s] = 0anddist[v] = ∞for all other vertices. Mark all vertices unvisited. - Pick the unvisited vertex
uwith the minimumdist, and mark it visited. - For each neighbour
vofu: ifdist[u] + w(u,v) < dist[v], updatedist[v] = dist[u] + w(u,v). - Repeat Steps 4 and 5 until all vertices are visited.
- Display the shortest distances.
- Stop.
Time Complexity (n = number of vertices, using an adjacency matrix):
- Best case: O(n²)
- Average case: O(n²)
- Worst case: O(n²)
- Space: O(n)
Conclusion: Dijkstra’s algorithm was implemented successfully and the shortest paths from the source were found. It does not work with negative edge weights.
Experiment 5: Fractional Knapsack
Aim: To implement the Fractional Knapsack problem using the Greedy method.
Theory: Given items with weights and values and a knapsack of capacity W, we maximize the total value. Items can be broken into fractions. The greedy choice is to sort items by value/weight ratio in descending order and take them in that order. If an item does not fit, take the fraction that fills the remaining capacity.
Algorithm:
- Start.
- Read
n, the weights, the values, and the capacityW. - Calculate the ratio
value / weightfor each item. - Sort the items in decreasing order of ratio.
- Set
total value = 0. - For each item in sorted order:
- If its weight ≤ remaining capacity, take the whole item, add its value, and reduce the capacity.
- Otherwise, take the fraction
remaining capacity / weight, add that fraction of the value, and stop.
- Display the total value.
- Stop.
Time Complexity (n = number of items):
- Best case: O(n log n)
- Average case: O(n log n)
- Worst case: O(n log n)
- Space: O(1)
Conclusion: The fractional knapsack problem was solved successfully using the greedy method. Greedy gives the optimal answer here because items can be divided.
Experiment 6: Floyd’s Algorithm (All-Pairs Shortest Paths)
Aim: To implement the All-Pairs Shortest Path problem using Floyd’s algorithm (Dynamic Programming).
Theory: Floyd’s algorithm finds the shortest distance between every pair of vertices. It takes each vertex k as a possible intermediate vertex and updates D[i][j] if the path through k is shorter: D[i][j] = min(D[i][j], D[i][k] + D[k][j]). It works with negative edges but not negative cycles.
Algorithm:
- Start.
- Read the number of vertices
nand the weight matrixD(∞ where there is no edge, 0 on the diagonal). - For
k = 1ton:- For
i = 1ton:- For
j = 1ton:- If
D[i][k] + D[k][j] < D[i][j], setD[i][j] = D[i][k] + D[k][j].
- If
- For
- For
- Display the final matrix
D. - Stop.
Time Complexity (n = number of vertices):
- Best case: O(n³)
- Average case: O(n³)
- Worst case: O(n³)
- Space: O(n²)
Conclusion: Floyd’s algorithm was implemented successfully and the shortest distances between all pairs of vertices were found using dynamic programming.
Experiment 7: Travelling Salesperson Problem
Aim: To implement the Travelling Salesperson Problem using Dynamic Programming.
Theory: A salesperson must visit every city exactly once and return to the start with minimum total cost. The DP approach stores the minimum cost of reaching city j after visiting a subset S of cities: C(S, j) = min over i in S of [C(S - {j}, i) + cost(i, j)]. Reusing subproblems makes it far better than brute force O(n!).
Algorithm:
- Start.
- Read
nand the cost matrix. - Take city 1 as the start and set
C({1}, 1) = 0. - For subset sizes 2 to
n, for every subsetScontaining city 1 and every cityjinS(j ≠ 1), find the minimum cost of reachingjby trying every other cityiinSas the previous city. - Find the minimum of
C(all cities, j) + cost(j, 1)over allj. - Display the minimum tour cost and the path.
- Stop.
Time Complexity (n = number of cities):
- Best case: O(n² · 2ⁿ)
- Average case: O(n² · 2ⁿ)
- Worst case: O(n² · 2ⁿ)
- Space: O(n · 2ⁿ)
Conclusion: The Travelling Salesperson Problem was solved successfully using dynamic programming. It is faster than brute force but still exponential, so it is practical only for small n.
Experiment 8: N-Queens Problem
Aim: To implement the N-Queens problem using Backtracking.
Theory: The goal is to place n queens on an n × n chessboard so that no two queens attack each other, meaning no two share a row, column or diagonal. Backtracking places queens one row at a time. If a queen cannot be safely placed in a row, it goes back to the previous row and moves that queen to the next column.
Algorithm:
- Start.
- Read
nand create ann × nboard. - Start with row 0.
- For each column in the current row, check if placing a queen is safe (no queen in the same column or either diagonal above).
- If safe, place the queen and move to the next row.
- If all
nqueens are placed, print the board as a solution. - If no column in the row is safe, remove the previous queen (backtrack) and try its next column.
- Stop when all possibilities are tried.
Time Complexity (n = number of queens / board size):
- Best case: O(n) (a solution found without much backtracking)
- Average case: O(n!)
- Worst case: O(n!)
- Space: O(n) for the recursion stack (O(n²) if the full board is stored)
Conclusion: The N-Queens problem was solved successfully using backtracking. Backtracking rejects invalid placements early, so it is more efficient than trying every arrangement.
Experiment 9: Naive String Matching
Aim: To implement the Naive String Matching algorithm.
Theory: The naive algorithm finds all occurrences of a pattern P in a text T. It slides the pattern over the text one position at a time and compares characters left to right at each position. If all characters match, the position is reported. It needs no preprocessing but is slow.
Algorithm:
- Start.
- Read the text
Tand the patternP. Find their lengthsnandm. - For
i = 0ton - m:- Set
j = 0. - While
j < mandT[i + j] = P[j], increasej. - If
j = m, print “Pattern found at indexi”.
- Set
- If no match was found, print “Pattern not found”.
- Stop.
Time Complexity (n = length of the text, pattern length treated as comparable to n in the worst case):
- Best case: O(n)
- Average case: O(n)
- Worst case: O(n²)
- Space: O(1)
Conclusion: The naive string matching algorithm was implemented successfully. It is easy to understand but inefficient for long texts because it rechecks characters.
Experiment 10: Rabin-Karp Algorithm
Aim: To implement the Rabin-Karp string matching algorithm.
Theory: Rabin-Karp compares the hash value of the pattern with the hash of each window of the text instead of comparing characters directly. The next window’s hash is computed quickly from the previous one using a rolling hash. If the hashes match, the characters are compared to rule out a false match (spurious hit).
Algorithm:
- Start.
- Read the text
T, patternP, a basedand a primeq. Find their lengthsnandm. - Calculate the hash of the pattern and of the first window of the text.
- For
i = 0ton - m:- If the pattern hash equals the current window hash, compare the characters one by one. If all match, print “Pattern found at index
i”. - Calculate the hash of the next window by removing the leading character and adding the next one.
- If the pattern hash equals the current window hash, compare the characters one by one. If all match, print “Pattern found at index
- If no match was found, print “Pattern not found”.
- Stop.
Time Complexity (n = length of the text, pattern length treated as comparable to n in the worst case):
- Best case: O(n)
- Average case: O(n)
- Worst case: O(n²) (when many spurious hits occur)
- Space: O(1)
Conclusion: The Rabin-Karp algorithm was implemented successfully. Hashing makes it fast on average, and it is useful for searching multiple patterns.
