Comp 372 Assignment 1

Download as pdf or txt
Download as pdf or txt
You are on page 1of 13

Assignment 1:

1.1-4 : How are the shortest-path and traveling-salesman problems given above similar?
How are they different?

Similarities:
1. Graph Representation: Both problems are based on a graph representation where
nodes represent locations (cities, points, etc.), and edges represent connections
(distances, costs, etc.) between those locations.
2. Optimization: Both problems aim to find optimal solutions. In the shortest-path
problem, the goal is to find the shortest route (minimum distance or cost) between two
specific nodes. In the traveling-salesman problem, the objective is to find the shortest
possible tour that visits all nodes exactly once and returns to the starting node.
Differences:
1. Objective:
• Shortest-Path Problem: In this problem, you are given two specific nodes, a
source node, and a destination node. The objective is to find the shortest path
(lowest total distance or cost) between these two nodes.
• Traveling-Salesman Problem: In this problem, you have a set of nodes that need
to be visited exactly once and return to the starting node, forming a cycle. The
objective is to find the shortest possible tour that visits all nodes.
2. Constraints:
• Shortest-Path Problem: There are no specific constraints other than finding the
shortest path between the given source and destination nodes.
• Traveling-Salesman Problem: The main constraint is that all nodes must be
visited exactly once, and the tour must form a closed cycle.
3. Optimal Solution:
• Shortest-Path Problem: The optimal solution involves finding the shortest path
between two specific nodes. It can be solved using algorithms like Dijkstra's
algorithm, Bellman-Ford algorithm, or A* search.
• Traveling-Salesman Problem: The optimal solution involves finding the shortest
tour that visits all nodes. This problem is known to be NP-hard, which means that
there is no known efficient algorithm that can solve all instances in polynomial
time. Various heuristic and approximation algorithms exist, such as the nearest
neighbor algorithm, the Christofides algorithm, and genetic algorithms.
4. Application:
• Shortest-Path Problem: It has direct applications in navigation systems, network
routing, transportation planning, and logistics.
• Traveling-Salesman Problem: It has applications in route optimization for
salespeople, circuit design, DNA sequencing, and various scheduling problems.
In summary, while both the shortest-path problem and the traveling-salesman problem deal
with finding optimal routes in graphs, they have different objectives, constraints, and solution
approaches. The shortest-path problem focuses on finding the shortest route between two
specific nodes, while the traveling-salesman problem aims to find the shortest tour that visits all
nodes and returns to the starting node.

1.2-2: Suppose we are comparing implementations of insertion sort and merge sort on the
same machine. For inputs of size n, insertion sort runs in 8n2 steps, while merge sort runs in
64n lg n steps. For which values of n does insertion sort beat merge sort?

To determine for which values of n insertion sort beats merge sort in terms of running time
(number of steps), we need to find the values of n for which the running time of insertion sort
(8n^2) is less than the running time of merge sort (64n log n).
Mathematically, we can set up the inequality:
8n^2 < 64n log n
Dividing both sides by 8n:
n < 8 log n
Now, we need to solve for n. Keep in mind that log here is the logarithm base 2, as it is the
typical base used in computer science analyses.
However, solving this equation analytically for n is not straightforward, and we would need to
use numerical methods or approximation techniques. However, we can use the fact that
logarithmic functions grow slower than polynomial functions to get a sense of when insertion
sort might beat merge sort.
Let's try some values:
1. For small values of n, insertion sort might be better due to its lower constant factor even
though it has a quadratic time complexity. But as n grows, the quadratic term dominates
and insertion sort becomes inefficient.
2. As n grows larger, the logarithmic term in merge sort (64n log n) grows significantly
slower than the quadratic term in insertion sort (8n^2). So, for sufficiently large values of
n, merge sort will be more efficient.
In practice, insertion sort becomes more efficient than merge sort for very small input sizes,
where the constant factors and lower-order terms might make a significant difference. However,
for larger input sizes, merge sort's superior asymptotic complexity (n log n) will eventually make
it faster than insertion sort (n^2).
To summarize, insertion sort might be more efficient for very small input sizes, but as n grows,
merge sort will quickly become more efficient due to its better asymptotic complexity. The exact
point at which merge sort becomes more efficient depends on the specific constants and
coefficients involved in the algorithms' running time equations.
2.1-3: Consider the searching problem: Input: A sequence of n numbers A D ha1; a2;:::;ani and a
value . Output: An index i such that D AŒi or the special value NIL if does not appear in A. Write
pseudocode for linear search, which scans through the sequence, looking for . Using a loop
invariant, prove that your algorithm is correct. Make sure that your loop invariant fulfills the
three necessary properties.

