Skip to content

Commit

Permalink
ws1819_1/4
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulOxxx1 committed Feb 9, 2020
1 parent 793d151 commit bfce2bf
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 0 deletions.
13 changes: 13 additions & 0 deletions Altklausuren/ws1819_1/4/README.md
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.
23 changes: 23 additions & 0 deletions Altklausuren/ws1819_1/4/graph.dot
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
}
9 changes: 9 additions & 0 deletions Altklausuren/ws1819_1/4/graph.txt
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
112 changes: 112 additions & 0 deletions Altklausuren/ws1819_1/4/main.cpp
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;
}
106 changes: 106 additions & 0 deletions Altklausuren/ws1819_1/4/main2.cpp
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;
}

0 comments on commit bfce2bf

Please sign in to comment.