-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
263 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Es gibt zwei Arten, die `is_path` Methode zu implementieren, einmal mithilfe von Rekursion und einmal ohne. | ||
|
||
In `main.cpp` habe ich sie mit Rekursion implementiert, das heißt man schaut immer nur, ob der *nächste Schritt* entlang des Pfads gültig ist, und ruft dann die Funktion einfach nochmal mit einem kürzeren Pfad auf, so lange bis der Pfad nur noch einen Knoten enthält. Gelingt dies, so ist der Pfad korrekt. | ||
|
||
Wie immer bei Rekursion muss man darauf achten, dass man ihr einen Rekursions**anfang** und einen Rekursions**schritt** gibt, in diesem Fall prüfe ich für den Rekursionsanfang, ob die Liste schon nur noch eine Zahl enthält, und für den Rekursionsschritt verkleinere ich ggf. die Liste. So ist garantiert, dass die Funktion irgendwann an ein Ende gelangt. | ||
|
||
In `main2.cpp` ist die Variante ohne Rekursion implementiert, die man sicher etwas leichter verstehen kann. Hier wird der Pfad einfach von Anfang bis Ende geprüft, wobei man immer schauen muss, ob die Verbindungen zwischen zwei benachbarten Knoten im Pfad existieren. | ||
|
||
Die Funktion `to_dot` ist vermutlich nur für den Kontext da, so dass man z.B. bemerken kann, dass hinter dem `vertices` immer ein `->` steht, und man es somit auch im eigenen Code nicht vergisst. Außerdem wird ein `std::ofstream` verwendet, was ein Hinweis sein sollte, dass man `fstream` einbinden muss. Ansonsten kann man die Datei, die dadurch generiert wird, online unter http://webgraphviz.com in ein richtiges Bild eines Graphen verwandeln, wenn man nochmal per Hand prüfen will, ob die Pfade alle soweit möglich sind oder nicht. | ||
|
||
Die `assert` statements sind soweit recht willkürlich gesäht, da habe ich einfach mal geschaut, was denn so alles sinnvoll wäre. Ob das `assert(filename)` wirklich sinnvoll ist, darüber lässt sich streiten, aber die anderen ergeben durchaus Sinn. Man sollte aber schon darauf achten, dass auch Graphen mit 0 Knoten bzw. Knoten mit 0 Kanten durchaus vorkommen dürfen. | ||
|
||
In der Funktion `is_path` sind mir keine guten `assert`s eingefallen, da man hier ohnehin nur den Parameter `path` prüfen könnte ... und dieser Vektor dürfte durchaus auch 0 Zahlen beinhalten, was meiner Meinung nach ein valider Pfad wäre ... aber das ist in der Aufgabe auch nicht genau genug definiert. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
digraph { | ||
140669351893248[label="0 (124933377)"] | ||
140669351893280[label="1 (124933377)"] | ||
140669351893312[label="2 (124933377)"] | ||
140669351893344[label="3 (124933377)"] | ||
140669351893376[label="4 (124933377)"] | ||
140669351893408[label="5 (124933377)"] | ||
140669351893440[label="6 (124933377)"] | ||
140669351893472[label="7 (124933377)"] | ||
140669351893248->140669351893280 | ||
140669351893280->140669351893312 | ||
140669351893312->140669351893344 | ||
140669351893312->140669351893280 | ||
140669351893344->140669351893248 | ||
140669351893376->140669351893408 | ||
140669351893376->140669351893344 | ||
140669351893408->140669351893248 | ||
140669351893408->140669351893280 | ||
140669351893408->140669351893472 | ||
140669351893440->140669351893312 | ||
140669351893472->140669351893440 | ||
140669351893472->140669351893248 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
8 | ||
1 1 | ||
1 2 | ||
2 3 1 | ||
1 0 | ||
2 5 3 | ||
3 0 1 7 | ||
1 2 | ||
2 6 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
#include<vector> | ||
#include<iostream> | ||
#include<fstream> | ||
|
||
template<typename T> | ||
struct vertex { | ||
T data; | ||
std::vector<vertex*> neighbors; | ||
|
||
int add_neighbor(vertex* v) { | ||
assert(v); // assert that the pointer is valid (not null) | ||
neighbors.push_back(v); // add neighbor | ||
return neighbors.size(); // return new number of neighbors | ||
} | ||
}; | ||
|
||
|
||
template<typename T> | ||
struct graph { | ||
std::vector<vertex<T>>* vertices; | ||
|
||
graph(char* filename) { | ||
assert(filename); // assert that the pointer is valid (not null) | ||
vertices = new std::vector<vertex<int>>; // since "vertices" is a pointer, the vector is allocated on the heap | ||
|
||
std::ifstream ifs(filename); // open input file | ||
int n; ifs >> n; | ||
assert(n>=0); // no negative number of vertices | ||
|
||
// create all vertices | ||
for (int i=0;i<n;i++) { | ||
vertex<T> new_vertex; | ||
vertices->push_back(new_vertex); | ||
} | ||
|
||
// create all edges | ||
for (int i=0;i<n;i++) { | ||
int num_edges; ifs >> num_edges; | ||
assert(num_edges>=0); // no negative number of edges | ||
for (int j=0;j<num_edges;j++) { | ||
int target_index; ifs >> target_index; | ||
assert(target_index>=0 && target_index<vertices->size()); // assert that index is in valid range | ||
vertices->at(i).add_neighbor(&vertices->at(target_index)); // add_neighbor accepts pointers, therefore we must use "&" | ||
} | ||
} | ||
} | ||
|
||
~graph() { | ||
delete vertices; // delete the vector on the heap | ||
} | ||
|
||
void to_dot() const { | ||
std::ofstream ofs("graph.dot"); | ||
ofs << "digraph {" << std::endl; | ||
long i=0; | ||
for (auto v_it=vertices->begin(); | ||
v_it!=vertices->end();v_it++) | ||
ofs << (long)(&*v_it) << "[label=\"" << i++ << " (" | ||
<< v_it->data << ")" << "\"]" << std::endl; | ||
for (auto v_it=vertices->begin(); | ||
v_it!=vertices->end();v_it++) | ||
for (auto n_it=v_it->neighbors.begin(); | ||
n_it!=v_it->neighbors.end();n_it++) | ||
ofs << (long)(&*v_it) << "->" | ||
<< (long)(*n_it) << std::endl; | ||
ofs << "}" << std::endl; | ||
ofs.close(); | ||
} | ||
|
||
bool is_path(const std::vector<int>& path) { | ||
// if remaining path is empty ("path" only contains one number), we have arrived at our destination, and it is a valid path | ||
if (path.size() <= 1) return true; | ||
|
||
// if remaining path is not empty, check if next step is valid | ||
|
||
// create two helpful pointers to avoid a lot of clutter in the code | ||
vertex<T>* start_vertex = &vertices->at(path[0]); | ||
vertex<T>* next_vertex = &vertices->at(path[1]); | ||
|
||
// look in neighbor list of start vertex for next vertex along path | ||
bool found = false; | ||
for (int i=0;i<start_vertex->neighbors.size();i++) { | ||
if (start_vertex->neighbors[i] == next_vertex) { | ||
found = true; | ||
break; | ||
} | ||
} | ||
|
||
if (found) { | ||
// since the next vertex is valid, we can recursively check the remaining path | ||
// by applying "is_path" again with a shorter path | ||
std::vector<int> remaining_path; // since "path" is const, we have to make a copy | ||
for (int i=0;i<path.size()-1;i++) { | ||
remaining_path.push_back(path[i+1]); | ||
} | ||
return is_path(remaining_path); | ||
} | ||
|
||
// the next vertex was not found, therefore it is not a valid path | ||
return false; | ||
} | ||
}; | ||
|
||
|
||
int main(int c, char* v[]) { | ||
if (c!=2) throw 42; | ||
graph<int> g(v[1]); | ||
g.to_dot(); | ||
std::vector<int> p={4,3,0,1,2,3}; | ||
std::cout << g.is_path(p) << std::endl; | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
#include<vector> | ||
#include<iostream> | ||
#include<fstream> | ||
|
||
template<typename T> | ||
struct vertex { | ||
T data; | ||
std::vector<vertex*> neighbors; | ||
|
||
int add_neighbor(vertex* v) { | ||
assert(v); // assert that the pointer is valid (not null) | ||
neighbors.push_back(v); // add neighbor | ||
return neighbors.size(); // return new number of neighbors | ||
} | ||
}; | ||
|
||
|
||
template<typename T> | ||
struct graph { | ||
std::vector<vertex<T>>* vertices; | ||
|
||
graph(char* filename) { | ||
assert(filename); // assert that the pointer is valid (not null) | ||
vertices = new std::vector<vertex<int>>; // since "vertices" is a pointer, the vector is allocated on the heap | ||
|
||
std::ifstream ifs(filename); // open input file | ||
int n; ifs >> n; | ||
assert(n>=0); // no negative number of vertices | ||
|
||
// create all vertices | ||
for (int i=0;i<n;i++) { | ||
vertex<T> new_vertex; | ||
vertices->push_back(new_vertex); | ||
} | ||
|
||
// create all edges | ||
for (int i=0;i<n;i++) { | ||
int num_edges; ifs >> num_edges; | ||
assert(num_edges>=0); // no negative number of edges | ||
for (int j=0;j<num_edges;j++) { | ||
int target_index; ifs >> target_index; | ||
assert(target_index>=0 && target_index<vertices->size()); // assert that index is in valid range | ||
vertices->at(i).add_neighbor(&vertices->at(target_index)); // add_neighbor accepts pointers, therefore we must use "&" | ||
} | ||
} | ||
} | ||
|
||
~graph() { | ||
delete vertices; // delete the vector on the heap | ||
} | ||
|
||
void to_dot() const { | ||
std::ofstream ofs("graph.dot"); | ||
ofs << "digraph {" << std::endl; | ||
long i=0; | ||
for (auto v_it=vertices->begin(); | ||
v_it!=vertices->end();v_it++) | ||
ofs << (long)(&*v_it) << "[label=\"" << i++ << " (" | ||
<< v_it->data << ")" << "\"]" << std::endl; | ||
for (auto v_it=vertices->begin(); | ||
v_it!=vertices->end();v_it++) | ||
for (auto n_it=v_it->neighbors.begin(); | ||
n_it!=v_it->neighbors.end();n_it++) | ||
ofs << (long)(&*v_it) << "->" | ||
<< (long)(*n_it) << std::endl; | ||
ofs << "}" << std::endl; | ||
ofs.close(); | ||
} | ||
|
||
bool is_path(const std::vector<int>& path) { | ||
// if path is empty ("path" only contains one number), the path is technically valid | ||
if (path.size() <= 1) return true; | ||
|
||
// otherwise we need to check all intermediate steps between two vertices along the path | ||
for (int i=0;i<path.size()-1;i++) { | ||
// create two helpful pointers to avoid a lot of clutter in the code | ||
vertex<T>* current_index = &vertices->at(path[i]); | ||
vertex<T>* next_vertex = &vertices->at(path[i+1]); | ||
|
||
// look in neighbor list of start vertex for next vertex along path | ||
bool found = false; | ||
for (int i=0;i<current_index->neighbors.size();i++) { | ||
if (current_index->neighbors[i] == next_vertex) { | ||
found = true; | ||
break; | ||
} | ||
} | ||
|
||
// if at any point along the path, the next vertex can not be reached, | ||
// the entire path immediately becomes invalid and we can return false | ||
if (!found) return false; | ||
} | ||
|
||
return true; | ||
} | ||
}; | ||
|
||
|
||
int main(int c, char* v[]) { | ||
if (c!=2) throw 42; | ||
graph<int> g(v[1]); | ||
g.to_dot(); | ||
std::vector<int> p={4,3,0,1,2,3}; | ||
std::cout << g.is_path(p) << std::endl; | ||
return 0; | ||
} |