diff --git a/Uebung 5/Uebung5_1/Makefile b/Uebung 5/Uebung5_1/Makefile new file mode 100644 index 0000000..a315b9c --- /dev/null +++ b/Uebung 5/Uebung5_1/Makefile @@ -0,0 +1,52 @@ + +# Name of the binary for Development +BINARY = main +# Name of the binary for Release +FINAL = prototyp +# Object files +OBJS = edge.o graph.o main.o +# Compiler flags +CFLAGS = -Werror -Wall -std=c++17 -fsanitize=address,undefined -g +# Linker flags +LFLAGS = -fsanitize=address,undefined +#Which Compiler to use +COMPILER = c++ + + +# all target: builds all important targets +all: binary + +final : ${OBJS} + ${COMPILER} ${LFLAGS} -o ${FINAL} ${OBJS} + rm ${OBJS} + +binary : ${OBJS} + ${COMPILER} ${LFLAGS} -o ${BINARY} ${OBJS} + +# Links the binary +${BINARY} : ${OBJS} + ${COMPILER} ${LFLAGS} -o ${BINARY} ${OBJS} + + +# Compiles a source-file (any file with file extension .c) into an object-file +# +# "%" is a wildcard which matches every file-name (similar to * in regular expressions) +# Such a rule is called a pattern rule (because it matches a pattern, see https://www.gnu.org/software/make/manual/html_node/Pattern-Rules.html), +# which are a form of so called implicit rules (see https://www.gnu.org/software/make/manual/html_node/Implicit-Rules.html) +# "$@" and "$<" are so called automatic variables (see https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html) +%.o : %.cpp + ${COMPILER} -c ${CFLAGS} -o $@ $< + + +# Rules can not only be used for compiling a program but also for executing a program +run: ${BINARY} + ./${BINARY} + + +# Delete all build artifacts +clean : + rm -rf ${BINARY} ${OBJS} + + +# all and clean are a "phony" targets, meaning they are no files +.PHONY : all clean diff --git a/Uebung 5/Uebung5_1/edge.cpp b/Uebung 5/Uebung5_1/edge.cpp new file mode 100644 index 0000000..92fc080 --- /dev/null +++ b/Uebung 5/Uebung5_1/edge.cpp @@ -0,0 +1,22 @@ +#include "edge.h" + + +Edge::Edge(std::string src, std::string dest, int weight) { + this->setSrc(src); + this->setDest(dest); + this->weight = weight; +} + +const std::string& Edge::getSrc() const { + return this->src; +} +void Edge::setSrc(std::string src) { + this->src = src; +} + +const std::string& Edge::getDest() const { + return this->dest; +} +void Edge::setDest(std::string dest) { + this->dest = dest; +} \ No newline at end of file diff --git a/Uebung 5/Uebung5_1/edge.h b/Uebung 5/Uebung5_1/edge.h new file mode 100644 index 0000000..41ed213 --- /dev/null +++ b/Uebung 5/Uebung5_1/edge.h @@ -0,0 +1,18 @@ +#include + +#pragma once + +/* -- Struct Edge -- */ +class Edge { + private: + std::string src; // string source; beginning of the directed connection + std::string dest; // string destination; end of the directed connection + + public: + Edge(std::string src, std::string dest, int weight=0); // Constructor + int weight; + const std::string& getSrc() const; // getter function for std::string src + void setSrc(std::string src); // setter funciton for std::string src + const std::string& getDest() const; // getter function for std::string dest + void setDest(std::string dest); // setter function for std::string dest +}; diff --git a/Uebung 5/Uebung5_1/graph.cpp b/Uebung 5/Uebung5_1/graph.cpp new file mode 100644 index 0000000..8962e9a --- /dev/null +++ b/Uebung 5/Uebung5_1/graph.cpp @@ -0,0 +1,447 @@ +#include "graph.h" + +Graph::Graph() { // Default Constructor + this->vertices = + std::vector(0); // Empty Vector for this->vertices + this->adjacencyMatrix.resize( + 0, std::vector(0)); // 0x0 Matrix for this->adjacencyMatrix +} + +/* -- + Constructor with params +-- */ +Graph::Graph(const std::vector &vertices, + const std::vector &edges) { + this->vertices = vertices; // definition of this->vertex through parameter + // vertices (type: std::vector) + this->adjacencyMatrix.resize( + (int)vertices.size(), + std::vector((int)this->vertices.size(), + INT_MAX)); // creation of an NxN Matrix, based on the + // size of vertices + for (int i = 0; i < edges.size(); i++) { + insertEdge(edges[i]); // edges are added one by one, utilizing the + // insertEdge()-Function + } +} + +/* -- / +Function to insert a vertex into the Graph; + - takes one argument of type std::string that represents a vertex + - returns void +/ -- */ +void Graph::insertVertex(const std::string &vertex) { + if (this->resolveVertex(vertex) != -1) { /* -- */ + std::cerr + << "Vertex already in Graph!\n"; /* Calls this->resolveVertex to check + if a given vertex is already in the + Graph. Returns with an error, if + this is the case. */ + return; /* -- */ + } + this->vertices.push_back(vertex); // adds a vertetx via the push_back + // function provided by std::vector + this->adjacencyMatrix.resize( + this->vertices.size(), + std::vector(this->vertices.size())); // resizes the adjacencyMatrix + // to the new size + for (int i = 0; i < this->vertices.size(); i++) { /* -- */ + adjacencyMatrix[i].resize( + this->vertices.size()); /* resizes every "sub" vector of the matrix to + the new size */ + } /* -- */ +} + +/* -- / +Function to delete a vertex from the Graph; + - takes one argument of type std::string that represents a vertex + - returns void +/ -- */ +void Graph::deleteVertex(const std::string &vertex) { + int index = this->resolveVertex(vertex); + if (index == -1) { /* -- */ + std::cerr << "Vertex not found\n"; /* Calls this->resolveVertex to check if + a given vertex is already in the + Graph. Returns with an error, if this + is the case. */ + return; /* -- */ + } + this->vertices.erase( + this->vertices.begin() + + index); // erases the vertex at position "index" from this->vertices + + this->adjacencyMatrix.erase( + this->adjacencyMatrix.begin() + + index); // erases the entries from the adjacencyMatrix at "column" + // position "index" + for (int i = 0; i < this->adjacencyMatrix[0].size(); i++) { /* -- */ + this->adjacencyMatrix[i].erase(this->adjacencyMatrix[i].begin() + + index); /* erases the entries from the + adjacencyMatrix in every "row" */ + } /* -- */ +} + +/* -- / +Function to insert an Edge + - takes one argument of type Edge that represents an edge + - returns void +/ -- */ +void Graph::insertEdge(const Edge &edge) { + int col = this->resolveVertex( + edge.getSrc()); // resolves the src of the edge to the index within the + // adjacencyMatrix + int row = this->resolveVertex( + edge.getDest()); // resolves the dest of the edge to the index within the + // adjacencyMatrix + if (col == -1 || row == -1) { /* -- */ + std::cerr << "Vertex not found!\n"; /* Calls this->resolveVertex to check + if a given vertex is already in the + Graph. Returns with an error, if this + is the case. */ + return; /* -- */ + } + + this->adjacencyMatrix[col][row] = + edge.weight; // sets the value of the adjacencyMatrix at position + // [col][row] to the weight of the edge + this->adjacencyMatrix[row][col] = + edge.weight; // sets the value of the adjacencyMatrix at position + // [col][row] to the weight of the edge +} + +/* -- / +Function to delete an Edge + - takes one argument of type Edge that represents an edge + - returns void +/ -- */ +void Graph::deleteEdge(const Edge &edge) { + int col = this->resolveVertex( + edge.getSrc()); // resolves the src of the edge to the index within the + // adjacencyMatrix + int row = this->resolveVertex( + edge.getDest()); // resolves the dest of the edge to the index within the + // adjacencyMatrix + if (col == -1 || row == -1) { /* -- */ + std::cerr << "Vertex not found!\n"; /* Calls this->resolveVertex to check + if a given vertex is already in the + Graph. Returns with an error, if this + is the case. */ + return; /* -- */ + } + this->adjacencyMatrix[col][row] = + 0; // sets the value of the adjacencyMatrix at position [col][row] to 0 +} + +/* -- / +Function to check whether vertex v2 is adjacent to vertex v1 + - takes one argument of type Edge that represents an edge + - returns void +/ -- */ +bool Graph::adjacent(const std::string &vertex1, const std::string &vertex2) { + int indexVertex1 = this->resolveVertex(vertex1); + int indexVertex2 = this->resolveVertex(vertex2); + if (indexVertex1 == -1 || indexVertex2 == -1) { + std::cerr << "Vertex not found!\n"; + return false; + } + // As adjacency is an equivalency relation, we need to check for a possible + // relation in both direction. This can be achieved by swapping the the + // position specifications of the adjacencyMatrix (from + // [indexVertex1][indexVertex2] to [indexVertex2][indexVertex1]). + if (this->adjacencyMatrix[indexVertex1][indexVertex2] != 0 || + this->adjacencyMatrix[indexVertex2][indexVertex1] != 0) { + return true; // if a connection (in any direction) is found, return true + } else { + return false; // else, return false + } +} + +/* -- / +Function that returns an std::vector of std::string representing the +neighbouring vertices of the parameter std::string vertex. Other than adjacent, +neighbourhood is a directed relationship, which means that a vertex "A" can be +the neighbour of "B", but this does not automatically imply that "B" is the +neighbour of "A". + - takes one argument of type std::string that represents a vertex + - returns a std::vector of std::string +/ -- */ +std::vector Graph::neighbours(const std::string &vertex) { + int indexVertex = this->resolveVertex( + vertex); // resolves the vertex to the index within the adjacencyMatrix + std::vector resVector = + {}; // initializes an empty std::vetor of std::string + for (int i = 0; i < this->adjacencyMatrix[indexVertex].size(); + i++) { /* Loops through all entries of the subvector of + adjacencyMatrix[indexVertex] */ + if (this->adjacencyMatrix[indexVertex][i] != + 0) { /* and checks if the entry at position [indexVertex][i] is not 0. + */ + resVector.push_back( + this->vertices[i]); /* If the condition is fulfilled, add the vertex + (name) to the result vector resVector */ + } + } + return resVector; +} + +/* -- / +Function that prints the current Graph's adjacencyMatrix. + - prints the Graph by utilizing std::cout + - returns void +/ -- */ +void Graph::printGraph() { + std::cout << "--------------------------------------------\n"; + std::cout << "\t\t"; + for (int i = 0; i < this->vertices.size(); i++) { + std::cout << this->vertices[i] << "\t"; + } + std::cout << "\n"; + for (int i = 0; i < this->vertices.size(); i++) { + std::cout << this->vertices[i] << "\t-->\t"; + for (int j = 0; j < this->vertices.size(); j++) { + std::cout << this->adjacencyMatrix[i][j] << "\t"; + } + std::cout << "\n"; + } + std::cout << "--------------------------------------------\n"; +}; + +/* -- / +Function that resolves a parameter std::string name and returns it's index +within the this->vertices vector; between vertices of type std::string and the +corresponding index (in the adjacencyMatrix) of type int. + - takes one argument of type std::string that represents a vertex + - returns an int representing the vertex's index in the adjacencyMatrix; + - function returns -1 in case the resolution of the name is unsuccessful +/ -- */ +int Graph::resolveVertex(const std::string &vertexName) { + for (int i = 0; i < this->vertices.size(); i++) { + if (this->vertices[i] == vertexName) { + return i; + } + } + return -1; +} + +/* Public implementation of the travelling salesman's algorithm + - This approach reduces the complexity of use for the programmer, because + it encapsulates: + * the creation of a std::vector filled with booleans, + * resolving the vertex + * the handling of potential errors + * Handling the startingPoint + - This "convenience" function increases efficiency of use and reduces + error-proneness +*/ +void Graph::letTheSalesmanTravel(const std::string &vertex) { + int vertexIndex = this->resolveVertex(vertex); + if (vertexIndex == + -1) { // Error-Handling in case the vertex could not be resolved + std::cerr << "Vertex not found!\n"; + exit(1); + } + std::cout << vertex; // Outputs the first vertex + std::vector visited; // declaration... + visited.resize(this->vertices.size(), + false); // ...and initialization of the "visited" vector + visited[vertexIndex] = true; // every entry of visited is false, except the + // vertex that has been passed to the function + this->_letTheSalesmanTravel(vertex, visited, + vertex); // call the private implementation for + // the travelling salesman's algorithm +} + +int *Graph::performDijkstra(const std::string &sourceVertex) { + std::stringstream retString; // stringstream variable to concat output + int src = + this->resolveVertex(sourceVertex); // resolve vertexName of sourceVertex + // to indicies in the matrix + if (src == -1) { /* -- */ + std::cerr + << "Vertex not found!\n"; /* If src is -1 (not in this->vertices), + return function with error warning */ + return new int(-1); /* -- */ + } + bool visited[(int)this->vertices + .size()]; // Declare an array of bool with the size of + // this->vertices.size() to remember which + // vertices have already been visited + int *distances = (int *)malloc( + (int)this->vertices.size() * + sizeof(int)); // Declare an array of int with the size of + // this->vertices.size() for the distance metrics + + for (int i = 0; i < (int)this->vertices.size(); i++) { + visited[i] = false; // every entry of visited is set to false + distances[i] = INT_MAX; // every entry of distances is set to INT_MAX (this + // is our way of defining the value "INFINITY") + } + + distances[src] = 0; // sets the distance of the source to 0, as a vertex can + // not have a distance to itself + + for (int j = 0; j < (int)this->vertices.size(); j++) { + int u; // declaration of int u; will represent our "minimum" element + int minDist = + INT_MAX; // set minDist to INT_MAX, so that the following check does + // not run into an unexpected error with longer distances + + // Function to get the minimum distance of the vertexes (in distances) that + // has not been visited yet (indicated by the visited array) + for (int k = 0; k < (int)this->vertices.size(); k++) { + if (distances[k] < minDist && + visited[k] == + false) { // compare distance[k] with the current minDist + minDist = distances[k]; // if a new min is found, assign it to minDist + u = k; // the index of the min is assigned to u + } + } + visited[u] = true; // state that the vertex at the index of min has been + // visited by setting it to true + + /* -- */ + /* The following loop is key for the Djikstra algorithm. It every for every + loop pass it checks if a node has been visited (first condition), if the + minimum distance is INT_MAX ("INFINITY", second condition), if the there is + an edge between the two vertices (third condition, as edges are represented + trough entries at adjacencyMatrix[u][a]) and if the minimal distance + the + edge weight is samller than distance at the current distance (distances[a], + fourth condition). Only if a vertex has NOT been visited, the minimal + distance is NOT INT_MAX, there IS an edge between the two vertices and the + minimal distance + adjacencyMatrix[u][a] IS SMALLER, adjust the distance at + position a. / -- */ + + for (int a = 0; a < (int)this->vertices.size(); a++) { + if (visited[a] == false && distances[u] != INT_MAX && + this->adjacencyMatrix[u][a] != INT_MAX && + distances[u] + this->adjacencyMatrix[u][a] < distances[a]) { + distances[a] = distances[u] + adjacencyMatrix[u][a]; + } + } + } + return distances; +} + +void Graph::_letTheSalesmanTravel(const std::string &vertex, + std::vector &visited, + const std::string &startingPoint) { + std::string nearestNeighbour; + + /* std::count takes two arguments of type InputIterator, which are returned + by both a vector's begin() and end() function and a value to compare it to + as a third argument. It will then count the occurences of this third + argument in a range between the first and second argument. For further and + more detailed information please refer to: + https://en.cppreference.com/w/cpp/algorithm/count + */ + if (std::count(visited.begin(), visited.end(), false) > + 0) { // if there is a city/vertex that has not been visited yet + nearestNeighbour = this->getNearestNeighbour( + vertex, visited); // get the nearest neighbour of the verted + this->_letTheSalesmanTravel( + nearestNeighbour, visited, + startingPoint); //...and then the function calls itself with the nearest + // neigbhour as vertex; note: starting point remains the + // same! + } + if (std::count(visited.begin(), visited.end(), true) == 0) { + return; // if all cities/vertices are marked as unvisited, return; this + // only happens after the next if-block has been + // ...called at any point within the recusive structure. As you can + // see in the public implementation "letTheSalesmanTravel" + // ...the function is initially called with one "true" value in the + // vector "visited". + } + /* Note: this if condition can only be fulfilled by the last recursion. This + results from the for-loop that will set every entry of "visited" to false. + Considering the way recursions work, the approach presented here ensures + that the last visited city/vertex will connect with the initial one and + therefore conclude the round trip + */ + if (std::count(visited.begin(), visited.end(), false) == + 0) { // if all vertices have been visited + int startIndex = + this->resolveVertex(startingPoint); // resolve the initial vertex + int vertexIndex = this->resolveVertex(vertex); // ...and the current vertex + if (startIndex == -1 || + vertexIndex == -1) { // Error-handling if either initial or current + // vertex could not be resolved + std::cerr << "Vertex not found!\n"; + exit(1); + } + std::cout << " -("; + if (this->adjacencyMatrix[startIndex][vertexIndex] == INT_MAX) { + std::cout << "INVALID EDGE"; + } else { + std::cout << this->adjacencyMatrix[startIndex][vertexIndex]; + } + std::cout << ")-> " << startingPoint + << "\n"; // output that connects the current vertex and the + // starting point again + for (int i = 0; i < (int)this->vertices.size(); + i++) { // setting every entry of "visited" to false + visited[i] = false; + } + } +} + +/* Function that finds the nearest neighbour of a given vertex that has not + been visited already + - It therefore takes a parameter vertex of type string and a vector of + booleans to remember which of the vertices have been visited + - It returns a vertex of type std::string + - The function will terminate the whole program if the vertex (from the + param) cannot be resolved! +*/ +std::string Graph::getNearestNeighbour(const std::string &vertex, + std::vector &visited) { + int nearestNeighbour = INT_MAX; // initialization of the nearestNeighbour as + // the index in this->vertices + int distNearest = + INT_MAX; // initialization of the nearest distance; not completely + // necessary, it just improves the readability of the code + int vertexPos = + this->resolveVertex(vertex); // Error handling in case the vertex could + // not be found in this->vertices + if (vertexPos == -1) { + std::cerr << "Vertex not found!\n"; + exit(1); + } + + /* looping through all vertices to find which of these is nearest */ + for (int i = 0; i < (int)this->vertices.size(); i++) { + /* This if condition checks whether the distance between the vertex from + the first input param and this->vertices[i] fulfills the condition of <= + distNearest. If so, it will assign the corresponding values to + nearestNeighbour and distNearest. It should be noted that it is crucial + to compare with "<=" rather than just "<", because only this way it can + be ensured that there will ALWAYS be a result, as long as at least one + vertex has not been visited. Let's say, we landed at a vertex that is not + connected to any city that has not yet been visited. If we used "<", the + result for nearestNeighbour would be INT_MAX, as it has been used to + initialize the variable nearestNeighbour, as no city would be closer than + INT-MAX (this is because the adjacency matrix has been altered to + previous versions). By utilizing "<=", it now chooses a city within the + bounds of this->vertices, even though the distance will be INT_MAX + */ + if (this->adjacencyMatrix[vertexPos][i] <= distNearest && + visited[i] == false) { + nearestNeighbour = i; + distNearest = this->adjacencyMatrix[vertexPos][i]; + } + } + + visited[nearestNeighbour] = true; // mark the nearestVertex as visited + + std::stringstream s; + std::cout << " -("; + if (distNearest == INT_MAX) { + std::cout << "INVALID EDGE"; + } else { + std::cout << distNearest; + } + std::cout << ")-> " << this->vertices[nearestNeighbour]; + // output + s << this->vertices[nearestNeighbour]; + return s.str(); +} diff --git a/Uebung 5/Uebung5_1/graph.h b/Uebung 5/Uebung5_1/graph.h new file mode 100644 index 0000000..988af47 --- /dev/null +++ b/Uebung 5/Uebung5_1/graph.h @@ -0,0 +1,52 @@ +#include /* type vector */ +#include /* type string */ +#include /* cout, cerr */ +#include /* type stringstream to easily concat a result string */ +#include /* INT_MAX */ +#include + +#include "edge.h" + +#pragma once + +void vectorToString(std::vector vector); + + + +/* -- Class Graph -- */ +class Graph { + private: + std::vector vertices; // vector connecting vertices with indicies; needed for name resolution + void _letTheSalesmanTravel(const std::string& vertex, std::vector& visited, const std::string& startingPoint); + public: + Graph(); // default constructor + Graph(const std::vector& vertices, const std::vector& edges); // param. constructor + std::vector> adjacencyMatrix; // vector of vectors containing integers (the adjacency matrix) + + /* -- + Resolves a given string of a vertex and returns its position in the + adjacencyMatrix (as integer). Returns -1 if name could not be resolved, + which indicates the the name was not found. + -- */ + int resolveVertex(const std::string& name); + void printGraph(); // prints out the graph with vertices and the adjacencyMatrix + + /* -- + Graph Manipulation Functions + -- */ + void insertVertex(const std::string& vertex); // inserts a new vertex; throws error, if vertex already exists in Graph + void deleteVertex(const std::string& vertex); // deletes a vertex from the Graph; throws an error, if vertex does not exist + void insertEdge(const Edge& edge); // inserts a new edge; parameter can be {std::string, std::string} due to implicit cast... + // ...does not check if the edge already exists, nor notifies user/programmer + void deleteEdge(const Edge& edge); // deletes an edge; parameter can be {std::string, std::string} due to implicit cast... + // ...does not check if edge exists + bool adjacent(const std::string& vertex1, const std::string& vertex2); // checks if vertex1 and vertex2 are adjacent; returns boolean; adjacency indicates that... + // ...a direct connection between a <--> b (direction NOT important) exists + std::vector neighbours(const std::string& vertex); // returns a vector of strings, containing all neighbouring vertices of the parameter vertex... + // ...Neighbours are all vertices that a given vertex is connected to through OUTGOING edges + + int* performDijkstra(const std::string& sourceVertex); // Dijkstra algorithm implementation + void letTheSalesmanTravel(const std::string& vertex); + std::string getNearestNeighbour(const std::string& vertex, std::vector& visited); + +}; \ No newline at end of file diff --git a/Uebung 5/Uebung5_1/main.cpp b/Uebung 5/Uebung5_1/main.cpp new file mode 100644 index 0000000..2ebafd1 --- /dev/null +++ b/Uebung 5/Uebung5_1/main.cpp @@ -0,0 +1,40 @@ + +#include "graph.h" +#include "edge.h" +#include /* type vector */ +#include /* type string */ +#include /* cout, cerr */ +#include /* type stringstream to easily concat a result string */ + + +int main() { + /* Example 1: Das. Ist. Das. Haus. Vom. Ni. Ko. Laus. */ +// std::vector vertices = {"A", "B", "C", "D", "E"}; +// std::vector edges = { +// {"A", "B", 4}, {"A", "D", 13}, {"A", "E", 7}, {"B", "C", 2}, +// {"B", "D", 4}, {"B", "E", 13}, {"C", "D", 19}, {"D", "E", 2} +// }; +// Graph g(vertices, edges); +// g.letTheSalesmanTravel("A"); +// g.printGraph(); + + /* Example 2: Infinity between two vertices */ + // std::vector vertices = {"A", "B", "C", "D", "E"}; + // std::vector edges = { + // {"A", "B", 4}, {"A", "D", 2}, {"A", "E", 3}, {"B", "C", 7}, + // {"B", "D", 4}, {"B", "E", 1}, {"C", "D", 6}, {"D", "E", 5} + // }; +// Graph g(vertices, edges); +// g.insertEdge({"A", "B", 8}); +// g.letTheSalesmanTravel("A"); + + /* Example 3: Oh gosh, looks like we are stranded */ + std::vector vertices = {"Bregenz", "Muenchen", "Innsbruck", "Klagenfurt", "Salzburg", "Linz", "Wien", "StPoelten", "Eisenstadt", "Graz"}; + std::vector edges = { + {"Bregenz", "Muenchen", 2}, {"Bregenz", "Innsbruck", 2}, {"Innsbruck", "Muenchen", 6}, {"Innsbruck", "Graz", 5}, {"Innsbruck", "Klagenfurt", 4}, + {"Muenchen", "Salzburg", 5}, {"Salzburg", "Linz", 3}, {"Salzburg", "Klagenfurt", 5}, {"Klagenfurt", "Linz", 4}, {"Klagenfurt", "Wien", 3}, + {"Linz", "Graz", 3}, {"Linz", "StPoelten", 2}, {"StPoelten", "Wien", 2}, {"Graz", "Wien", 1},{"Graz", "Eisenstadt", 1},{"Wien", "Eisenstadt", 1} + }; + Graph g(vertices, edges); + g.letTheSalesmanTravel("Innsbruck"); +}