Professional Documents
Culture Documents
Greedy Algorithm: Feasible Solution
Greedy Algorithm: Feasible Solution
This algorithm may not be the best option for all the problems. It may
produce wrong results in some cases.
This algorithm never goes back to reverse the decision made. This
algorithm works in a top-down approach.
Feasible Solution
A feasible solution is the one that provides the optimal solution to the
problem.
Greedy Algorithm
1. To begin with, the solution set (containing answers) is empty.
Ford-Fulkerson Algorithm
Ford-Fulkerson algorithm is a greedy approach for calculating the
maximum possible flow in a network or a graph.
A term, flow network, is used to describe a network of vertices and edges
with a source (S) and a sink (T). Each vertex, except S and T, can receive
and send an equal amount of stuff through it. S can only send and T can
only receive stuff.
We can visualize the understanding of the algorithm using a flow of liquid
inside a network of pipes of different capacities. Each pipe has a certain
capacity of liquid it can transfer at an instance. For this algorithm, we are
going to find how much liquid can be flowed from the source to the sink at
an instance using the network.
You can kind of eyeball this one and see that although the edges coming
out of the source (vertex s) have a large capacity, we’re bottlenecked by
the edge leading to our home (the sink vertex t) which can only transport 1
unit of water.
Here our flow can clearly be at most the capacity of our smallest edge
leading into t. So can we simply look for the smallest capacity edges and
say definitively that we know our maximum flow? Almost… and we’ll get to
that later with the max-flow min-cut theorem, but first let’s look at a more
difficult example that has multiple edges flowing into the sink.
https://www.youtube.com/watch?v=NwenwITjMys
Dijkstra's Algorithm
Dijkstra's Algorithm allows you to calculate the shortest path between one
node (you pick which one) and every other node in the graph. Let's calculate
the shortest path between node C and the other nodes in our graph:
During the algorithm execution, we'll mark every node with its minimum
distance to node C (our selected node). For node C, this distance is 0. For
the rest of nodes, as we still don't know that minimum distance, it starts
being infinity (∞):
We'll also have a current node. Initially, we set it to C (our selected node). In
the image, we mark the current node with a red dot.
Now, we check the neighbours of our current node (A, B and D) in no
specific order. Let's begin with B. We add the minimum distance of the
current node (in this case, 0) with the weight of the edge that connects our
current node with B (in this case, 7), and we obtain 0 + 7 = 7. We compare
that value with the minimum distance of B (infinity); the lowest value is the
one that remains as the minimum distance of B (in this case, 7 is less than
infinity):
We now need to pick a new current node. That node must be the unvisited
node with the smallest minimum distance (so, the node with the smallest
number and no check mark). That's A. Let's mark it with the red dot:
And now we repeat the algorithm. We check the neighbours of our current
node, ignoring the visited nodes. This means we only check B.
For B, we add 1 (the minimum distance of A, our current node) with 3 (the
weight of the edge connecting A and B) to obtain 4. We compare that 4
with the minimum distance of B (7) and leave the smallest value: 4.
As there are not univisited nodes, we're done! The minimum distance of
each node now actually represents the minimum distance from that node
to node C (the node we picked as our initial node)!
1. Mark your selected initial node with a current distance of 0 and the
rest with infinity.
2. Set the non-visited node with the smallest current distance as the
current node C.
3. For each neighbour N of your current node C: add the current
distance of C with the weight of the edge connecting C-N. If it's
smaller than the current distance of N, set it as the new current
distance of N.
4. Mark the current node C as visited.
5. If there are non-visited nodes, go to step 2.
Prim’s Algorithm-
Prim’s Algorithm is a famous greedy algorithm.
It is used for finding the Minimum Spanning Tree (MST) of a given graph.
To apply Prim’s algorithm, the given graph must be weighted, connected and
undirected.
Step-01:
Randomly choose any vertex.
The vertex connecting to the edge having least weight is usually selected.
Step-02:
Find all the edges that connect the tree to new vertices.
Find the least weight edge among those edges and include it in the existing tree.
If including that edge creates a cycle, then reject that edge and look for the next least
weight edge.
Step-03:
Keep repeating step-02 until all the vertices are included and Minimum Spanning
Tree (MST) is obtained.
Problem-01:
Construct the minimum spanning tree (MST) for the given graph using Prim’s
Algorithm-
Solution-
The above discussed steps are followed to find the minimum cost spanning tree
using Prim’s Algorithm-
Step-01:
Step-02:
Step-03:
Step-04:
Step-05:
Step-06:
Since all the vertices have been included in the MST, so we stop.
Now, Cost of Minimum Spanning Tree
= Sum of all edge weights
= 10 + 25 + 22 + 12 + 16 + 14
= 99 units
Problem-02:
Using Prim’s Algorithm, find the cost of minimum spanning tree (MST) of the given
graph-
Solution-
The minimum spanning tree obtained by the application of Prim’s Algorithm on the
given graph is as shown below-
Now, Cost of Minimum Spanning Tree
= Sum of all edge weights
= 1 + 4 + 2 + 6 + 3 + 10
= 26 units
Huffman Coding
Huffman Coding is a technique of compressing data to reduce its size
without losing any of the details. It was first developed by David Huffman.
Huffman Coding is generally useful to compress the data in which there are
frequently occurring characters.
Initial string
Each character occupies 8 bits. There are a total of 15 characters in the
above string. Thus, a total of 8 * 15 = 120 bits are required to send this
string.
Using the Huffman Coding technique, we can compress the string to a
smaller size.
Huffman coding first creates a tree using the frequencies of the character
and then generates code for each character.
Huffman Coding prevents any ambiguity in the decoding process using the
concept of prefix code ie. a code associated with a character should not
be present in the prefix of any other code. The tree created above helps in
maintaining the property.
Huffman coding is done with the help of the following steps.
Frequency of string
For sending the above string over a network, we have to send the tree as
well as the above compressed-code. The total size is given by the table
below.
A 5 11 5*2 = 10
B 1 100 1*3 = 3
C 6 0 6*1 = 6
D 3 101 3*3 = 9
Without encoding, the total size of the string was 120 bits. After encoding
the size is reduced to 32 + 15 + 28 = 75 .
Decoding the code
For decoding the code, we can take the code and traverse through the tree
to find the character.
Let 101 is to be decoded, we can traverse from the root as in the figure
below.
Decoding
Dynamic Programming
Dynamic Programming is a technique in computer programming that helps
to efficiently solve a class of problems that have overlapping subproblems
and optimal substructure property.
Such problems involve repeatedly calculating the value of the same
subproblems to find the optimum solution.
If the sequence is F(1) F(2) F(3)........F(50), it follows the rule F(n) = F(n-1)
+ F(n-2)
...
But not all problems that use recursion can use Dynamic Programming.
Unless there is a presence of overlapping subproblems like in the fibonacci
sequence problem, a recursion can only reach the solution using a divide
and conquer approach.
That is the reason why a recursive algorithm like Merge Sort cannot use
Dynamic Programming, because the subproblems are not overlapping in
any way.
Dynamic Programming and Recursion:
Dynamic programming is basically, recursion plus using common sense. What it means is
that recursion allows you to express the value of a function in terms of other values of that
function. Where the common sense tells you that if you implement your function in a way
that the recursive calls are done in advance, and stored for easy access, it will make your
program faster. This is what we call Memoization - it is memorizing the results of some
specific states, which can then be later accessed to solve other sub-problems.
The intuition behind dynamic programming is that we trade space for time, i.e. to say
that instead of calculating all the states taking a lot of time but no space, we take up space
to store the results of all the sub-problems to save time later.
Fibonacci (n) = 1; if n = 0
Fibonacci (n) = 1; if n = 1
Fibonacci (n) = Fibonacci(n-1) + Fibonacci(n-2)
So, the first few numbers in this series will be: 1, 1, 2, 3, 5, 8, 13, 21... and so on!
void fib () {
fibresult[0] = 1;
fibresult[1] = 1;
for (int i = 2; i<n; i++)
fibresult[i] = fibresult[i-1] + fibresult[i-2];
}
Are we using a different recurrence relation in the two codes? No. Are we doing anything
different in the two codes? Yes.
In the recursive code, a lot of values are being recalculated multiple times. We could do
good with calculating each unique quantity only once. Take a look at the image to
understand that how certain values were being recalculated in the recursive way: