You are on page 1of 9

Prim’s Algorithm for a growing

MST

Erno Ledder, s4548566


Véronne Reinders, s4603478
Teachers: Paul Kamsteeg, Johan Kwisthout, Janos Sarbo

Artificial Intelligence
Radboud University
Nijmegen, the Netherlands

1 Abstract
Prim’s algorithm an algorithm to find the minimal spanning tree in a graph
with n nodes and m vertices. We research whether the runtime increases
when the weights change, or when the amount of nodes/vertices increase.
This was researched by first creating our own code for growing a MST with
Prim’s algorithm in java and implementing different kinds of graphs. We
discuss our thought process, our implementations and why certain functions
were chosen. In the end we look over the collected data and discuss what we
can conclude from the collected data.

1
2 Introduction
Implementing Prim’s algorithm for growing a Minimal Spanning Tree (MST)
in Java can only be done when you know how Prim’s algorithm works. Prim’s
algorithm is a greedy algorithm that adds the currently best-looking edge
one-by-one. This process of adding the best-looking edge at every instance
is an iterative process. The algorithm adds a safe edge, which is an edge in
graph G such that, when added to subset A (of the final MST) A remains a
subset of MST. In the iterative process that is Prim’s algorithm, a safe edge
must exist at every iteration.

Figure 1: The original graph.

If Prim’s algorithm grows a MST, it starts with a single node at the root.
By finding the best-looking safe edge that is connect to the current node, the
algorithm finds a ‘new’ node/vertex. Looking at figure 1, you can see that
every vertex has a value v.key where v is a vertex. For all the vertices that
are not the root goes v.key = ∞ and for the root goes root.key = 0. You
want to update v.key if a vertex is directly connected to your current vertex
and thus considered to add to the MST.

Figure 2: Step 1 of Prim’s algorithm.

In figure 2 the values v.key of the vertices b and h are updated. When using
Prim’s algorithm you want to add the safe edge to the MST that connects
the current vertex to the vertex with the smallest v.key. The safe edges that
are added to the MST will be changed to a blue line in the original graph
and the added vertices will turn black. Vertices that are red delineated are

2
neighbouring vertices. So vertex b and h are neighbouring vertices of vertex
a. As shown in figure 3 the safe edge that is added to the MST is the edge
that connects vertex a with vertex b.

Figure 3: Step 2, adding the safe edge.

Recursively searching the best-looking edges one-by-one gives you a Minimal


Spanning Tree that is shown in figure 4.

Figure 4: The Minimal Spanning Tree.

The MST in figure 4 is not the only MST that can be found in this graph.
There is at least one other Minimal Spanning Tree that can be computed
by adding the edge between a and h as the second step/edge. On the basis
of Prim’s algorithm it is possible to compute all Minimal Spanning Trees.
Computing these trees can be done a lot faster by a computer program than
by a human. And that is why we are going to implement Prim’s algorithm
for growing a Minimal Spanning Tree (MST) in Java.

3 Methods
Provided in the lecture slides was the pseudo code which was useful for
implementing Prim’s algorithm in Java. As always start implementing a
program by creating the needed classes. We decided on making a class that
had the attributes of a node/vertex, a class that had the attributes of an
edge and a class that made the graphs out of the nodes and edges. The class
Node contains the name, the parent (so were the algorithm was before him)
and a weight/key value of the vertex.

