You are on page 1of 21

Advanced Algorithms Date: 31-03-2021

Name: M PUNEETH Reg No: 19MID0069

DIGITAL ASSIGNMENT - 1

1.Write a program to find the shortest path in a graph using either Prim’s
Algorithm or Kruskal’s Algorithm.

Prim’s Algorithm:

#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#define V 5

int minKey(int key[], bool mstSet[])


{
int min = INT_MAX, min_index;

for (int v = 0; v < V; v++)


if (mstSet[v] == false && key[v] < min)
min = key[v], min_index = v;

return min_index;
}
int printMST(int parent[], int graph[V][V])
{
printf("Edge \tWeight\n");
for (int i = 1; i < V; i++)
printf("%d - %d \t%d \n", parent[i], i, graph[i][parent[i]]);
}

void primMST(int graph[V][V])


{

int parent[V];
int key[V];
bool mstSet[V];

for (int i = 0; i < V; i++)


key[i] = INT_MAX, mstSet[i] = false;

key[0] = 0;
parent[0] = -1;

for (int count = 0; count < V - 1; count++) {


int u = minKey(key, mstSet);
mstSet[u] = true;
for (int v = 0; v < V; v++)

if (graph[u][v] && mstSet[v] == false && graph[u][v] < key[v])


parent[v] = u, key[v] = graph[u][v];
}

printMST(parent, graph);
}

int main()
{
int graph[V][V] = { { 0, 2, 0, 6, 0 },
{ 2, 0, 3, 8, 5 },
{ 0, 3, 0, 0, 7 },
{ 6, 8, 0, 0, 9 },
{ 0, 5, 7, 9, 0 } };

primMST(graph);

return 0;
}
Output:

2. Write a program to create a binary search tree. Provide facilities to insert,


delete, update and search nodes in the tree.
#include<stdio.h>
#include<stdlib.h>
struct node
{
int info;
struct node*left;
struct node*right;
};
typedef struct node BST;
BST *LOC, *PAR;
void search(BST *root, int item)
{
BST *save,*ptr;
if (root == NULL)
{
LOC = NULL;
PAR=NULL;
}
if (item == root -> info)
{
LOC = root;
PAR = NULL;
return;
}
if (item < root->info)
{
save = root;
ptr = root->left;
}
else
{
save = root;
ptr = root -> right;
}
while( ptr != NULL)
{
if (ptr -> info == item)
{
LOC = ptr;
PAR = save;
return;
}
if(item < ptr->info)
{
save = ptr;
ptr = ptr->left;
}
else
{
save = ptr;
ptr = ptr->right;
}
}
LOC = NULL;
PAR = save;
return;
}
struct node* findmin(struct node*r)
{
if (r == NULL)
return NULL;
else if (r->left!=NULL)
return findmin(r->left);
else if (r->left == NULL)
return r;
}
struct node*insert(struct node*r, int x)
{
if (r == NULL)
{
r = (struct node*)malloc(sizeof(struct node));
r->info = x;
r->left = r->right = NULL;
return r;
}
else if (x < r->info)
r->left = insert(r->left, x);
else if (x > r->info)
r->right = insert(r->right, x);
return r;
}
struct node* del(struct node*r, int x)
{
struct node *t;
if(r == NULL)
printf("\nElement not found");
else if (x < r->info)
r->left = del(r->left, x);
else if (x > r->info)
r->right = del(r->right, x);
else if ((r->left != NULL) && (r->right != NULL))
{
t = findmin(r->right);
r->info = t->info;
r->right = del(r->right, r->info);
}
else
{
t = r;
if (r->left == NULL)
r = r->right;
else if (r->right == NULL)
r = r->left;
free(t);
}
return r;
}
int main()
{
struct node* root = NULL;
int x, c = 1, z;
int element;
char ch;
printf("\nEnter an element: ");
scanf("%d", &x);
root = insert(root, x);
printf("\nDo you want to enter another element :y or n");
scanf(" %c",&ch);
while (ch == 'y')
{
printf("\nEnter an element:");
scanf("%d", &x);
root = insert(root,x);
printf("\nPress y or n to insert another element: y or n: ");
scanf(" %c", &ch);
}
while(1)
{
printf("\n1 Insert an element ");
printf("\n2 Delete an element");
printf("\n3 Search for an element ");
printf("\n4 Exit ");
printf("\nEnter your choice: ");
scanf("%d", &c);
switch(c)
{
case 1:
printf("\nEnter the item:");
scanf("%d", &z);
root = insert(root,z);
break;
case 2:
printf("\nEnter the info to be deleted:");
scanf("%d", &z);
root = del(root, z);
break;
case 3:
printf("\nEnter element to be searched: ");
scanf("%d", &element);
search(root, element);
if(LOC != NULL)
printf("\n%d Found in Binary Search Tree !!\n",element);
else
printf("\nIt is not present in Binary Search Tree\n");
break;
case 4:
printf("\nExiting...");
return;
default:
printf("Enter a valid choice: ");
}
}
return 0;
}

