Implementation 1
Usage
I assume incorporation of my graph ADT
Weighted Undirected HashList
.Ensure to self identify some distinguished vertex as input for construction.
infile.txt
5
A : B-10, C-20
B : D-5, C-30
C : D-15, E-6
D : E-8
E :
ID WEIGHT PARENT_ID
A 0 null
B 10 A
D 5 B
E 8 D
C 6 E
min_weight: 29
Implementation
enum State{
UnVisited, Visited, Visiting
};
class Node {
public:
Node(pair<string, int> id) {
this->id = id.first;
weight = id.second;
state = UnVisited;
}
Node(string id) {
this->id = id;
state = UnVisited;
}
string id;
State state;
int weight;
bool operator==(const Node &other) const
{
return (id == other.id);
}
private:
int otherdata1;
};
struct MST_info {
Node id, parent_id;
int min_weight;
};
namespace std {
template <>
struct hash<Node>
{
std::size_t operator()(const Node& k) const
{
return (hash<string>()(k.id));
}
};
}
class Graph {
public:
Graph(const string &file_path) {
ifstream infile;
infile.open(file_path);
if (infile.good()) cout << "File opened successfully." << endl;
else cout << "File not opened successfully." << endl;
infile >> num_V;
graph.reserve(num_V);
string line;
getline(infile, line); // skip first
while (getline(infile, line)) {
pair<string, vector<pair<string, int>>> user_in = split(line);
add_hash_list(user_in);
}
add_hash_list_inv();
}
void erase(Node &n)
{
if (!exists(n)) { cout << "Node does not exist" << endl; return; }
graph.erase(n);
// removal should be everywhere in the graph, not just locally
// O(n)
for (auto &node : graph) {
if (node.second.find(n) != node.second.end()) {
node.second.erase(n);
}
}
}
std::unordered_set<Node>& findAdj(Node &n)
{
if (!exists(n)) {
cout << "Node does not exist" << endl;
return unordered_set<Node>();
}
return graph.find(n)->second;
}
void traverse_all() {
for (auto& i : graph) {
cout << setw(10) << i.first.id << "(" << i.first.state << ") : ";
traverse_set(i.second);
}
}
void traverse_set(const unordered_set<Node> &s)
{
for (auto &i : s)
cout << i.id << "-" << i.weight << ", ";
cout << endl;
}
unordered_map<string, MST_info> MST_prims(Node &start)
{
unordered_map<string, MST_info> isInMST;
isInMST.reserve(num_V);
auto comp = [](MST_info &left, MST_info &right) {
return left.min_weight > right.min_weight;
};
MST_info temp{ start, Node("null"), 0 };
priority_queue < MST_info, vector<MST_info>, decltype(comp)> pq(comp);
pq.push(temp);
while (!pq.empty()) {
// next minimum element
MST_info top = pq.top();
pq.pop();
// track visited
isInMST.insert({ top.id.id, top });
unordered_set<Node> temp_adj_map = graph.find(top.id)->second;
for (auto &node : temp_adj_map) {
// already added
if (isInMST.find(node.id) != isInMST.end()) continue;
// unvisited (weight = MAX_INT)
else if (node.state == UnVisited) {
const_cast<State&>(graph.find(node.id)->first.state) = Visited;
pq.push(MST_info{ graph.find(node.id)->first, top.id, node.weight });
}
else if (node.weight < isInMST.find(node.id)->second.min_weight) { // visited
pq.push(MST_info{ graph.find(node.id)->first, top.id, isInMST.find(node.id)->second.min_weight });
}
}
}
resetStates();
return isInMST;
}
private:
int num_V;
const bool directed = false;
unordered_map<Node, unordered_set<Node>> graph;
void add_hash_list(pair<string, vector<pair<string, int>>> &hl)
{
// insert front
Node adj_front(hl.first);
if (!exists(adj_front)) {
graph.insert(make_pair(adj_front, unordered_set<Node>()));
if (hl.second.empty()) return; // nothing to add
}
// transform to node object(s) and
// insert into the unordered set of the front element
for (pair<string, int> &s : hl.second) {
// insert link
Node dependency(s);
if (contains(graph.find(adj_front)->second, dependency))
cout << hl.first << " already contains " << s.first << endl;
else graph.find(adj_front)->second.insert(dependency);
// insert front_element (include empty links as well)
if (!exists(dependency))
graph.insert(make_pair(dependency, unordered_set<Node>()));
}
}
void add_hash_list_inv()
{
for (auto map : graph) {
for (auto node : map.second) {
if (contains(graph.find(node)->second, map.first))
cout << graph.find(node)->first.id << " already contains " << map.first.id << endl;
else {
pair<string, int> to_add = make_pair(map.first.id, graph.find(map.first)->second.find(node)->weight);
Node newIns(to_add);
graph.find(node)->second.insert(to_add);
}
}
}
}
void resetStates()
{
for (auto &i : graph)
const_cast<Node&>(i.first).state = UnVisited;
}
bool exists(const Node &n)
{
return (graph.find(n) != graph.end());
}
bool contains(const unordered_set<Node> &s, const Node &n)
{
return (s.find(n) != s.end());
}
// assumes validity of input file
pair<string, vector<pair<string, int>>> split(string &in, const char delimitor1 = ':',
const char delimitor2 = ',',
const char delimitor3 = '-')
{
int end = 0;
string adj_front, node_elements = "";
while (in[end++] != delimitor1);
adj_front = in.substr(0, end - 1);
sanatize(adj_front);
if (end < in.length())
node_elements = in.substr(end + 1, in.length() - end);
stringstream ss;
ss.str(node_elements);
string nodes, node;
vector<pair<string, int>> adj_nodes;
int i = 0;
while (getline(ss, nodes, delimitor2)) {
sanatize(nodes);
for (; i < nodes.size(); i++) {
if (nodes[i] == '-') {
node = nodes.substr(0, i);
break;
}
}
int i_digit_state = ++i;
while (isdigit(nodes[i])) i++;
int weight = stoi(nodes.substr(i_digit_state, i - i_digit_state));
adj_nodes.push_back(make_pair(node, weight));
i = 0;
}
return make_pair(adj_front, adj_nodes);
}
void sanatize(string &in)
{
in.erase(remove_if(in.begin(), in.end(), [](char c) {
return c == ' ';
}), in.end());
}
};
void test(Graph &g)
{
g.traverse_all();
cout << "\n\n";
unordered_map<string, MST_info> test = g.MST_prims(Node("A"));
cout << setw(10) << "ID" << setw(10) << "WEIGHT" << setw(10) << "PARENT_ID" << endl;
int acc_sum = 0;
for (auto i : test) {
cout << setw(10) << i.second.id.id <<
setw(10) << i.second.min_weight <<
setw(10) << i.second.parent_id.id << endl;
acc_sum += i.second.min_weight;
}
cout << endl;
cout << setw(10 + 10 + 10) << "min_weight: " << acc_sum << "\n\n";
}
int main()
{
const string filepath = "../infiles/graph.txt";
Graph friend_network(filepath);
test(friend_network);
}
Complex Example
12
vitaly : sam-5
dan : dav-2, lil-2, olga-1, dim-10
dav : dan-20, olga-23
olga : andrey-23
andrey : olga-32
sam : dan-15
jacob :
andrew : sam-7, nick-22
michael : vitaly-20
nick : andrew-19
lil : sam-18
dim : andrew-39
Graph Visualizer
dan -> dav [label="2"];
dan -> olga [label="1"];
dan -> lil [label="2"];
dan -> dim [label="10"];
dan -> sam [label="15"];
vitaly -> sam [label="5"];
vitaly -> michael [label="20"];
sam -> vitaly [label="5"];
sam -> dan [label="15"];
sam -> lil [label="18"];
sam -> andrew [label="7"];
dav -> dan [label="20"];
dav -> olga [label="23"];
nick -> andrew [label="19"];
lil -> sam [label="18"];
lil -> dan [label="2"];
andrey -> olga [label="32"];
olga -> andrey [label="23"];
olga -> dav [label="23"];
olga -> dan [label="1"];
dim -> andrew [label="39"];
dim -> dan [label="10"];
andrew -> sam [label="7"];
andrew -> nick [label="22"];
andrew -> dim [label="39"];
jacob
michael -> vitaly [label="20"];
Output
ID WEIGHT PARENT_ID
vitaly 5 sam
dan 0 null
andrey 23 olga
olga 1 dan
dav 2 dan
nick 22 andrew
lil 2 dan
dim 10 dan
sam 15 dan
andrew 7 sam
michael 20 vitaly
min_weight: 107
Discussion
isInMST
tracks the nodes in the graph that are currently in the MST. As per Prims greedy approach, once its in, it wont get out. It also helps avoid cycles.I first initialize the priority queue with the first 'distinguished' element, will follows from a
null
parent. This is used to basically bootstrap the priority queue with the first element.MST_info
gathers important data will become necessary later in identifying the tree.In the algorithm, we first pop the minimum most element, and add its adjacent elements into the queue if and only if
1) It is not already in the pq, in which case, it will be unvisited. (other like to use the 'infinity hack')
2) OR it is not in the pq, and its weight it smaller with the weight of the path of whatever relative node we are in.
Last updated
Was this helpful?