3
public class Node {

private String name;


private Node parent;
private int key;

public Node(String name, Node parent, int key){


this.name=name;
this.key = key;
this.parent = parent;
}

The class Edge contains two nodes, node a and node b. The edge connects
these nodes and every edge has a weight.
public class Edge {
private int weight;
private Node a;
private Node b;

public Edge(int weight, Node a,Node b){


this.weight = weight;
this.a = a;
this.b = b;
}

The class Graph combines edges and nodes by initializing the nodes and
making edges consisting of two nodes, that where already initialized, and the
weight of the edge.

While reading the pseudo code it became clear that the v.key values of two
nodes needed to be compared. To compare these values a compare class had
to be made. The compare function in this class overrides the comparator
function and returns whether the weight of a given vertex is bigger, smaller
or equal to the vertex it is compared with.

4
public class CompareWeights implements Comparator<Node> {

@Override
public int compare(Node a, Node b) {
if (a.getKey() < b.getKey())
return -1;
if (a.getKey() > b.getKey())
return 1;
return 0;
}
}

After computing these basic classes, the basis for the algorithm had been
made. The easy part was done now the real problem began when imple-
menting Prim’s algorithm. The algorithm was built up function by function
because of this it was doable to program the algorithm. One mistake that was
made was not giving the algorithm a start node but before the first function
was implemented that mistake was already fixed. The first function that was
implemented was one that added the possible nodes into a priority-queue.
private void makeTree(Graph graph, ArrayList<Node> nodesList,
Comparator<Node> comparator) {
PriorityQueue<Node> quw = new PriorityQueue<Node>(nodesList.size(),
comparator);

for (int i = 0; i < nodesList.size(); i++) {


iterations = iterations + 1;
quw.add(nodesList.get(i));
}

The priority-queue is quite important for the algorithm because, by using


the comparator function, it decides which node is the best to visit next. All
the nodes are added to the queue, all that has to be done now is add the
vertices in the right order to the tree. This is done by looking a the ’cheapest’
connection between two nodes, the best-looking edge. After finding the best
connection for a node this connection is added to a list and the priority-queue
is updated by first removing the nodes from the queue and then adding them
again. This is done because we want to make sure that the compare function
is activated and the priority-queue has all the nodes in the right order.

5
private void updateTree(Graph graph, PriorityQueue<Node> quw, Node
current, ArrayList<Node> vertexN) {
for (Node n : vertexN) {
iterations = iterations + 1;
if (quw.contains(n) && graph.getEdge(current, n).getWeight() <
n.getKey()) {
n.makeParent(current);
n.setKey(graph.getEdge(current, n).getWeight());
}
}
}

After the queue is updated the algorithm starts at the beginning by once more
finding the best edge for the node that is at the top of the priority-queue.
This iterative process is repeated until the queue is completely empty.
while (quw.size() != 0) {
iterations = iterations + 1;
Node current = quw.poll();
ArrayList<Node> vertexN = graph.getvertex(current);

updateTree(graph, quw, current, vertexN);

updateQuw(quw);

If the Minimal Spanning Tree is created the only function left to implement
is a function that prints the nodes and its corresponding edge and weight.
We also want to compute the runtime. The runtime will be needed for the
experiments we are going to execute later on.
private void printTree(ArrayList<Edge> result) {
for (Edge e : result) {
iterations = iterations + 1;
System.out.println("Edge between " + e.getNodea().getString()+ "
and " + e.getNodeb().getString() + " with weight "+
e.getWeight());
}
System.out.println(iterations);
long endTime = System.nanoTime();
long time = endTime - startTime;
System.out.println(time/100);
}

The function System.nanoTime(); is used at the beginning of the algorithm


and at the end of the algorithm. By comparing the these two different times

6
the total processing time can be calculated. The reason we choice this kind
of time measurement instead of calculating the amount of edges added or
amount of times spent in a certain part of the program, is because the runtime
is way more secure. Upfront we did not know whether the amount of edges
has influences on the processing time or if bigger weights take longer to
process. This is why it is not smart to just count edges or any other kind of
time measurement like that.

4 Results
What we want to check with the Prim’s algorithm we have implemented, is
if the runtime changes when:

• The number of vertices in the graph increases.

• All weights are equal or different.

• The weights of the edges are small of large weights.

• The range between the weights is small or large.

Table 1: The results for different experiments and different graphs.


Average runtime
Graph Number of nodes Number of edges Weight values
in microseconde
1 2 1 Small weights 34
2 2 1 Same small weight 34
3 2 1 Large weights 36
4 2 1 Wide range Not possible
5 4 6 Small weights 60
6 4 6 Same small weight 59
7 4 6 Large weights 70
8 4 6 Wide range 63
9 9 14 Small weights 108
10 9 14 Same small weight 96
11 9 14 Large weights 155
12 9 14 Wide range 111
13 15 20 Small weights 186
14 15 20 Same small weight 188
15 15 20 Large weights 248
16 15 20 Wide range 179

7
To check if the runtime changes when the number of vertices in the graph
increases, we computed different graphs with different number of vertices.
We created 4 different graphs, as you can see in table 1 the runtime does
increase when the number of vertices increases. This increase can also be
explained by the increase in the number of edges. The increase in runtime
when the number of vertices and edges increases is a logical consequence,
because how bigger the graph, the more Minimal Spanning Trees can be
computed.

An interesting result we came across was that the difference in weights, so


whether all weights are equal or different, did not influence the runtime. The
average runtime over 500 runs was not significantly different.

The magnitude of the weights does have an influence on the runtime. Espe-
cially if the graph consists of a big amount of vertices and edges. As visual-
ized in table 1, you can see that when the graph gets bigger, the difference
in runtime between small and large weights gets bigger.

There is one difference that we still want to check and that is if the range
in edge weights does have an effect on the runtime. For the first graph we
can not check this, because this graph has only one edge. But for the other
graph goes that the range does not really matter. As for the difference in
weights, there is a difference, but this difference is not significantly big.

Because we want accurate results we decided to grow the Minimal Spanning


Tree of every graph 500 times and take the average runtime of these 500
runtimes. To compute this average we adjusted the code a bit to make this
easier, otherwise we had to run the implemented algorithm 500 times by
hand.
private void printTree(ArrayList<Edge> result) {
long [] z = new long[500];
long p = 0;
Graph tree = new Graph();
Node start = tree.start();
for(int i = 0; i<500; i++){
Prim prim = new Prim(start, tree);
z[i] = prim.getTime()/1000;
}
for(long q:z){
p = p + q;
}
System.out.println(p/500);

8
}

What is interesting about the results that we found is that the difference in
runtime is not as we expected it to be. Per graph it is shown in table 1 that
the difference in runtime per experiment is not nameable. The biggest effect
on the runtime is the size of the graph, but the difference in weights has a
smaller effect on the runtime than we would have expected.

5 Discussion
So after implementing and testing Prim’s algorithm we can say that the
program is quite steady. When the amount of vertices or nodes increases the
time increases too. This is quite logical because the algorithm has to check
more. The fact that running time gets bigger when the graph gets bigger
is also something that holds for other search algorithms so that was not a
suprise. Bigger weights make the algorithm go significantly slower. This
was something we did not expect. It was a big finding that the big weights
made the algorithm that much slower. The opposite holds for when all the
weights are the same, the running time is bit lower compared to a difference
in weights but not significantly lower. In the end the implemented Prim’s
algorithm does the job, it does not grow in time that much when the amount
of vertices or nodes increases which is important for a search algorithm.

You might also like