Output:

3. Write algorithms to implement


A) Depth First Search Algorithm

Depth First Search (DFS) Algorithm


Depth first search (DFS) algorithm starts with the initial node of the graph G,
and then goes to deeper and deeper until we find the goal node or the node
which has no children. The algorithm, then backtracks from the dead end
towards the most recent node that is yet to be completely unexplored.

The data structure which is being used in DFS is stack. The process is similar to
BFS algorithm. In DFS, the edges that leads to an unvisited node are called
discovery edges while the edges that leads to an already visited node are called
block edges.
Algorithm
o Step 1: SET STATUS = 1 (ready state) for each node in G
o Step 2: Push the starting node A on the stack and set its STATUS = 2
(waiting state)
o Step 3: Repeat Steps 4 and 5 until STACK is empty
o Step 4: Pop the top node N. Process it and set its STATUS = 3 (processed
state)
o Step 5: Push on the stack all the neighbours of N that are in the ready
state (whose STATUS = 1) and set their
STATUS = 2 (waiting state)
[END OF LOOP]
o Step 6: EXIT

B) Breadth First Search Algorithm

Breadth First Search (BFS) Algorithm


Breadth first search is a graph traversal algorithm that starts traversing the
graph from root node and explores all the neighbouring nodes. Then, it selects
the nearest node and explore all the unexplored nodes. The algorithm follows
the same process for each of the nearest node until it finds the goal.

The algorithm of breadth first search is given below. The algorithm starts with
examining the node A and all of its neighbours. In the next step, the
neighbours of the nearest node of A are explored and process continues in the
further steps. The algorithm explores all neighbours of all the nodes and
ensures that each node is visited exactly once and no node is visited twice.

Algorithm
o Step 1: SET STATUS = 1 (ready state)
for each node in G
o Step 2: Enqueue the starting node A
and set its STATUS = 2
(waiting state)
o Step 3: Repeat Steps 4 and 5 until
QUEUE is empty
o Step 4: Dequeue a node N. Process it
and set its STATUS = 3
(processed state).
o Step 5: Enqueue all the neighbours of
N that are in the ready state
(whose STATUS = 1) and set
their STATUS = 2
(waiting state)
[END OF LOOP]
o Step 6: EXIT

4. Compare Ford-Fulkerson Method and Edmonds-Karp algorithm and explain


them.

Comparison:

• Edmonds-Karp is a specialisation/elaboration of Ford-Fulkerson, so any


bound for the latter also applies to the former.
• In other words, Edmonds-Karp is O(|E|min(fmax,|V||E|))
O(|E|min(fmax,|V||E|)) time (and writing it this way does add
information, since fmaxfmax can be much smaller than |V||E||V||E| )
This is the only time when you might otherwise consider using some
other variant of FordFulkerson in preference to Edmonds-Karp.
• The above does not imply that Edmonds-Karp is faster than a particular
(say, DFS-based) variant of Ford-Fulkerson on any particular instance.
Ford–Fulkerson algorithm isn't guaranteed to terminate, it may run
forever in certain cases and its run-time (Complexity) is also depended
on the max flow O(ME) where M is the Max flow Edmonds Karp
algorithm guarantees termination and removes the max flow
dependency O(VE2).
• Ford-Fulkerson is a Method and Edmonds-Karp is an algorithm.
• BFS and DFS have the same runtime, but DFS only promises to find a
path from source to sink in the residual graph -- not necessarily a
shortest possible such path, which BFS does promise.

Explanation:

Ford-Fulkerson method

Let's define one more thing. A residual capacity of an directed edge is the
capacity minus the flow. It should be noted that if there is a flow along some
directed edge (u,v)(u,v), than the reversed edge has capacity 0 and we can
define the flow of it as f((v,u))=−f((u,v))f((v,u))=−f((u,v)). This also defines the
residual capacity for all reversed edges. From all these edges we can create
a residual network, which is just a network with the same vertices and same
edges, but we use the residual capacities as capacities.

