I was cloning a graph (deep copy) and the only problem in my code is in the BFS()
function inside the if
statement, when I’m updating the visited
hashmap, it’s not updating.
Node* cloneGraph(Node* node){
Node* r_head = node;
if(node == NULL){
return r_head;
}
std::unordered_map<Node*,Node*> indexer;
r_head = new Node(r_head->val,r_head->neighbors);
indexer.insert({node,r_head});
BFS(r_head,indexer);
return r_head;
}
void BFS(Node* node,std::unordered_map<Node*,Node*> indexer){
int temp_val;
std::unordered_map<int,bool> visited;
std::vector<Node*> output_nodes;
output_nodes.push_back(node);
visited.insert(std::make_pair(node->val,1)); // only this one is updated
Node* temp_node;
Node* temp_node_2;
for(int i = 0 ; i < output_nodes.size() ; i++){
for(int j = 0 ; j < output_nodes.at(i)->neighbors.size(); j++){
temp_node = output_nodes.at(i)->neighbors[j];
if(!visited[temp_node->val]){
temp_node_2 = new Node(temp_node->val,temp_node->neighbors);
indexer.insert({temp_node,temp_node_2});
output_nodes.push_back(temp_node_2);
visited.insert(std::make_pair(temp_val,true)); //does not get updated
//printf("visited %dn", visited[2]);
}
}
}
new_neigbors(indexer,node,output_nodes);
}
void new_neigbors(std::unordered_map<Node*,Node*> indexer ,Node* node, std::vector<Node*> output_nodes){
printf("output node size in new_neighbor %dn",output_nodes.size());
for(int i = 0 ; i < output_nodes.size(); i ++){
for(int j = 0 ; j < output_nodes.at(i)->neighbors.size(); j++){
output_nodes.at(i)->neighbors.at(j) = indexer[output_nodes.at(i)->neighbors.at(j)];
printf(" inside new _neignors wooo j %dn",j);
}
}
}
9
You seem to be missing code to assign a value to temp_val
.
My suggestion is to change this line:
if(!visited[temp_node->val]){
into two lines:
temp_val = temp_node->val;
if(!visited[temp_val]){
That way, you will not be trying to read from an uninitialized value when creating your new entry:
visited.insert(std::make_pair(temp_val,true)); //...
However, as user4581301 pointed out in comments, this insertion fails. When you use the []
accessor to test if you have visited the node, you have generated an entry in visited
for that key. So, you will not be able to insert the new pair.
You could just assign true
, though.
visited[temp_val] = true; //...
2
The visited[temp_node->val]
in if(!visited[temp_node->val])
Automatically adds temp_node->val
:false
to visited
and makes the later insert` fail because the node is already present.
Simple example:
#include <iostream>
#include <unordered_map>
int main()
{
std::unordered_map<int, bool> visited;
if (!visited[42]) // auto inserts {42,false}
{
visited.insert({42,true}); //42 already in map, insertion refused.
}
std::cout << visited[42] <<std::endl;
}
Outputs 0.
A unordered_set
should work better for you here as presence, or lack of it, in the set implies the boolean output.
#include <iostream>
#include <unordered_set>
int main()
{
std::unordered_set<int> visited;
if (!visited.contains(42))
{
visited.insert(42);
}
std::cout << visited.contains(42) <<std::endl;
}
Outputs 1.
Side note: std::unordered_set::contains
was added in C++20. Before that, use find
and compare the result against visited
‘s end
iterator:
#include <iostream>
#include <unordered_set>
int main()
{
std::unordered_set<int> visited;
if (visited.find(42) == visited.end())
{
visited.insert(42);
}
std::cout << visited.contains(42) <<std::endl;
}
You must also address the uninitialized temp_val
pointed out by jhx’s answer.
Replace
std::unordered_map<int,bool> visited;
with
std::unordered_set<int> visited;
and then you can
temp_node = output_nodes.at(i)->neighbors[j];
temp_val = temp_node->val;
if(!visited[temp_val].contains()){
temp_node_2 = new Node(temp_node->val,temp_node->neighbors);
indexer.insert({temp_node,temp_node_2});
output_nodes.push_back(temp_node_2);
visited.insert(temp_val);
}
And at some point in the future you need to address the pass-by-value problem Yakk highlights.
2
void BFS(Node* node,std::unordered_map<Node*,Node*> indexer){
when you take an object (like an unordered map) by value, you get a local copy of it. You appear to think that modifying indexer
within BFS
will change the passed in indexer
when you called it. This won’t happen.
The easiest way to detect such errors is to mark your function arguments as const
on the right like this:
void BFS(Node*const node,std::unordered_map<Node*,Node*>const indexer){
the compiler will now generate errors whenever you try to modify them. You do this on the right so that if they are pointers, it is the pointer and not the pointed to that is const
. (For reference parameters, const
on the right generates an error: this is good, because you don’t want the const
in that case).
Do this to every function in this example. Every line that generates an error is you misunderstanding what “passing an object by value” means.
It is possible that you intend to actually take the objects by &
reference; I do not know. It is a very bad habit to just take every parameter by reference, however.
The core issue is you need to understand that objects in C++ have actual values, and when you create a variable of an object type you get a type that is an actual value, not a reference to that object on the heap somewhere.
Keeping track of where objects are and who owns them is an extra task that C++ places on programmers. One way we avoid the problem is that we write “pure functions” that take values in and return modified versions, and we std::move
stuff in and assign the results.
Your next problem is here:
if(!visited[temp_node->val]){
this causes visited
to have a false
value under temp_node->val
if it didn’t exist prior to this line. []
creates with default value in a std::map
and std::unordered_map
, here false
, if it is missing.
There are two problems here:
visited.insert(std::make_pair(temp_val,true)); //does not
first, temp_val
is never given a value. You could just delete it and use temp_node->val
, or you could assign it the value earlier; either works.
The second is that insert
does not replace a value that already exists. It returns a std::pair<iterator, bool>
where the 2nd parameter says if the insert actually replaced something.
Normally this isn’t a problem, but earlier use of []
caused the element to be created
.insert_or_assign
does what you want; alternatively, you can just do visited[key] = true
.
The use of a map from a key to a bool is often a mistake. Using a unordered_set
can help; use visited.count(key) > 0
or visited.contains(key)
to detect if something is in the set, and use insert (without the pair/bool) to add to the set: visited.insert(key)
.
This is a bit less error prone, and doesn’t waste memory on storing not-visited elements.
1