initial commit
This commit is contained in:
commit
174777396a
6 changed files with 597 additions and 0 deletions
5
Make.config
Normal file
5
Make.config
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
CXX=clang++
|
||||||
|
CXX_STD_FLAGS=-std=c++20
|
||||||
|
CC=clang
|
||||||
|
C_STD_FLAGS=-std=c99
|
||||||
|
LINK_FLAGS=
|
131
Makefile
Normal file
131
Makefile
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
# Configuration (File extensions, tools to be used, etc)
|
||||||
|
|
||||||
|
OPTLIBEXT=-O
|
||||||
|
DEBUGLIBEXT=-g
|
||||||
|
LIBEXTS=$(OPTLIBEXT) $(DEBUGLIBEXT)
|
||||||
|
|
||||||
|
ifeq "$(MAKECMDGOALS)" "opt"
|
||||||
|
LIBEXT=$(OPTLIBEXT)
|
||||||
|
OPTFLAGS=$(OPTFLAGS_OPT)
|
||||||
|
else
|
||||||
|
LIBEXT=$(DEBUGLIBEXT)
|
||||||
|
OPTFLAGS=$(OPTFLAGS_DEBUG)
|
||||||
|
endif
|
||||||
|
|
||||||
|
CXX=clang++
|
||||||
|
CC=clang
|
||||||
|
LN=ln
|
||||||
|
RM=rm
|
||||||
|
MKDIR=mkdir
|
||||||
|
TOUCH=touch
|
||||||
|
DIRNAME=dirname
|
||||||
|
|
||||||
|
OPTFLAGS_OPT=-O3
|
||||||
|
OPTFLAGS_DEBUG=-O0 -g
|
||||||
|
# flags used for linking and compilation
|
||||||
|
FLAGS=#
|
||||||
|
# These flags are for compilation (not linking) only
|
||||||
|
# -MMD auto-genenerates .d files in Make format when .o files are created
|
||||||
|
COMPILE_FLAGS=-MMD -Werror -Wall -Wextra -pedantic -pipe $(OPTFLAGS)
|
||||||
|
# Flags for linking only.
|
||||||
|
LINK_FLAGS=-fuse-ld=gold
|
||||||
|
# C / C++ specific compilation flags
|
||||||
|
CXX_STD_FLAGS=-std=c++20
|
||||||
|
CXX_COMPILE_FLAGS=$(CXX_STD_FLAGS)
|
||||||
|
C_STD_FLAGS=-std=c99
|
||||||
|
C_COMPILE_FLAGS=$(C_STD_FLAGS)
|
||||||
|
|
||||||
|
CXX_SRC_EXTS=cpp C CPP
|
||||||
|
C_SRC_EXTS=c
|
||||||
|
|
||||||
|
SRC_EXTS=$(CXX_SRC_EXTS) $(C_SRC_EXTS)
|
||||||
|
|
||||||
|
BUILDDIR=build
|
||||||
|
DIRECTORIES=$(BUILDDIR)
|
||||||
|
EXECUTABLE_REL=main
|
||||||
|
LINK_NAME=$(BUILDDIR)/$(EXECUTABLE_REL)
|
||||||
|
EXECUTABLE=$(LINK_NAME)$(LIBEXT)
|
||||||
|
|
||||||
|
# Collect files to be compiled
|
||||||
|
|
||||||
|
CXX_SRCS=$(foreach ext, $(CXX_SRC_EXTS), $(shell find . -name "*.$(ext)"))
|
||||||
|
C_SRCS=$(foreach ext, $(C_SRC_EXTS), $(shell find . -name "*.$(ext)"))
|
||||||
|
SRCS=$(CXX_SRCS) $(C_SRCS)
|
||||||
|
OBJS=$(foreach ext, $(SRC_EXTS), $(patsubst %.$(ext), $(BUILDDIR)/%$(LIBEXT).o, $(filter %.$(ext), $(SRCS))))
|
||||||
|
|
||||||
|
# If there is at least one C++ source use the c++ compiler CXX to link.
|
||||||
|
LINKER=$(CC) $(FLAGS) $(LINK_FLAGS)
|
||||||
|
ifneq "$(strip $(CXX_SRCS))" ""
|
||||||
|
LINKER=$(CXX) $(FLAGS) $(LINK_FLAGS)
|
||||||
|
endif
|
||||||
|
|
||||||
|
MAKECONFIG=Make.config
|
||||||
|
# The set of Make-files influencing the build, hence everything depends on them
|
||||||
|
MAKE_DEPENDENCIES=Makefile $(MAKECONFIG)
|
||||||
|
|
||||||
|
DEFAULT_TARGET=debug
|
||||||
|
|
||||||
|
# Include Make.config to modify variables above
|
||||||
|
-include $(MAKECONFIG)
|
||||||
|
|
||||||
|
# The supported meta targets, dependencies
|
||||||
|
.PHONY: clean debug opt default check
|
||||||
|
default: $(DEFAULT_TARGET)
|
||||||
|
clean debug opt: check
|
||||||
|
|
||||||
|
opt debug: $(EXECUTABLE)
|
||||||
|
|
||||||
|
$(OBJS) : $(MAKE_DEPENDENCIES) | $(BUILDDIR)
|
||||||
|
|
||||||
|
# include autogenerated .d files
|
||||||
|
-include $(OBJS:.o=.d)
|
||||||
|
|
||||||
|
# Rules
|
||||||
|
|
||||||
|
define CXX_TO_OBJ
|
||||||
|
$(CXX) $(FLAGS) $(CXX_COMPILE_FLAGS) $(COMPILE_FLAGS) $< -c -o $@
|
||||||
|
endef
|
||||||
|
|
||||||
|
define C_TO_OBJ
|
||||||
|
$(CC) $(FLAGS) $(C_COMPILE_FLAGS) $(COMPILE_FLAGS) $< -c -o $@
|
||||||
|
endef
|
||||||
|
|
||||||
|
define MK_TARGETDIR
|
||||||
|
@$(MKDIR) -p $$($(DIRNAME) $@)
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(BUILDDIR)/%$(LIBEXT).o: %.cpp
|
||||||
|
$(MK_TARGETDIR)
|
||||||
|
$(CXX_TO_OBJ)
|
||||||
|
|
||||||
|
$(BUILDDIR)/%$(LIBEXT).o: %.C
|
||||||
|
$(MK_TARGETDIR)
|
||||||
|
$(CXX_TO_OBJ)
|
||||||
|
|
||||||
|
$(BUILDDIR)/%$(LIBEXT).o: %.CPP
|
||||||
|
$(MK_TARGETDIR)
|
||||||
|
$(CXX_TO_OBJ)
|
||||||
|
|
||||||
|
$(BUILDDIR)/%$(LIBEXT).o: %.c
|
||||||
|
$(MK_TARGETDIR)
|
||||||
|
$(C_TO_OBJ)
|
||||||
|
|
||||||
|
$(EXECUTABLE): $(OBJS) | $(BUILDDIR)
|
||||||
|
$(LINKER) $(OBJS) -o $@
|
||||||
|
|
||||||
|
# Create link in opt/debug target
|
||||||
|
opt debug:
|
||||||
|
$(LN) -sf $(EXECUTABLE_REL)$(LIBEXT) $(LINK_NAME)
|
||||||
|
|
||||||
|
check: $(BUILDDIR)
|
||||||
|
@forbidden=$$(find $(BUILDDIR) -not -type d $(foreach filext, .o .d, -not -name "*$(filext)") $(foreach libext, $(LIBEXTS) "", -not -name "$(EXECUTABLE_REL)$(libext)")); \
|
||||||
|
if [ "$${forbidden}x" != "x" ]; then echo "Error: $(BUILDDIR) contains non-autogenerated files $${forbidden}"; exit 1; fi
|
||||||
|
|
||||||
|
$(DIRECTORIES):
|
||||||
|
@$(MKDIR) $@
|
||||||
|
|
||||||
|
$(MAKECONFIG):
|
||||||
|
@$(TOUCH) $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@$(RM) -rf $(BUILDDIR)
|
34
README.md
Normal file
34
README.md
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# Sample Code
|
||||||
|
|
||||||
|
## Graph class
|
||||||
|
`graph.hpp` and `graph.cpp` contain a simple class to model unweighed undirected graphs that you may use if you wish.
|
||||||
|
For convenience, the graph already supports input and output from and to the DIMACS format.
|
||||||
|
|
||||||
|
## Main routine
|
||||||
|
`example.cpp` contains a toy `main` routine that, for demonstration purposes,
|
||||||
|
reads in a graph in DIMACS format, greedily removes edges until
|
||||||
|
every node is incident to at most one edge and outputs the result to stdout.
|
||||||
|
|
||||||
|
## Makefiles
|
||||||
|
This code also contains a Makefile that you can use for compilation if you prefer it to a manual compilation command (but you don't have to).
|
||||||
|
In the Make.config file, you can change e.g. the c++ compiler which should be used (`CXX`).
|
||||||
|
In order to to use the make setup, open up a terminal and navigate to this folder.
|
||||||
|
You can write `make debug` in order to create an executable in which the compiler stayed close
|
||||||
|
to your code and generated debug symbols which can e.g. be used in the gnu debugger gdb.
|
||||||
|
You can also write `make opt` in order to create an executable in which the compiler was
|
||||||
|
allowed to do a lot of optimization as long as the result stays the same.
|
||||||
|
This executable is much faster, but not as usefull if you are still testing
|
||||||
|
if your program correctly, or why it does not.
|
||||||
|
Both variants use all the error flags used in the problem specification,
|
||||||
|
meaning the compilation will fail if there are any warnings.
|
||||||
|
This is as otherwise one might miss important warnings,
|
||||||
|
which can save a lot of time one would otherwise spend debugging!
|
||||||
|
Finally you can write `make clean` in order to remove everything generated when building
|
||||||
|
one of the other make targets, including the executables and the output directory.
|
||||||
|
|
||||||
|
You do not need to read the Makefile itself, but you can if you want to.
|
||||||
|
It automatically compiles all files ending with `.cpp`, `.C` and `.CPP` using the C++ compiler specified as `CXX` in the Make.config,
|
||||||
|
and compiles all files ending with `.c` using the C compiler specified as `CC` in the same file.
|
||||||
|
Finally it links everything together into a binary which is referenced by the symlink `build/main`.
|
||||||
|
Note exactly one of your `.C` and `.c` files should include a main function!
|
||||||
|
|
35
example.cpp
Normal file
35
example.cpp
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#include <iostream> // For writing to the standard output.
|
||||||
|
#include <fstream> // For reading input files.
|
||||||
|
|
||||||
|
#include "graph.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
if (argc != 2)
|
||||||
|
{
|
||||||
|
std::cout << "Expected one argument, found " << argc - 1 << std::endl;
|
||||||
|
return EXIT_FAILURE; // return 1 would do the same, but is way too easy to mix up!
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fstream input_file_graph{argv[1]};
|
||||||
|
ED::Graph const graph = ED::Graph::read_dimacs(input_file_graph);
|
||||||
|
|
||||||
|
ED::Graph greedy_matching_as_graph{graph.num_nodes()};
|
||||||
|
for (ED::NodeId node_id = 0; node_id < graph.num_nodes(); ++node_id)
|
||||||
|
{
|
||||||
|
if (greedy_matching_as_graph.node(node_id).neighbors().empty())
|
||||||
|
{
|
||||||
|
for (ED::NodeId neighbor_id : graph.node(node_id).neighbors())
|
||||||
|
{
|
||||||
|
if (greedy_matching_as_graph.node(neighbor_id).neighbors().empty())
|
||||||
|
{
|
||||||
|
greedy_matching_as_graph.add_edge(node_id, neighbor_id);
|
||||||
|
break; // Do not add more edges incident to this node!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cout << greedy_matching_as_graph;
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
181
graph.cpp
Normal file
181
graph.cpp
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
#include "graph.hpp" // always include corresponding header first
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note this included everything from the header similar to copy-pasting it here,
|
||||||
|
* including our two classes, the function declarations and all the includes.
|
||||||
|
* In this file we will actually implement the in- and output routines though,
|
||||||
|
* so we need to include the actual implementation of std::istream and std::ostream.
|
||||||
|
*/
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We are also going to use stringstream in order to treat a line,
|
||||||
|
* which we have already read from the input, like an input stream.
|
||||||
|
*/
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The execption header is used to terminate our program
|
||||||
|
* in the case of unexpected input,
|
||||||
|
* which is often the best way to handle such input.
|
||||||
|
* More complex programs may want to catch exceptions in
|
||||||
|
* surrouding code and either try to recover or help debug them.
|
||||||
|
*/
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
// Anonymous name spaces may be used to show the reader
|
||||||
|
// that a function will only be used in the current file.
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
// Using a function for converting the DIMACS node ids to and from our node ids
|
||||||
|
// makes the in and output code more understandable.
|
||||||
|
ED::NodeId from_dimacs_id(ED::size_type dimacs_node_id)
|
||||||
|
{
|
||||||
|
if (dimacs_node_id <= 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Non-positive DIMACS node id can not be converted.");
|
||||||
|
}
|
||||||
|
return dimacs_node_id - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ED::size_type to_dimacs_id(ED::NodeId node_id)
|
||||||
|
{
|
||||||
|
return node_id + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the first line which is not a comment, i.e. does not start with c.
|
||||||
|
std::string read_next_non_comment_line(std::istream & input)
|
||||||
|
{
|
||||||
|
std::string line;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (!std::getline(input, line))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Unexpected end of DIMACS stream.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (line[0] == 'c');
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end of anonymous namespace
|
||||||
|
|
||||||
|
namespace ED
|
||||||
|
{
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
//! \c Node definitions
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
|
||||||
|
void Node::add_neighbor(NodeId const id)
|
||||||
|
{
|
||||||
|
_neighbors.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
//! \c Graph definitions
|
||||||
|
/////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Whenever reasonably possible you should prefer to use `:`
|
||||||
|
// to initalize the members of your class, instead of
|
||||||
|
// assigning values to them after they were default initialized.
|
||||||
|
// Note you should initialize them in the same order
|
||||||
|
// they were declare in back in the class body!
|
||||||
|
Graph::Graph(NodeId const num_nodes)
|
||||||
|
: _nodes(num_nodes)
|
||||||
|
, _num_edges(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void Graph::add_edge(NodeId node1_id, NodeId node2_id)
|
||||||
|
{
|
||||||
|
// It is ok if your program crashes for garbage input,
|
||||||
|
// but it should be an explicit, deliberate choice, e.g. like this.
|
||||||
|
if (node1_id == node2_id)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("ED::Graph class does not support loops!");
|
||||||
|
}
|
||||||
|
|
||||||
|
_nodes[node1_id].add_neighbor(node2_id);
|
||||||
|
_nodes[node2_id].add_neighbor(node1_id);
|
||||||
|
++_num_edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
Graph Graph::read_dimacs(std::istream & input)
|
||||||
|
{
|
||||||
|
// Unfortunatley the common std input functions require us to first declare
|
||||||
|
// our variables and assign them the correct values only later.
|
||||||
|
// Because we want to avoid unitizalized variables, we use a new syntax
|
||||||
|
// added in c++17 to call the constuctor with no arguments,
|
||||||
|
// often called the default constructor: We write {} behind the variable name.
|
||||||
|
|
||||||
|
// When parsing the DIMACS format, there are some words we are not interested in.
|
||||||
|
// We read them into this variable and never use the afterwards.
|
||||||
|
std::string unused_word{};
|
||||||
|
|
||||||
|
// As we need to watch out for comments, we first need to read the input by line.
|
||||||
|
// In order to split non-comment lines into multiple variables we use a std::stringstream.
|
||||||
|
std::stringstream first_buffering_stream{};
|
||||||
|
|
||||||
|
// Note if you do not plan to modify a variable, always declare it as constant.
|
||||||
|
// This does not only prevent you from doing so accidently,
|
||||||
|
// but also helps anybody reading your code understand what you are doing,
|
||||||
|
// as there are less possiblities what can happen.
|
||||||
|
std::string const first_line = read_next_non_comment_line(input);
|
||||||
|
|
||||||
|
size_type num_nodes{};
|
||||||
|
size_type num_edges{};
|
||||||
|
first_buffering_stream << first_line;
|
||||||
|
first_buffering_stream >> unused_word >> unused_word >> num_nodes >> num_edges;
|
||||||
|
|
||||||
|
// Now we successively add edges to our graph;
|
||||||
|
Graph graph(num_nodes);
|
||||||
|
for (size_type i = 1; i <= num_edges; ++i)
|
||||||
|
{
|
||||||
|
// This works just as parsing the first line!
|
||||||
|
std::stringstream ith_buffering_stream{};
|
||||||
|
std::string const ith_line = read_next_non_comment_line(input);
|
||||||
|
size_type dimacs_node1{};
|
||||||
|
size_type dimacs_node2{};
|
||||||
|
ith_buffering_stream << ith_line;
|
||||||
|
ith_buffering_stream >> unused_word >> dimacs_node1 >> dimacs_node2;
|
||||||
|
graph.add_edge(from_dimacs_id(dimacs_node1), from_dimacs_id(dimacs_node2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream & operator<<(std::ostream & output, Graph const & graph)
|
||||||
|
{
|
||||||
|
// We use std::endl to write new lines here.
|
||||||
|
// If you prefer the new line character, \n on linux, that one works fine, too.
|
||||||
|
output << "c Recall each line starting with c encodes a comment in DIMACS format!" << std::endl
|
||||||
|
<< "c The first non-comment line specifies the number of nodes and edges:" << std::endl
|
||||||
|
<< "p edge " << graph.num_nodes() << " " << graph.num_edges() << std::endl
|
||||||
|
<< "c Each of the remaining non-comment lines specifys an edge by two nodes:" << std::endl;
|
||||||
|
|
||||||
|
// We will need the id of the node we are at, so we write a plain old loop here.
|
||||||
|
for (NodeId node_id = 0; node_id < graph.num_nodes(); ++node_id)
|
||||||
|
{
|
||||||
|
Node const & node = graph.node(node_id);
|
||||||
|
// We do not need to keep track of the index neighbor_id has in node,
|
||||||
|
// so we can use this cool loop syntax introduced in c++11.
|
||||||
|
for (NodeId const & neighbor_id : node.neighbors())
|
||||||
|
{
|
||||||
|
// Note we iterate over each edge two times, so we use the following
|
||||||
|
// comparism to check if the edge was not yet written to str!
|
||||||
|
if (node_id < neighbor_id)
|
||||||
|
{
|
||||||
|
output << "e " << to_dimacs_id(node_id) << " " << to_dimacs_id(neighbor_id) << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output << "c If you use this graph class in your solution, you should probably remove this comments!" << std::endl;
|
||||||
|
|
||||||
|
// Streams sometimes buffer their output.
|
||||||
|
// Once one is done with some output routine, it can make sense to flush them,
|
||||||
|
// which clears the buffer and writes the remaining output.
|
||||||
|
output << std::flush;
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ED
|
211
graph.hpp
Normal file
211
graph.hpp
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
// A so called include guard uses the preprocessor to make sure nothing happens when
|
||||||
|
// this header is include a second time. This becomes important if there are many headers
|
||||||
|
// including each other, as undirected cycles can usually not be avoided.
|
||||||
|
#ifndef GRAPH_HPP
|
||||||
|
#define GRAPH_HPP
|
||||||
|
|
||||||
|
/**
|
||||||
|
@file graph.hpp
|
||||||
|
|
||||||
|
@brief This file provides a simple class @c Graph to model unweighted undirected graphs.
|
||||||
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In and output in the standard library is done using streams.
|
||||||
|
* In this header we only need to know std::istream and std::ostream are classes,
|
||||||
|
* since we only declare the function which read write our graph
|
||||||
|
* from an std::istream or to an std::ostream, so we only include the forward declaration.
|
||||||
|
*/
|
||||||
|
#include <iosfwd>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This header defined many different integer types,
|
||||||
|
* enabling us to choose what integers we want to use.
|
||||||
|
*/
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limits are provided by the standard library to check
|
||||||
|
* e.g. if some value can be represented in a certain integer type.
|
||||||
|
*/
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vectors are implemented in the standard library as std::vector.
|
||||||
|
* They encapsulate an array of dynamic size,
|
||||||
|
* so that you don't have to know about the exact implementation.
|
||||||
|
* If you add an element in the end (aka push_back) but the dynamic array is full,
|
||||||
|
* it will automatically be resized.
|
||||||
|
* See https://en.cppreference.com/w/cpp/container/vector for documentation.
|
||||||
|
*/
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Namespaces can be used in order to make sure different modules,
|
||||||
|
* possibly implemented by different people don't have classes/functions/...
|
||||||
|
* with the same name. If you want to refer to some symbol S,
|
||||||
|
* which is defined in a namespace N from outside of that namespace,
|
||||||
|
* you need to write N::S.
|
||||||
|
* The most prominent example is std, the namespace used by the standard library.
|
||||||
|
* This namespace is intended to be used for Edmonds.
|
||||||
|
* */
|
||||||
|
namespace ED
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using names for types has many advantages.
|
||||||
|
* One of them is being able to switch type with very little effort.
|
||||||
|
* For now, we are going to use unsigned (i.e. non negative)
|
||||||
|
* 32 bit integers for all sizes and indices.
|
||||||
|
* But if there was some large graph for which we need 64 bit indices,
|
||||||
|
* we would only need to change the type once, right here!
|
||||||
|
*/
|
||||||
|
using size_type = uint32_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Another advantage of naming types is making your code more readable.
|
||||||
|
* For example an Id is usually a light weight object (read: few bits)
|
||||||
|
* which uniquely determines some object, in this case a node in our graph.
|
||||||
|
* Note the same Id may be used by different graphs though!
|
||||||
|
*/
|
||||||
|
using NodeId = size_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@class Node
|
||||||
|
|
||||||
|
@brief A @c Node stores an array of neighbors (via their ids).
|
||||||
|
|
||||||
|
@note The neighbors are not necessarily ordered, so searching for a specific neighbor takes O(degree)-time.
|
||||||
|
**/
|
||||||
|
class Node
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/** @brief Create an isolated node (you can add neighbors later). **/
|
||||||
|
Node() = default;
|
||||||
|
|
||||||
|
/** @return The number of neighbors of this node. **/
|
||||||
|
size_type degree() const;
|
||||||
|
|
||||||
|
/** @return The array of ids of the neighbors of this node. **/
|
||||||
|
std::vector<NodeId> const & neighbors() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// This allows each Graph to access private members of this class,
|
||||||
|
// in our case the add_neighbor function
|
||||||
|
friend class Graph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Adds @c id to the list of neighbors of this node.
|
||||||
|
@warning Does not check whether @c id is already in the list of neighbors (a repeated neighbor is legal, and
|
||||||
|
models parallel edges).
|
||||||
|
@warning Does not check whether @c id is the identity of the node itself (which would create a loop!).
|
||||||
|
**/
|
||||||
|
void add_neighbor(NodeId const id);
|
||||||
|
|
||||||
|
std::vector<NodeId> _neighbors;
|
||||||
|
}; // class Node
|
||||||
|
|
||||||
|
/**
|
||||||
|
@class Graph
|
||||||
|
|
||||||
|
@brief A @c Graph stores an array of @c Node s, but no array of edges. The list of edges is implicitly given
|
||||||
|
by the fact that the nodes know their neighbors.
|
||||||
|
|
||||||
|
This class models undirected graphs only (in the sense that the method @c add_edge(node1, node2) adds both @c node1
|
||||||
|
as a neighbor of @c node2 and @c node2 as a neighbor of @c node1). It also forbids loops, but parallel edges are
|
||||||
|
legal.
|
||||||
|
|
||||||
|
@warning Nodes are numbered starting at 0, as is usually done in programming,
|
||||||
|
instead starting at 1, as is done in the DIMACS format that your program should take as input!
|
||||||
|
Be careful.
|
||||||
|
**/
|
||||||
|
class Graph
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
@brief Creates a @c Graph with @c num_nodes isolated nodes.
|
||||||
|
|
||||||
|
The number of nodes in the graph currently cannot be changed. You can only add edges between the existing nodes.
|
||||||
|
**/
|
||||||
|
Graph(NodeId const num_nodes);
|
||||||
|
|
||||||
|
/** @return The number of nodes in the graph. **/
|
||||||
|
NodeId num_nodes() const;
|
||||||
|
|
||||||
|
/** @return The number of edges in the graph. **/
|
||||||
|
size_type num_edges() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@return A reference to the id-th entry in the array of @c Node s of this graph.
|
||||||
|
**/
|
||||||
|
Node const & node(NodeId const id) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
@brief Adds the edge <tt> {node1_id, node2_id} </tt> to this graph.
|
||||||
|
|
||||||
|
Checks that @c node1_id and @c node2_id are distinct and throws an exception otherwise.
|
||||||
|
This method adds both @c node1_id as a neighbor of @c node2_id and @c node2_id as a neighbor of @c node1_id.
|
||||||
|
|
||||||
|
@warning Does not check that the edge does not already exist, so this class can be used to model non-simple graphs.
|
||||||
|
**/
|
||||||
|
void add_edge(NodeId node1_id, NodeId node2_id);
|
||||||
|
|
||||||
|
// Static functions are not called on an object of the class, but on the class itself.
|
||||||
|
/**
|
||||||
|
* Reads a graph in DIMACS format from the given istream and returns that graph.
|
||||||
|
*/
|
||||||
|
static Graph read_dimacs(std::istream & str);
|
||||||
|
/**
|
||||||
|
@brief Prints the graph to the given ostream in DIMACS format.
|
||||||
|
**/
|
||||||
|
friend std::ostream & operator<<(std::ostream & str, Graph const & graph);
|
||||||
|
private:
|
||||||
|
std::vector<Node> _nodes;
|
||||||
|
size_type _num_edges;
|
||||||
|
}; // class Graph
|
||||||
|
|
||||||
|
// Calling a function usually has some constant time overhead.
|
||||||
|
// The compiler is capable of "inlining" function calls,
|
||||||
|
// which means when your code calls this function,
|
||||||
|
// the compiler will instead insert the content of the function.
|
||||||
|
// This has no affect on your code, but will get rid of this overhead.
|
||||||
|
// The inline keywoard recommends the compiler to inline a function.
|
||||||
|
// If you use it for some, you must implement that function
|
||||||
|
// in the header! For readablility, we put all implementations
|
||||||
|
// of inline function into the following inline section.
|
||||||
|
//BEGIN: Inline section
|
||||||
|
|
||||||
|
inline
|
||||||
|
size_type Node::degree() const
|
||||||
|
{
|
||||||
|
return neighbors().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
std::vector<NodeId> const & Node::neighbors() const
|
||||||
|
{
|
||||||
|
return _neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
NodeId Graph::num_nodes() const
|
||||||
|
{
|
||||||
|
return _nodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
size_type Graph::num_edges() const
|
||||||
|
{
|
||||||
|
return _num_edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline
|
||||||
|
Node const & Graph::node(NodeId const id) const
|
||||||
|
{
|
||||||
|
return _nodes[id];
|
||||||
|
}
|
||||||
|
//END: Inline section
|
||||||
|
|
||||||
|
} // namespace ED
|
||||||
|
|
||||||
|
#endif /* GRAPH_HPP */
|
Loading…
Reference in a new issue