The Ford-Fulkerson method works as follows. First we set the flow of each
edge to zero. Then we look for an augmenting path from ss to tt. An
augmenting path is simple path in the residual graph, i.e. along the edges
whose residual capacity is positive. Is such a path is found, then we can add
increase the flow along these edges. We keep on searching for augmenting
paths and increasing the flow. Once there doesn't exists an augmenting path
any more, the flow is maximal.

Let us specify in more detail, what increasing the flow along an augmenting
path means. Let CC be the smallest residual capacity of the edges in the path.
Then we increase the flow in the following way: we
update f((u,v)) += Cf((u,v)) += C and f((v,u)) -= Cf((v,u)) -= C for every
edge (u,v)(u,v) in the path.

Here is an example to demonstrate the method. We use the same flow


network as above. Initially we start with a flow of 0.
We can find the path s−A−B−ts−A−B−t with the residual capacities 7, 5 and 8.
Their minimum is 5, therefore we can increase the flow along this path by 5.
This gives a flow of 5 for the network.
Again we look for an augmenting path, this time we
find s−D−A−C−ts−D−A−C−t with the residual capacities 4, 3, 3 and 5. Therefore
we can increase the flow by 3 and we get a flow of 8 for the network.
This time we find the path s−D−C−B−ts−D−C−B−t with the residual capacities 1,
2, 3 and 3, and we increase by 1.
This time we find the augmenting path s−A−D−C−ts−A−D−C−t with the residual
capacities 2, 3, 1 and 2. We can increase by 1. But this path is very interesting.
It includes the reversed edge (A,D)(A,D). In the original flow network we are
not allowed to send any flow from AA to DD. But because we already have a
flow of 3 from DD to AA this is possible. The intuition of it is the following:
Instead of sending a flow of 3 from DD to AA, we only send 2 and compensate
this by sending an additional flow of 1 from ss to AA, which allows us to send
an additional flow of 1 along the path D−C−tD−C−t.
Now it is impossible to find an augmenting path between ss and tt, therefore
this flow of 1010 is the maximal possible. We have found the maximal flow.

It should be noted, that the Ford-Fulkerson method doesn't specify a method


of finding the augmenting path. Possible approaches are
using DFS or BFS which both work in O(E)O(E). If all capacities of the network
are integers, then for each augmenting path the flow of the network increases
by at least 1 (for more details see Integral flow theorem). Therefore the
complexity of Ford-Fulkerson is O(EF)O(EF), where FF is the maximal flow of
the network. In case of rational capacities, the algorithm will also terminate,
but the complexity is not bounded. In case of irrational capacities, the
algorithm might never terminate, and might not even converge to the maximal
flow.

Edmonds-Karp algorithm
Edmonds-Karp algorithm is just an implementation of the Ford-Fulkerson
method that uses BFS for finding augmenting paths. The algorithm was first
published by Yefim Dinitz in 1970, and later independently published by Jack
Edmonds and Richard Karp in 1972.

The complexity can be given independently of the maximal flow. The algorithm
runs in O(VE2)O(VE2) time, even for irrational capacities. The intuition is, that
every time we find an augmenting path one of the edges becomes saturated,
and the distance from the edge to ss will be longer, if it appears later again in
an augmenting path. And the length of a simple paths is bounded by VV.

Implementation

The matrix capacity stores the capacity for every pair of vertices. adj is the
adjacency list of the undirected graph, since we have also to use the reversed
of directed edges when we are looking for augmenting paths.

The function maxflow will return the value of the maximal flow. During the
algorithm the matrix capacity will actually store the residual capacity of the
network. The value of the flow in each edge will actually not be stored, but it is
easy to extend the implementation - by using an additional matrix - to also
store the flow and return it.

int n;

vector<vector<int>> capacity;

vector<vector<int>> adj;

int bfs(int s, int t, vector<int>& parent) {

fill(parent.begin(), parent.end(), -1);

parent[s] = -2;

queue<pair<int, int>> q;

q.push({s, INF});
while (!q.empty()) {

int cur = q.front().first;

int flow = q.front().second;

q.pop();

for (int next : adj[cur]) {

if (parent[next] == -1 && capacity[cur][next]) {

parent[next] = cur;

int new_flow = min(flow, capacity[cur][next]);

if (next == t)

return new_flow;

q.push({next, new_flow});

return 0;

int maxflow(int s, int t) {

int flow = 0;

vector<int> parent(n);

int new_flow;
while (new_flow = bfs(s, t, parent)) {

flow += new_flow;

int cur = t;

while (cur != s) {

int prev = parent[cur];

capacity[prev][cur] -= new_flow;

capacity[cur][prev] += new_flow;

cur = prev;

return flow;

You might also like