1. LinearSearch(A, x):
2. for i = 1 to length(A):
3. if A[i] == x:
4. return i
5. return NIL
6.

Loop Invariant Explanation:


A loop invariant is a condition that holds true before and after each iteration of a loop. In this
case, our loop invariant could be stated as follows:
"At the start of each iteration of the loop, the subsequence A[1..i-1] does not contain the value
x."
Three Necessary Properties:
1. Initialization: Before the loop starts, i is set to 1. So, the subsequence A[1..i-1] is empty,
and the loop invariant holds true.
2. Maintenance: At each iteration, we check if A[i] is equal to x. If it is, we immediately
return i, indicating that we've found the value x. If A[i] is not equal to x, then the loop
continues to the next iteration, and we move to the next element in the sequence. The
loop invariant still holds true because we haven't found x in the subsequence A[1..i-1],
and we're only checking A[i] in this iteration.
3. Termination: The loop terminates either when we find the value x in the sequence (in
which case we return its index) or when we have checked all elements of the sequence.
If we've checked all elements and haven't found x, we return NIL. In either case, the loop
invariant still holds true, as the loop ends without violating it.
Summary:
The loop invariant "At the start of each iteration of the loop, the subsequence A[1..i-1] does not
contain the value x" satisfies all three necessary properties for correctness: initialization,
maintenance, and termination. This demonstrates that the linear search algorithm correctly
identifies the index of the value x in the sequence A or returns NIL if x is not present.

2.2-3: Consider linear search again (see Exercise 2.1-3). How many elements of the input
sequence need to be checked on the average, assuming that the element being searched for is
equally likely to be any element in the array? How about in the worst case? What are the
average-case and worst-case running times of linear search in ‚-notation? Justify your answers.

