Computational Problem-Solving in Python
Computational Problem-Solving in Python
MODULE IV
Computational Approaches to Problem-Solving
Computational approaches to problem-solving use the power of computers to solve complex
problems efficiently. This module covers key strategies such as:
Brute Force: Trying all possible solutions to find the correct one.
Divide-and-Conquer: Breaking a problem into smaller parts, solving each, and
combining the results.
Dynamic Programming: Solving problems by reusing solutions to smaller
subproblems.
Greedy Algorithms: Making the best choice at each step to find a solution.
Randomized Approaches: Using randomness to explore solutions in an
unpredictable way.
1
Examples:
1. Padlock Combination:
Imagine a padlock with 3 dials, each having 10 digits (0-9).
The brute-force approach would involve trying all 1,000 combinations (000 to 999)
until the correct one unlocks the padlock.
Steps:
1. Start with 000.
2. Increment sequentially: 001, 002, ..., 999.
3. Stop when the lock opens.
Program:
# Brute-force approach to unlock a padlock
def padlock(key):
for combination in range(1000): # 000 to 999
guess = f"{combination:03}" # Format to always have 3 digits
print(f"Trying combination: {guess}")
if guess == key:
print(f"Padlock opened with combination: {guess}")
break
# Example usage
padlock(“302”)
2
2. Password Guessing
Here, 95 is the total number of possible characters (26 lowercase + 26 uppercase + 10 digits +
33 special characters), and 956 gives the total number of possible combinations for a 6-
character password.
# Example usage
password = "CaT" # Set the correct password
check_password(password)
3
2. DIVIDE AND CONQUER ALGORITHM
Divide and Conquer Algorithm is a problem-solving technique used to solve problems by dividing the
main problem into sub-problems, solving them individually and then merging them to find solution to
the original problem.
The Divide and Conquer algorithmic approach consists of three main steps: Divide,
Conquer, and Merge.
1. Divide
Goal: Break down the original problem into smaller, more manageable subproblems.
Each subproblem represents a part of the overall problem.
The division continues recursively until the subproblems are simple enough to solve
directly.
2. Conquer
4
3. Merge
Goal: Combine the solutions of the subproblems to form the solution to the original
problem.
Once the subproblems are solved, their solutions are recursively merged to resolve the
overall problem efficiently.
The Divide and Conquer algorithm can efficiently find the maximum element in an array by
following these steps:
Divide:
Conquer:
Once the subarrays have only one element, return that element as the maximum of
that subarray.
Merge:
Compare the maximums of the two halves and return the larger value as the maximum
for the combined array.
5
Python Program:
if left == right:
return arr[left]
6
arr = [12, 34, 67, 89, 23, 45, 78]
Merge Sort
Ref: Notes/Video : Module 3 – Part – 8
1. Divide: The array is divided into two halves recursively until each subarray has one
element.
2. Sort: Each sub-array is sorted individually.
3. Merge: The sorted sub-arrays are merged to produce a single sorted array.
7
Algorithm Outline:
8
3. DYNAMIC PROGRAMMING APPROACH
def fib(n):
if n <= 1:
return n
else:
return(fib(n-1) + fib(n-2))
n = int(input("Enter n :"))
if n <= 0:
else:
print("Fibonacci sequence:")
for i in range(n):
9
Consider n = 5 ; We know fib(0) = 0 and fib(1) = 1
Fib(5)
Fib(4) Fib(3)
Fib(2) Fib(1)
Fib(3) Fib(2)
Fib(1) Fib(0)
Fib(2) Fib(1) Fib(1) Fib(0)
Fib(1) Fib(0)
Even though the recursive approach is intuitive, it often leads to increased time complexity.
This is because the same subproblems are repeatedly calculated, resulting in redundant
computations and inefficient performance.
Definition:
Memoization solves the problem recursively while storing the results of subproblems
in a table (often a dictionary or array).
Process:
o When a subproblem is solved, its result is saved.
o If the same subproblem is encountered again, the solution is retrieved from the
table instead of being recalculated.
10
2. Tabulation (Bottom-Up Approach)
Definition:
Tabulation solves the problem iteratively by filling a table (usually an array) in a
bottom-up manner.
Process:
o Starts by solving the smallest subproblems.
o Uses their solutions to iteratively build solutions for larger subproblems.
Advantages:
o Eliminates recursion overhead.
o Suitable for problems where all subproblems need to be solved.
def fib(n):
return array
11
Compare Recursion and Dynamic Programming
Using a bottom-up
Recursion frequently employs
methodology, dynamic
a top-down method in which
programming starts by
the primary problem is broken
resolving the smallest
down into more manageable
subproblems before moving
subproblems.
Approach on to the primary issue.
Memory Usage
12
Features Recursion Dynamic Programming
13
[Link] ALGORITHM FOR PROBLEM SOLVING
Let’s consider the task of traveling from Thiruvananthapuram to Ernakulam with multiple
available modes of transport: bike, car, bus, train, airplane, or even walking. The goal is to
select the mode of transport based on a set of constraints or objectives:
At each step, the greedy algorithm makes the locally optimal choice by filtering options
based on the immediate constraint.
Available Options:
14
o Bike: 6 hours
o Car: 4.5 hours
o Bus: 5 hours
o Train: 3.5 hours
o Airplane: 1 hour
o Walking: 40 hours
Greedy Decision:
Select modes that satisfy the time constraint of reaching as quickly as possible. The airplane
(1 hour) is the fastest, followed by the train (3.5 hours).
Filter Outcome:
Airplane, Train.
15
Stage 2: Minimize Cost (Economical Constraint)
Greedy Decision:
Select the train as it is cheaper and satisfies the economical constraint.
Filter Outcome:
Train.
Final Decision
The algorithm selects the train as the final mode of transport based on:
Locally Optimal Choices: At each step, the algorithm optimizes for the immediate
constraint (time, cost, practicality) without revisiting earlier decisions.
Global Solution: The sequence of locally optimal decisions leads to an efficient and
practical final solution.
The Coin Changing Problem aims to find the minimum number of coins required to
make a specified amount using valid Indian Rupee coins. The greedy algorithm
repeatedly selects the largest denomination that fits into the remaining amount.
16
Example:
1. Start with ₹10: Take one ₹10 coin (₹18 - ₹10 = ₹8 left).
2. Next, ₹5: Take one ₹5 coin (₹8 - ₹5 = ₹3 left).
3. Next, ₹2: Take one ₹2 coin (₹3 - ₹2 = ₹1 left).
4. Finally, ₹1: Take one ₹1 coin (₹1 - ₹1 = ₹0 left).
1. Local Optimization
o Makes the best possible choice at each step using only current state
information.
2. Irrevocable Decisions
o Choices are final; no backtracking or revision of earlier decisions.
3. Problem-Specific Heuristics
o Relies on heuristics tailored to the problem's properties for decision-making.
4. Optimality
17
o
Guarantees optimal solutions for problems like coin change, Huffman coding,
and Kruskal's algorithm, but not universally applicable.
5. Efficiency
o High efficiency in time and space due to reliance on local information and
limited exploration of solutions.
Pr o b l e m ( T as k C o m p l eti o n Pr o b l e m )
Given an array of positive integers each indicating the completion time for a task, find the maximum
number of tasks that can be completed in the limited amount of time that you have.
Steps:
• Iterating:
breaks.
18
Program:
total_time = 0
task_count = 0
total_time += time
task_count += 1
else:
break
return task_count
Example usage
completion_times = [2, 3, 1, 4, 6]
available_time = 8
19
Comparison between Dynamic programming and Greedy Approach
Advantages
Disadvantages
Not guaranteed to find the best solution: Greedy algorithms may not find the best
solution because they don't consider all the data.
Local optima: Greedy algorithms may get stuck in local optima and fail to find the
global optimum.
Dependence on problem structure: Greedy algorithms may not work well for
problems that don't fit the greedy paradigm.
Lack of rigorous proof: Greedy algorithms often lack a rigorous proof of correctness
20
Problem Details
Mr. Chottu has a bag with a capacity of 15 kg, and he plans to fill it with objects to maximize his
profit as he heads to the market for a sale. He has the following items with their respective weights
and profits:
Steps:
21
Table of Calculations:
Weight
Object Profit (P) P/W
(W)
1 10 2 5.0
2 5 3 1.67
3 15 5 3.0
4 7 7 1.0
5 6 1 6.0
6 18 4 4.5
7 3 1 3.0
22
Sorted by P/W (Descending):
5 6 1 6
1 10 2 5
6 18 4 4.5
3 15 5 3
7 3 1 3
2 5 3 1.67
4 7 7 1
23
Execution:
24
Python's random module provides various functions to generate random numbers and perform
random operations. Here's an overview of the most commonly used functions:
[Link]()
o Returns a random float number between 000 (inclusive) and 111 (exclusive).
o Example:
import random
print([Link]()) # e.g., 0.7234
[Link](a, b)
o Returns a random float between aaa and bbb (inclusive).
o Example:
[Link](a, b)
o Returns a random integer between aaa and bbb (both inclusive).
o Example:
[Link](sequence)
o Returns a random element from a non-empty sequence (like a list, tuple, or
string).
o Example:
[Link](sequence, k=n)
o Returns a list of n random elements (with replacement).
o Example:
[Link](sequence)
o Shuffles the sequence in place.
o Example:
nums = [1, 2, 3, 4, 5]
[Link](nums)
print(nums) # e.g., [3, 1, 5, 2, 4]
25
[Link] APPROACH TO PROBLEM SOLVING
Introduction
Calculating the area of a unit circle using a randomized approach involves Monte Carlo
simulation, a technique often used in numerical methods to estimate values through random
sampling.
Steps to Calculate the Area of a Unit Circle Using Monte Carlo Simulation:
26
Program
import random
num_points = 100000
inside_circle = 0
for _ in range(num_points):
x = [Link](-1,1)
y = [Link](-1,1)
inside_circle = inside_circle + 1
area_of_circle = 4 * (inside_circle/num_points)
27
oApproach:
Simulate birthdays for a group multiple times.
Record how often at least two birthdays match.
Estimate the probability based on these simulations.
o Application: Simplifies combinatorial probability analysis.
3. Random Walk on a Grid
o Problem: Determine the expected return time to the starting point for a person
taking random steps on a grid.
o Approach:
Simulate random walks.
Calculate average return time from multiple trials.
o Application: Models stochastic processes and dynamics.
4. Random Coin Flips
o Problem: Estimate the probability distribution of heads in a series of coin
flips.
o Approach:
Simulate repeated coin flips.
Record outcomes and analyze the distribution of heads.
o Application: Empirical analysis of probabilistic events.
import random
head=0
tail=0
for _ in range(n):
if [Link](0,1)==1:
28
head=head+1
else:
tail=tail+1
Q. Toss the coin N=100,500,1000,5000 and 500000 times and compute the
probability (p) of head in each case.
import random
n = [100,500,1000,5000,500000]
prob=[]
count=0
while(count<len(n)):
head=0
tail=0
for i in range(n[count]):
if [Link](0,1)==0:
head=head+1
else:
tail=tail+1
p=head/(head+tail)
[Link](p)
count=count+1
print(prob)
1. Complexity Reduction
o Simplifies problems by introducing probabilistic decisions.
o Example: Optimizing screening station placements by sampling.
2. Versatility
o Adaptable across domains like optimization and simulations.
3. Performance
o Enhances efficiency in large datasets.
29
Characteristics of Randomized Approach
1. Probabilistic Choices
o Decisions are based on random sampling, leading to variable but statistically
predictable outcomes.
2. Efficiency
o Sacrifices deterministic guarantees for probabilistic correctness.
3. Average-Case Complexity
o Performance is evaluated over multiple iterations, focusing on typical behavior
rather than worst-case scenarios.
Randomized:
o Incorporates chance.
o Efficient for large, complex, or variable datasets.
o Example: Estimating customer satisfaction via random sampling.
Deterministic:
o Yields exact, repeatable results.
o Suitable for precision-critical problems.
o Example: Exact cost calculation in shopping.
30