-
-
Notifications
You must be signed in to change notification settings - Fork 7.3k
/
boruvkas_minimum_spanning_tree.cpp
227 lines (201 loc) · 7.76 KB
/
boruvkas_minimum_spanning_tree.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/**
* @author [Jason Nardoni](https://github.com/JNardoni)
* @file
*
* @brief
* [Borůvkas Algorithm](https://en.wikipedia.org/wiki/Borůvka's_algorithm) to
*find the Minimum Spanning Tree
*
*
* @details
* Boruvka's algorithm is a greepy algorithm to find the MST by starting with
*small trees, and combining them to build bigger ones.
* 1. Creates a group for every vertex.
* 2. looks through each edge of every vertex for the smallest weight. Keeps
*track of the smallest edge for each of the current groups.
* 3. Combine each group with the group it shares its smallest edge, adding the
*smallest edge to the MST.
* 4. Repeat step 2-3 until all vertices are combined into a single group.
*
* It assumes that the graph is connected. Non-connected edges can be
*represented using 0 or INT_MAX
*
*/
#include <cassert> /// for assert
#include <climits> /// for INT_MAX
#include <iostream> /// for IO operations
#include <vector> /// for std::vector
/**
* @namespace greedy_algorithms
* @brief Greedy Algorithms
*/
namespace greedy_algorithms {
/**
* @namespace boruvkas_minimum_spanning_tree
* @brief Functions for the [Borůvkas
* Algorithm](https://en.wikipedia.org/wiki/Borůvka's_algorithm) implementation
*/
namespace boruvkas_minimum_spanning_tree {
/**
* @brief Recursively returns the vertex's parent at the root of the tree
* @param parent the array that will be checked
* @param v vertex to find parent of
* @returns the parent of the vertex
*/
int findParent(std::vector<std::pair<int, int>> parent, const int v) {
if (parent[v].first != v) {
parent[v].first = findParent(parent, parent[v].first);
}
return parent[v].first;
}
/**
* @brief the implementation of boruvka's algorithm
* @param adj a graph adjancency matrix stored as 2d vectors.
* @returns the MST as 2d vectors
*/
std::vector<std::vector<int>> boruvkas(std::vector<std::vector<int>> adj) {
size_t size = adj.size();
size_t total_groups = size;
if (size <= 1) {
return adj;
}
// Stores the current Minimum Spanning Tree. As groups are combined, they
// are added to the MST
std::vector<std::vector<int>> MST(size, std::vector<int>(size, INT_MAX));
for (int i = 0; i < size; i++) {
MST[i][i] = 0;
}
// Step 1: Create a group for each vertex
// Stores the parent of the vertex and its current depth, both initialized
// to 0
std::vector<std::pair<int, int>> parent(size, std::make_pair(0, 0));
for (int i = 0; i < size; i++) {
parent[i].first =
i; // Sets parent of each vertex to itself, depth remains 0
}
// Repeat until all are in a single group
while (total_groups > 1) {
std::vector<std::pair<int, int>> smallest_edge(
size, std::make_pair(-1, -1)); // Pairing: start node, end node
// Step 2: Look throught each vertex for its smallest edge, only using
// the right half of the adj matrix
for (int i = 0; i < size; i++) {
for (int j = i + 1; j < size; j++) {
if (adj[i][j] == INT_MAX || adj[i][j] == 0) { // No connection
continue;
}
// Finds the parents of the start and end points to make sure
// they arent in the same group
int parentA = findParent(parent, i);
int parentB = findParent(parent, j);
if (parentA != parentB) {
// Grabs the start and end points for the first groups
// current smallest edge
int start = smallest_edge[parentA].first;
int end = smallest_edge[parentA].second;
// If there is no current smallest edge, or the new edge is
// smaller, records the new smallest
if (start == -1 || adj[i][j] < adj[start][end]) {
smallest_edge[parentA].first = i;
smallest_edge[parentA].second = j;
}
// Does the same for the second group
start = smallest_edge[parentB].first;
end = smallest_edge[parentB].second;
if (start == -1 || adj[j][i] < adj[start][end]) {
smallest_edge[parentB].first = j;
smallest_edge[parentB].second = i;
}
}
}
}
// Step 3: Combine the groups based off their smallest edge
for (int i = 0; i < size; i++) {
// Makes sure the smallest edge exists
if (smallest_edge[i].first != -1) {
// Start and end points for the groups smallest edge
int start = smallest_edge[i].first;
int end = smallest_edge[i].second;
// Parents of the two groups - A is always itself
int parentA = i;
int parentB = findParent(parent, end);
// Makes sure the two nodes dont share the same parent. Would
// happen if the two groups have been
// merged previously through a common shortest edge
if (parentA == parentB) {
continue;
}
// Tries to balance the trees as much as possible as they are
// merged. The parent of the shallower
// tree will be pointed to the parent of the deeper tree.
if (parent[parentA].second < parent[parentB].second) {
parent[parentB].first = parentA; // New parent
parent[parentB].second++; // Increase depth
} else {
parent[parentA].first = parentB;
parent[parentA].second++;
}
// Add the connection to the MST, using both halves of the adj
// matrix
MST[start][end] = adj[start][end];
MST[end][start] = adj[end][start];
total_groups--; // one fewer group
}
}
}
return MST;
}
/**
* @brief counts the sum of edges in the given tree
* @param adj 2D vector adjacency matrix
* @returns the int size of the tree
*/
int test_findGraphSum(std::vector<std::vector<int>> adj) {
size_t size = adj.size();
int sum = 0;
// Moves through one side of the adj matrix, counting the sums of each edge
for (int i = 0; i < size; i++) {
for (int j = i + 1; j < size; j++) {
if (adj[i][j] < INT_MAX) {
sum += adj[i][j];
}
}
}
return sum;
}
} // namespace boruvkas_minimum_spanning_tree
} // namespace greedy_algorithms
/**
* @brief Self-test implementations
* @returns void
*/
static void tests() {
std::cout << "Starting tests...\n\n";
std::vector<std::vector<int>> graph = {
{0, 5, INT_MAX, 3, INT_MAX}, {5, 0, 2, INT_MAX, 5},
{INT_MAX, 2, 0, INT_MAX, 3}, {3, INT_MAX, INT_MAX, 0, INT_MAX},
{INT_MAX, 5, 3, INT_MAX, 0},
};
std::vector<std::vector<int>> MST =
greedy_algorithms::boruvkas_minimum_spanning_tree::boruvkas(graph);
assert(greedy_algorithms::boruvkas_minimum_spanning_tree::test_findGraphSum(
MST) == 13);
std::cout << "1st test passed!" << std::endl;
graph = {{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}};
MST = greedy_algorithms::boruvkas_minimum_spanning_tree::boruvkas(graph);
assert(greedy_algorithms::boruvkas_minimum_spanning_tree::test_findGraphSum(
MST) == 16);
std::cout << "2nd test passed!" << std::endl;
}
/**
* @brief Main function
* @returns 0 on exit
*/
int main() {
tests(); // run self-test implementations
return 0;
}