In linear search, we sequentially scan through the input sequence to find the target element.
Let's analyze the average-case and worst-case scenarios in terms of the number of elements
checked and the corresponding running times.
Average-Case: Assuming that the element being searched for is equally likely to be any element
in the array, on average, we would need to check half of the elements in the array before finding
the target element (or determining that it's not present). This is because, on average, the
element we're looking for could be anywhere in the sequence with equal probability.
So, in the average-case scenario, we would need to check approximately n/2 elements on
average, where n is the number of elements in the array.
Worst-Case: In the worst-case scenario, the target element might be the last element in the
array, or it might not be present at all. This means we would need to check all n elements in the
array before determining that the element is not present or finding it at the last position.
So, in the worst-case scenario, we would need to check all n elements.
Running Time: In big-O notation, the average-case running time of linear search would be
O(n/2), which simplifies to O(n). This is because constant factors and lower order terms are not
considered in big-O notation, and the dominant factor here is the linear relationship with the
input size n.
Similarly, the worst-case running time of linear search would be O(n), since it takes at most n
comparisons to find the target element or determine its absence.
Justification: The average-case analysis assumes that the target element is equally likely to be
any element in the array. This is a reasonable assumption if we don't have any specific
information about the distribution of the target element. The average-case running time is
calculated based on the expectation of the number of comparisons required, which is
approximately n/2.
The worst-case analysis considers the scenario where the target element is at the last position
or absent. In this case, we have to check all n elements to reach a conclusion.
In both cases, the linear search algorithm's performance is directly proportional to the size of
the input sequence, leading to a linear running time complexity.

2.3-5: Referring back to the searching problem (see Exercise 2.1-3), observe that if the sequence
A is sorted, we can check the midpoint of the sequence against and eliminate half of the
sequence from further consideration. The binary search algorithm repeats this procedure,
halving the size of the remaining portion of the sequence each time. Write pseudocode, either
iterative or recursive, for binary search. Argue that the worst-case running time of binary search
is ‚.lg n/.

1. BinarySearch(A, x):
2. left = 1
3. right = length(A)
4.
5. while left <= right:
6. mid = left + (right - left) / 2
7. if A[mid] == x:
8. return mid
9. else if A[mid] < x:
10. left = mid + 1
11. else:
12. right = mid - 1
13.
14. return NIL
15.
Explanation of Worst-Case Running Time:
Binary search operates on the principle of repeatedly dividing the search interval in half. In each
step, it compares the middle element of the interval with the target value x. If the middle
element is equal to x, the search is successful. If the middle element is less than x, it searches
the right half of the interval. If the middle element is greater than x, it searches the left half of
the interval.
At each step, the search interval is halved, effectively reducing the number of remaining
elements that need to be considered. This process continues until the interval is reduced to a
single element or until the interval becomes empty, indicating that the element is not present in
the sequence.
The number of iterations required to reduce the interval to a single element (or an empty
interval) is determined by how many times we can divide the initial interval's size (n) in half. This
can be represented as log₂(n), where log₂ represents the base-2 logarithm.
In other words, after each step of binary search, the number of remaining elements is halved.
Therefore, the worst-case running time of binary search is proportional to the number of times
we can halve n, which is O(log n).
This demonstrates that the binary search algorithm has a worst-case running time of O(log n)
because it quickly narrows down the search space by half in each step, making it much more
efficient compared to linear search, especially for large input sizes.
3.1-1: Let f .n/ and g.n/ be asymptotically nonnegative functions. Using the basic definition of ‚-
notation, prove that max.f .n/; g.n// D ‚.f .n/ C g.n//.
3-1:
4.1-2: Write pseudocode for the brute-force method of solving the maximum-subarray problem.
Your procedure should run in ‚.n2/ time.

1. BruteForceMaxSubarray(A):
2. n = length(A)
3. max_sum = NEGATIVE_INFINITY
4. left_index = 0
5. right_index = 0
6.
7. for i = 1 to n:
8. current_sum = 0
9. for j = i to n:
10. current_sum = current_sum + A[j]
11. if current_sum > max_sum:
12. max_sum = current_sum
13. left_index = i
14. right_index = j
15.
16. return (left_index, right_index, max_sum)
17.

Explanation:
• The algorithm iterates through each possible starting index of the subarray (i) and then
iterates through each possible ending index (j) to calculate the sum of the subarray
A[i..j].
• The current_sum variable is used to keep track of the sum of the current subarray being
considered.
• If the current_sum becomes greater than the max_sum encountered so far, the
max_sum is updated, along with the left_index and right_index indicating the current
maximum subarray.
• The algorithm returns a tuple containing the starting index, ending index, and maximum
sum of the subarray.

4.2-1:

Let's denote the matrices as follows:


A=|13||75|
B=|68||42|
We want to compute the product C = A * B.
Step 1: Divide the matrices into smaller submatrices:
Divide matrix A and B into 2x2 submatrices: A11 = | 1 | A12 = | 3 | | 7 | | 5 |
B11 = | 6 | B12 = | 8 | | 4 | | 2 |
Step 2: Compute the following 7 products:
P1 = A11 * (B12 - B22) P2 = (A11 + A12) * B22 P3 = (A21 + A22) * B11 P4 = A22 * (B21 - B11) P5
= (A11 + A22) * (B11 + B22) P6 = (A12 - A22) * (B21 + B22) P7 = (A11 - A21) * (B11 + B12)
Step 3: Compute the submatrices of the result matrix C:
C11 = P5 + P4 - P2 + P6 C12 = P1 + P2 C21 = P3 + P4 C22 = P5 + P1 - P3 - P7
Step 4: Assemble the result matrix C:
C = | C11 C12 | | C21 C22 |
Now, let's compute the intermediate products:
P1 = A11 * (B12 - B22) = | 1 | * | -2 | | 7 | | 2 |
P2 = (A11 + A12) * B22 = | 1 + 3 | * | 8 | | 7 + 5 | | 2 |
P3 = (A21 + A22) * B11 = | 7 + 4 | * | 6 | | 5 + 2 | | 4 |
P4 = A22 * (B21 - B11) = | 5 | * | -2 | | 2 | | 2 |
P5 = (A11 + A22) * (B11 + B22) = | 1 + 5 | * | 6 + 8 | | 7 + 2 | | 4 + 2 |
P6 = (A12 - A22) * (B21 + B22) = | 3 - 5 | * | 4 + 8 | | 5 - 2 | | 2 + 2 |
P7 = (A11 - A21) * (B11 + B12) = | 1 - 7 | * | 6 + 8 | | 7 - 5 | | 4 + 2 |
Now, let's compute the submatrices of the result matrix C:
C11 = P5 + P4 - P2 + P6 = | 50 + 4 - 16 + 48 | | 18 + 2 - 4 |
C12 = P1 + P2 = | -2 + 16 | | 9 |
C21 = P3 + P4 = | 11 | | 7 |
C22 = P5 + P1 - P3 - P7 = | 50 - 2 - 42 - 6 | | 18 + 7 - 30 - 8 |
Finally, assemble the result matrix C:
C = | 86 14 | | -11 -13 |
So, the result of the matrix product A * B using Strassen's algorithm is:
C = | 86 14 | | -11 -13 |
4.3-2:

4.4-7:

Recursion Tree:

1. T(n)
2. / | \
3. T(n/2) T(n/2) T(n/2)
4. | | |
5. T(n/4) T(n/4) T(n/4)
6. ... ... ...
7. | | |
8. T(1) T(1) T(1)
9.
4.5-3:

You might also like