Day 23 - P83 Graph.spanningTrees: shift Edge and Node case classes to graph.Base

Previously the Edge and Node case classes were defined under the AbstractGraph
class. As such, each concrete instantiation of a class inherited from
AbstractGraph, such as Graph, defines its own Edge and Node case classes.
Therefore, 2 different copies of a Graph class can never be equal despite
containing the same edges in terms of content, simply because their types
do not match.

Now, we shift the Edge and Node case classes to a graph.Base object.
This makes the syntax more clunky, since Edge is now Edge[T, U] and Node is
now Node[T, U], but it allows us to do comparisons using the == operator
instead of relying on a common string representation.

Signed-off-by: Pang Yan Han <pangyanhan@gmail.com>
Day 23 Part 2 - Graph.spanningTrees - unify sorting functions

This is possible now that we have unified the Edge and Node case classes into
Base.Edge and Base.Node in the last patch.

Signed-off-by: Pang Yan Han <pangyanhan@gmail.com>
Day 23 Part 3 - Graph.spanningTrees - correct base case when we run out of edges

If there are no more remaining candidate edges during the search for spanning
trees, but before we hit the target number of edges, an empty Set should be
returned instead of a Set containing an empty Set.

Signed-off-by: Pang Yan Han <pangyanhan@gmail.com>
Day 23 Part 4 - implement Graph.isTree

Signed-off-by: Pang Yan Han <pangyanhan@gmail.com>
Day 23 Part 5 - implement Graph.isConnected

Thereby completing P83

Signed-off-by: Pang Yan Han <pangyanhan@gmail.com>