From: Jakob Cornell Date: Fri, 26 Jun 2020 06:55:58 +0000 (-0500) Subject: Add structures section, implement disjoint set and Kruskal's X-Git-Url: https://jcornell.net/gitweb/gitweb.cgi?a=commitdiff_plain;h=ba100e03db561835be9fa9f6b38638bf38f15128;p=contest-book.git Add structures section, implement disjoint set and Kruskal's --- diff --git a/algorithms/dijkstra/topic.tex b/algorithms/dijkstra/topic.tex deleted file mode 100644 index 35d4d8e..0000000 --- a/algorithms/dijkstra/topic.tex +++ /dev/null @@ -1,43 +0,0 @@ -{ -\newcommand{\ImplPath}{\ProjRootPrefix/impl/dijkstra} - -\TopicHeader{Weighted Shortest Path: Dijkstra's Agorithm} - - Dijkstra's algorithm is useful for computing the shortest path between nodes in a graph where edges have nonnegative weights. - The worst-case performance of the algorithm is $O(E + V \log V)$, if the graph has $E$ edges and $V$ nodes. - - All implementations here provide as output, for each node, the following information: - \begin{itemize} - \item whether the node was reachable from the search root - \item the shortest distance between the root and the node - \item the previous node along the shortest path, i.e. the node that was being explored when this node was first found - \end{itemize} - Note that the path of nodes from the root to any reachable node can be computed by following the previous-node links iteratively until the search root is reached. - - The implementations use directed graphs, so in order to emulate searching an undirected graph, two directed edges going in opposite directions should be made for each edge in the desired undirected graph. - - \TopicSubHeader{Python 3} - - \inputminted{python}{\ImplPath/python3/dijkstra.py} - - The \texttt{Node} constructor accepts a \texttt{data} parameter. - Any value can be passed in, and it will be stored in the node to facilitate associating the nodes with data specific to the problem. - Here's a usage example: - - \inputminted{python}{\ImplPath/python3/usage.py} - - In this implementation, two dictionaries are returned which provide the distance and previous-node information. - To see if a node is reachable, simply check whether the node is a key in the first (distance) dictionary. - - \TopicSubHeader{Java} - - \centerline{\texttt{Graph.java}} - \inputminted{java}{\ImplPath/java/Graph.java} - - The code may be used as follows. - If you need to attach some data to the nodes, you could add a field to the \texttt{Node} class or use a \texttt{Map} to keep track of the association. - - \inputminted{java}{\ImplPath/java/Usage.java} - - The visited flag, previous-node links, and distances are accessible as fields on the \texttt{Node} objects after the search. -} diff --git a/algorithms/hull-2d/topic.tex b/algorithms/hull-2d/topic.tex deleted file mode 100644 index 4eb9cc2..0000000 --- a/algorithms/hull-2d/topic.tex +++ /dev/null @@ -1,30 +0,0 @@ -{ -\newcommand{\ImplPath}{\ProjRootPrefix/impl/hull-2d} - -\TopicHeader{Convex Hull (2D)} - - Convex hull algorithms are given a collection of points and output a subset of just the outermost points. - Formally, the output is the points of a convex polygon within which lie all points in the input. - - The implementations here use a technique called Graham scan to compute the convex hull with time complexity $O(n \log n)$, where $n$ is the number of input points. - The output points are ordered: the bottom-most (and then leftmost) point appears first, then the remaining hull points in counterclockwise order. - - \TopicSubHeader{Java} - - \centerline{\texttt{Hull.java}} - \inputminted{java}{\ImplPath/java/Hull.java} - - This may be used as follows: - - \inputminted{java}{\ImplPath/java/Usage.java} - - \TopicSubHeader{Python 3} - - \inputminted{python}{\ImplPath/python3/hull.py} - - To run the algorithm, pass a set of $(x, y)$ pairs (\texttt{tuple}s) to \texttt{hull}. - - A sample usage: - - \inputminted{python}{\ImplPath/python3/usage.py} -} diff --git a/algorithms/network-flows/topic.tex b/algorithms/network-flows/topic.tex deleted file mode 100644 index 1dd1a68..0000000 --- a/algorithms/network-flows/topic.tex +++ /dev/null @@ -1,26 +0,0 @@ -{ -\newcommand{\ImplPath}{\ProjRootPrefix/impl/network-flows} - -\TopicHeader{Network Flows: Edmonds--Karp} - - Network flows algorithms find the maximum flow through and the minimum cut of a flow network. - This terminology and relevant algorithms are covered in Algorithms (CS 280) at Oberlin. - - The implementations here use the Edmonds--Karp algorithm on flow networks with integer capacities. - The time complexity of the algorithm is $O\left( VE^2 \right)$, where $V$ and $E$ are the number of vertices and edges in the network, respectively. - - \TopicSubHeader{Java} - - Coming soon... - - \TopicSubHeader{Python 3} - - \inputminted{python}{\ImplPath/python3/flows.py} - - To use the code: - - \inputminted{python}{\ImplPath/python3/usage.py} - - The \texttt{cut\_src} variable here is a set of node objects representing the source side of the minimum cut. - If the set \texttt{n} contains all nodes in the network, the sink side is computed by \texttt{n - cut\_src}. -} diff --git a/config.py b/config.py index 67eb52e..c835871 100644 --- a/config.py +++ b/config.py @@ -5,7 +5,7 @@ Some/all parameters are passed to LaTeX as generated code, which is probably a b config = { # whether to insert page breaks before reference items to ease lookup - 'RefPageBrk': False, + 'RefPageBrk': True, # languages for which to exclude implementations (java, python3) (to be implemented) 'exclude_langs': [], } diff --git a/contributors b/contributors index ba95e4b..e2d6d69 100644 --- a/contributors +++ b/contributors @@ -1,6 +1,12 @@ Contributors - - Jakob Cornell, creator: LaTeX code, build system, Python/Java algorithm implementations + - Jakob Cornell, creator: LaTeX code, build system, Python/Java implementations Attribution - - Dijkstra's Algorithm implementations and complexity analysis based on content here: https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm (reused under CC-BY-SA) - - convex hull implementation and complexity analysis based on content here: https://en.wikipedia.org/wiki/Graham_scan (reused under CC-BY-SA) + - Dijkstra's Algorithm implementations and complexity analysis based on content here: + https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm (reused under CC-BY-SA) + - convex hull implementation and complexity analysis based on content here: + https://en.wikipedia.org/wiki/Graham_scan (reused under CC-BY-SA) + - Kruskal's Algorithm implementations and complexity analysis based on content here: + https://en.wikipedia.org/wiki/Kruskal's_algorithm (reused under CC-BY-SA) + - Disjoint sets implementation and complexity analysis based on content here: + https://en.wikipedia.org/wiki/Disjoint-set_data_structure (reused under CC-BY-SA) diff --git a/doc.tex b/doc.tex index 231c016..7d2265a 100644 --- a/doc.tex +++ b/doc.tex @@ -5,6 +5,7 @@ \usepackage{parskip} % replaces paragraph indentation with vertical space \usepackage{float} % used for captions on code listings \usepackage{ifthen} % for conditional compilation +\usepackage[toc,page]{appendix} \newcommand{\ProjRootPrefix}{..} @@ -15,6 +16,10 @@ \newcommand{\TopicHeader}{\subsection} \newcommand{\TopicSubHeader}{\subsubsection} +% values used in includes +\newcommand{\ImplPath}{\ProjRootPrefix/impl/algorithms} +\newcommand{\DsImplPath}{\ProjRootPrefix/impl/structures} + \newcommand{\RefBreak}{ \ifthenelse{\boolean{RefPageBrk}}{\pagebreak}{} } @@ -31,9 +36,17 @@ \pagebreak \section{Algorithms} - \input{\ProjRootPrefix/algorithms/dijkstra/topic.tex} + \input{\ProjRootPrefix/latex/algorithms/dijkstra.tex} + \RefBreak + \input{\ProjRootPrefix/latex/algorithms/hull-2d.tex} \RefBreak - \input{\ProjRootPrefix/algorithms/hull-2d/topic.tex} + \input{\ProjRootPrefix/latex/algorithms/network-flows.tex} + \RefBreak + \input{\ProjRootPrefix/latex/algorithms/kruskals.tex} + \RefBreak - \input{\ProjRootPrefix/algorithms/network-flows/topic.tex} + \begin{appendix} + \section{Data Structures} + \input{\ProjRootPrefix/latex/structures/disjoint-sets.tex} + \end{appendix} \end{document} diff --git a/impl/algorithms/dijkstra/java/Graph.java b/impl/algorithms/dijkstra/java/Graph.java new file mode 100644 index 0000000..eefd961 --- /dev/null +++ b/impl/algorithms/dijkstra/java/Graph.java @@ -0,0 +1,50 @@ +import java.util.*; + +public class Graph { + public Set nodes = new HashSet<>(); + + // only needed for repeated search + public void reset() { + for (Node n : nodes) { + n.seen = false; + n.dist = Long.MAX_VALUE; + n.prev = null; + } + } + + public void search(Node root) { + Set q = new HashSet<>(); + root.dist = 0l; + q.add(root); + + while (!q.isEmpty()) { + Node a = Collections.min(q); + q.remove(a); + if (a.seen) continue; + a.seen = true; + for (Node b : a.adj.keySet()) { + long d = a.dist + a.adj.get(b); + if (d < b.dist) { + b.dist = d; + b.prev = a; + q.add(b); + } + } + } + } +} + +class Node implements Comparable { + public boolean seen; + public long dist = Long.MAX_VALUE; + public Node prev; + public Map adj = new HashMap<>(); + + public boolean equals(Object o) { + return dist == ((Node) o).dist; + } + + public int compareTo(Node n) { + return Long.compare(dist, n.dist); + } +} diff --git a/impl/algorithms/dijkstra/java/Test.java b/impl/algorithms/dijkstra/java/Test.java new file mode 100644 index 0000000..69a1a40 --- /dev/null +++ b/impl/algorithms/dijkstra/java/Test.java @@ -0,0 +1,58 @@ +import java.util.Map; +import java.util.HashMap; +import java.util.Collections; + +public class Test { + public static void main(String[] args) { + Graph graph = new Graph(); + Node a = new Node(); + Node b = new Node(); + Node c = new Node(); + Node d = new Node(); + Node e = new Node(); + Node f = new Node(); + + Collections.addAll(graph.nodes, new Node[] {a,b,c,d,e,f}); + + a.adj.put(c, 6l); + b.adj.put(a, 9l); + c.adj.put(a, 6l); + d.adj.put(b, 2l); + d.adj.put(c, 11l); + e.adj.put(b, 14l); + e.adj.put(d, 9l); + e.adj.put(f, 7l); + f.adj.put(c, 15l); + f.adj.put(d, 10l); + + graph.search(e); + + Map actDists = new HashMap<>(); + for (Node n : graph.nodes) { + actDists.put(n, n.dist); + } + Map expDists = new HashMap<>(); + expDists.put(a, 20l); + expDists.put(b, 11l); + expDists.put(c, 20l); + expDists.put(d, 9l); + expDists.put(e, 0l); + expDists.put(f, 7l); + assert actDists.equals(expDists); + + Map actPrevs = new HashMap<>(); + for (Node n : graph.nodes) { + actPrevs.put(n, n.prev); + } + Map expPrevs = new HashMap<>(); + expPrevs.put(a, b); + expPrevs.put(b, d); + expPrevs.put(c, d); + expPrevs.put(d, e); + expPrevs.put(e, null); + expPrevs.put(f, e); + assert actPrevs.equals(expPrevs); + + System.out.println("Pass"); + } +} diff --git a/impl/algorithms/dijkstra/java/Usage.java b/impl/algorithms/dijkstra/java/Usage.java new file mode 100644 index 0000000..ebf88bb --- /dev/null +++ b/impl/algorithms/dijkstra/java/Usage.java @@ -0,0 +1,29 @@ +import java.util.Collections; + +public class Usage { + public static void main(String[] args) { + Graph graph = new Graph(); + Node a = new Node(); + Node b = new Node(); + Node c = new Node(); + + // add nodes to graph + graph.nodes.add(a); + graph.nodes.add(b); + graph.nodes.add(c); + + a.adj.put(b, 5l); // make an edge from `a' to `b' of weight 5 + b.adj.put(c, 3l); + c.adj.put(a, 10l); + c.adj.put(b, 0l); + + // search the whole graph, starting at node `a' + graph.search(a); + + System.out.println(b.dist); + System.out.println(c.prev == b); + + // be sure to reset the graph before performing another search + graph.reset(); + } +} diff --git a/impl/algorithms/dijkstra/java/makefile b/impl/algorithms/dijkstra/java/makefile new file mode 100644 index 0000000..9ab38df --- /dev/null +++ b/impl/algorithms/dijkstra/java/makefile @@ -0,0 +1,10 @@ +.PHONY: test + +SOURCES := Graph.java Test.java Usage.java + +test: output/Test.class + @java -enableassertions -cp output Test + +output/Test.class: $(SOURCES) + @mkdir -p output + javac -d output Test.java Usage.java diff --git a/impl/algorithms/dijkstra/python3/dijkstra.py b/impl/algorithms/dijkstra/python3/dijkstra.py new file mode 100644 index 0000000..3f8e6db --- /dev/null +++ b/impl/algorithms/dijkstra/python3/dijkstra.py @@ -0,0 +1,24 @@ +import math + +class Node: + def __init__(s, data = None): + s.data = data + s.adj = {} + + def search(s): + dists = {s: 0} + prev = {} + expl = set() + q = {s} + while q: + a = min(q, key = lambda n: dists.get(n, math.inf)) + q.remove(a) + if a not in expl: + expl.add(a) + for n in a.adj: + d = dists[a] + a.adj[n] + if d < dists.get(n, math.inf): + dists[n] = d + prev[n] = a + q.add(n) + return (dists, prev) diff --git a/impl/algorithms/dijkstra/python3/makefile b/impl/algorithms/dijkstra/python3/makefile new file mode 100644 index 0000000..9827325 --- /dev/null +++ b/impl/algorithms/dijkstra/python3/makefile @@ -0,0 +1,6 @@ +.PHONY: test + +SOURCES := dijkstra.py test.py + +test: $(SOURCES) + @python3 test.py diff --git a/impl/algorithms/dijkstra/python3/test.py b/impl/algorithms/dijkstra/python3/test.py new file mode 100644 index 0000000..444637f --- /dev/null +++ b/impl/algorithms/dijkstra/python3/test.py @@ -0,0 +1,35 @@ +from dijkstra import * + +a, b, c, d, e, f = [Node() for _ in range(6)] + +a.adj[c] = 6 +b.adj[a] = 9 +c.adj[a] = 6 +d.adj[b] = 2 +d.adj[c] = 11 +e.adj[b] = 14 +e.adj[d] = 9 +e.adj[f] = 7 +f.adj[c] = 15 +f.adj[d] = 10 + +(dists, prev) = e.search() + +assert dists == { + a: 20, + b: 11, + c: 20, + d: 9, + e: 0, + f: 7, +} + +assert prev == { + a: b, + b: d, + c: d, + d: e, + f: e, +} + +print("Pass") diff --git a/impl/algorithms/dijkstra/python3/usage.py b/impl/algorithms/dijkstra/python3/usage.py new file mode 100644 index 0000000..1556caf --- /dev/null +++ b/impl/algorithms/dijkstra/python3/usage.py @@ -0,0 +1,15 @@ +a = Node() +b = Node('foo bar') +c = Node() +d = Node([1, 2, 3]) +e = Node() + +a.adj[b] = 5 # make an edge from `a' to `b' with weight 5 +b.adj[c] = 5 +c.adj[d] = 5 +a.adj[d] = 30 + +(dists, prev) = a.search() # search the graph outward from `a' + +assert dists[d] == 15 +assert prev[c] == b diff --git a/impl/algorithms/hull-2d/java/Hull.java b/impl/algorithms/hull-2d/java/Hull.java new file mode 100644 index 0000000..6b99aa9 --- /dev/null +++ b/impl/algorithms/hull-2d/java/Hull.java @@ -0,0 +1,49 @@ +import java.util.*; + +public class Hull { + static class Point { + long x; + long y; + Point(long x, long y) { + this.x = x; + this.y = y; + } + } + + static long dir(Point a, Point b, Point c) { + return (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x); + } + static List hull(Set pts) { + if (pts.isEmpty()) { + return new ArrayList<>(); + } + Point s = Collections.min(pts, new Comparator() { + public int compare(Point a, Point b) { + int yCmp = Long.compare(a.y, b.y); + return yCmp == 0 ? Long.compare(a.x, b.x) : yCmp; + } + }); + LinkedList srt = new LinkedList<>(pts); + srt.remove(s); + Collections.sort(srt, new Comparator() { + double cos(Point p) { + return (p.x - s.x) / Math.sqrt((s.x - p.x)*(s.x - p.x) + (s.y - p.y)*(s.y - p.y)); + } + public int compare(Point a, Point b) { + return -Double.compare(cos(a), cos(b)); + } + }); + srt.addFirst(s); + if (srt.size() < 3) { + return srt; + } + LinkedList out = new LinkedList<>(srt.subList(0, 3)); + for (Point p : srt.subList(3, srt.size())) { + while (dir(out.get(out.size() - 2), out.get(out.size() - 1), p) <= 0) { + out.removeLast(); + } + out.add(p); + } + return out; + } +} diff --git a/impl/algorithms/hull-2d/java/Test.java b/impl/algorithms/hull-2d/java/Test.java new file mode 100644 index 0000000..868f782 --- /dev/null +++ b/impl/algorithms/hull-2d/java/Test.java @@ -0,0 +1,16 @@ +import java.util.*; + +public class Test { + public static void main(String[] args) { + Hull.Point a = new Hull.Point(0, 0); + Hull.Point b = new Hull.Point(-2, 2); + Hull.Point c = new Hull.Point(0, 4); + Hull.Point d = new Hull.Point(2, 2); + Hull.Point e = new Hull.Point(0, 2); + + List hull = Hull.hull(new HashSet<>(Arrays.asList(new Hull.Point[] {a, b, c, d, e}))); + assert hull.equals(Arrays.asList(new Hull.Point[] {a, d, c, b})); + + System.out.println("Pass"); + } +} diff --git a/impl/algorithms/hull-2d/java/Usage.java b/impl/algorithms/hull-2d/java/Usage.java new file mode 100644 index 0000000..e045ee6 --- /dev/null +++ b/impl/algorithms/hull-2d/java/Usage.java @@ -0,0 +1,21 @@ +import java.util.*; + +public class Usage { + public static void main(String[] args) { + Hull.Point a = new Hull.Point(0, 3); + Hull.Point b = new Hull.Point(-2, 2); + Hull.Point c = new Hull.Point(1, -6); + + // construct a set of points for input + Set points = new HashSet<>(Arrays.asList(new Hull.Point[] {a, b, c})); + // or manually: + Set points2 = new HashSet<>(); + points2.add(a); + points2.add(b); + points2.add(c); + + List out = Hull.hull(points); + System.out.println(out.get(0) == c); + System.out.println(out.contains(b)); + } +} diff --git a/impl/algorithms/hull-2d/java/makefile b/impl/algorithms/hull-2d/java/makefile new file mode 100644 index 0000000..e950b22 --- /dev/null +++ b/impl/algorithms/hull-2d/java/makefile @@ -0,0 +1,10 @@ +.PHONY: test + +SOURCES := Hull.java Test.java Usage.java + +test: output/Test.class + @java -enableassertions -cp output Test + +output/Test.class: $(SOURCES) + @mkdir -p output + javac -d output Test.java Usage.java diff --git a/impl/algorithms/hull-2d/python3/hull.py b/impl/algorithms/hull-2d/python3/hull.py new file mode 100644 index 0000000..c4ddd9d --- /dev/null +++ b/impl/algorithms/hull-2d/python3/hull.py @@ -0,0 +1,15 @@ +import math + +def hull(points): + if not points: + return [] + sx, sy = s = min(points, key = lambda p: p[::-1]) + cos = lambda p: (p[0] - sx) / math.sqrt((sx - p[0])**2 + (sy - p[1])**2) + dir_ = lambda ax, ay, bx, by, cx, cy: (bx - ax)*(cy - ay) - (by - ay)*(cx - ax) + points = [s] + sorted(points - {s}, key = cos, reverse = True) + stack = points[:3] + for p in points[3:]: + while dir_(*stack[-2], *stack[-1], *p) <= 0: + stack.pop() + stack.append(p) + return stack diff --git a/impl/algorithms/hull-2d/python3/makefile b/impl/algorithms/hull-2d/python3/makefile new file mode 100644 index 0000000..ecb77a0 --- /dev/null +++ b/impl/algorithms/hull-2d/python3/makefile @@ -0,0 +1,6 @@ +.PHONY: test + +SOURCES := hull.py test.py + +test: $(SOURCES) + @python3 test.py diff --git a/impl/algorithms/hull-2d/python3/test.py b/impl/algorithms/hull-2d/python3/test.py new file mode 100644 index 0000000..8976d21 --- /dev/null +++ b/impl/algorithms/hull-2d/python3/test.py @@ -0,0 +1,6 @@ +from hull import * + +points = {(0,0), (-2,2), (0,4), (2,2), (0,2)} +assert hull(points) == [(0,0), (2,2), (0,4), (-2,2)] + +print("Pass") diff --git a/impl/algorithms/hull-2d/python3/usage.py b/impl/algorithms/hull-2d/python3/usage.py new file mode 100644 index 0000000..d8fff87 --- /dev/null +++ b/impl/algorithms/hull-2d/python3/usage.py @@ -0,0 +1,2 @@ +points = {(0,0), (-2,2), (0,4), (2,2), (0,2)} +assert hull(points) == [(0,0), (2,2), (0,4), (-2,2)] diff --git a/impl/algorithms/kruskals/python3/kruskals.py b/impl/algorithms/kruskals/python3/kruskals.py new file mode 100644 index 0000000..805ec0f --- /dev/null +++ b/impl/algorithms/kruskals/python3/kruskals.py @@ -0,0 +1,12 @@ +def min_span_tree(graph): + ds = DisjointSets() + for e in graph: + for n in e: + ds.add(n) + out = set() + for e in sorted(graph, key = lambda e: graph[e]): + a, b = e + if ds.find(a) is not ds.find(b): + out.add(e) + ds.union(a, b) + return out diff --git a/impl/algorithms/kruskals/python3/makefile b/impl/algorithms/kruskals/python3/makefile new file mode 100644 index 0000000..b16805a --- /dev/null +++ b/impl/algorithms/kruskals/python3/makefile @@ -0,0 +1,8 @@ +.PHONY: test + +PP := ../../../structures/disjoint-sets/python3 + +SOURCES := kruskals.py test.py $(PP)/disjoint_sets.py + +test: $(SOURCES) + @PYTHONPATH=$(PP) python3 test.py diff --git a/impl/algorithms/kruskals/python3/test.py b/impl/algorithms/kruskals/python3/test.py new file mode 100644 index 0000000..886052f --- /dev/null +++ b/impl/algorithms/kruskals/python3/test.py @@ -0,0 +1,32 @@ +from kruskals import min_span_tree +from disjoint_sets import DisjointSets +import kruskals + +kruskals.DisjointSets = DisjointSets + +_graph = { + ('a', 'b'): 7, + ('a', 'd'): 5, + ('b', 'c'): 8, + ('b', 'd'): 9, + ('b', 'e'): 7, + ('c', 'e'): 5, + ('d', 'e'): 15, + ('d', 'f'): 6, + ('e', 'f'): 8, + ('e', 'g'): 9, + ('f', 'g'): 11, +} +graph = {frozenset(k): v for (k, v) in _graph.items()} + +exp = set(map(frozenset, [ + ('a', 'b'), + ('a', 'd'), + ('b', 'e'), + ('c', 'e'), + ('d', 'f'), + ('e', 'g'), +])) + +assert min_span_tree(graph) == exp +print("Pass") diff --git a/impl/algorithms/kruskals/python3/usage.py b/impl/algorithms/kruskals/python3/usage.py new file mode 100644 index 0000000..ee9eb72 --- /dev/null +++ b/impl/algorithms/kruskals/python3/usage.py @@ -0,0 +1,10 @@ +graph = { + frozenset(["a", "b"]): 10, + frozenset(["b", "c"]): 12, + frozenset(["a", "c"]): 50, +} + +tree = min_span_tree(graph) +assert frozenset(["b", "c"]) in tree +assert frozenset(["a", "c"]) not in tree +total_cost = sum(graph[e] for e in tree) diff --git a/impl/algorithms/network-flows/Ek.java b/impl/algorithms/network-flows/Ek.java new file mode 100644 index 0000000..f043e76 --- /dev/null +++ b/impl/algorithms/network-flows/Ek.java @@ -0,0 +1,47 @@ +import java.util.*; + +public class Ek { + static class Node { + public Set edges = new HashSet<>(); + + public static Edge connect(Node from, Node to, long cap) { + Edge e = new Edge(from, to, cap); + from.edges.add(e); + to.edges.add(e); + return e; + } + } + + static class Edge { + public final Node from, to; + public final long cap; + public long flow; + + public Edge(Node from, Node to, long cap) { + self.from = from; + self.to = to; + self.cap = cap; + self.flow = 0; + } + public long capTo(Edge node) { + return node.equals(from) ? flow : cap - flow; + } + public void addTo(Node to, long amt) { + flow += node.equals(from) ? -amt : amt; + } + public Node other(Node node) { + return node.equals(from) ? to : from; + } + } + + static class EkResult { + public long val; + public Set cutSrc; + } + + static + + public static EkResult ek(Set nodes, Node src, Node sink) { + + } +} diff --git a/impl/algorithms/network-flows/java-junk/FlowNet.java b/impl/algorithms/network-flows/java-junk/FlowNet.java new file mode 100644 index 0000000..de0f1b6 --- /dev/null +++ b/impl/algorithms/network-flows/java-junk/FlowNet.java @@ -0,0 +1,117 @@ +import java.util.*; + +class FlowNet { + Map nodes = new HashMap<>(); + long value; + Node source, sink; + + void addNode(Node n) { + nodes.put(n, false); + } + + Edge edgeFor(Node one, Node two) { + return one.edgeTo.containsKey(two) ? one.edgeTo.get(two) : two.edgeTo.get(one); + } + + long findMax() { + long value = 0; + + AP augPath = getPath(); + while (augPath != null) { + Node from, to = augPath.path.pop(); + while (!augPath.path.isEmpty()) { + from = to; + to = augPath.path.pop(); + edgeFor(from, to).sendTo(to, augPath.value); + } + value += augPath.value; + augPath = getPath(); + } + + return value; + } + + // for min cut + Set srcSide() { + Set srcSide = new HashSet<>(); + for (Node n : nodes.keySet()) { + if (nodes.get(n)) { + srcSide.add(n); + } + } + return srcSide; + } + + AP getPath() { + Map prev = new HashMap<>(); + Queue q = new LinkedList<>(); + q.offer(source); + prev.put(source, null); + while (!q.isEmpty()) { + Node n = q.poll(); + for (Node other : n.adj) { + Edge e = edgeFor(n, other); + if (e.capTo(other) > 0 && !prev.containsKey(other)) { + prev.put(other, n); + q.offer(other); + } + } + } + for (Node n : nodes.keySet()) { + nodes.put(n, prev.keySet().contains(n)); + } + + if (nodes.get(sink)) { + LinkedList path = new LinkedList<>(); + long value = Long.MAX_VALUE; + + Node n = sink, p = prev.get(n); + path.push(n); + while (p != null) { + value = Math.min(value, edgeFor(p, n).capTo(n)); + path.push(p); + n = p; + p = prev.get(p); + } + + AP ap = new AP(); + ap.path = path; + ap.value = value; + return ap; + } else { + return null; + } + } + + class AP { + LinkedList path; + long value; + } +} + +class Node { + Set adj = new HashSet<>(); + Map edgeTo = new HashMap<>(); +} + +class Edge { + Node from, to; + long cap, flow; + + Edge(Node f, Node t, long c) { + from = f; + to = t; + cap = c; + f.adj.add(t); + t.adj.add(f); + f.edgeTo.put(t, this); + } + + long capTo(Node n) { + return n == to ? cap - flow : flow; + } + + void sendTo(Node n, long amt) { + flow += n == to ? amt : -amt; + } +} diff --git a/impl/algorithms/network-flows/java-junk/Main.java b/impl/algorithms/network-flows/java-junk/Main.java new file mode 100644 index 0000000..1410824 --- /dev/null +++ b/impl/algorithms/network-flows/java-junk/Main.java @@ -0,0 +1,28 @@ +public class Main { + public static void main(String[] args) { + FlowNet net = new FlowNet(); + Node s = new Node(); + Node t = new Node(); + Node u = new Node(); + Node v = new Node(); + for (Node n : new Node[] {s,t,u,v}) { + net.addNode(n); + } + net.source = s; + net.sink = t; + new Edge(s, u, 20); + new Edge(s, v, 10); + new Edge(u, v, 30); + new Edge(u, t, 10); + new Edge(v, t, 20); + System.out.println(net.findMax()); + for (Node n : net.srcSide()) { + char name = 0; + if (n.equals(s)) name = 's'; + if (n.equals(t)) name = 't'; + if (n.equals(u)) name = 'u'; + if (n.equals(v)) name = 'v'; + System.out.println(name); + } + } +} diff --git a/impl/algorithms/network-flows/java-junk/usage b/impl/algorithms/network-flows/java-junk/usage new file mode 100644 index 0000000..008896d --- /dev/null +++ b/impl/algorithms/network-flows/java-junk/usage @@ -0,0 +1,22 @@ +Usage: + + FlowNet net = new FlowNet(); + + Node s = new Node(); + Node t = new Node(); + Node u = new Node(); + Node v = new Node(); + for (Node n : new Node[] {s,t,u,v}) { + net.addNode(n); + } + net.source = s; + net.sink = t; + + new Edge(s, u, 20); + new Edge(s, v, 10); + new Edge(u, v, 30); + new Edge(u, t, 10); + new Edge(v, t, 20); + + System.out.println(net.findMax()); + System.out.println(net.srcSide()); diff --git a/impl/algorithms/network-flows/python3/flows.py b/impl/algorithms/network-flows/python3/flows.py new file mode 100644 index 0000000..eaa97bf --- /dev/null +++ b/impl/algorithms/network-flows/python3/flows.py @@ -0,0 +1,68 @@ +from collections import deque + +class Edge: + def __init__(s, frm, to, cap): + s.frm = frm + s.to = to + s.cap = cap + s.flow = 0 + + def cap_to(s, node): + return {s.frm: s.flow, s.to: s.cap - s.flow}[node] + + def add_to(s, node, amt): + s.flow += {s.frm: -amt, s.to: amt}[node] + + def other(s, node): + return {s.frm: s.to, s.to: s.frm}[node] + +class Node: + def __init__(s, data = None): + s.data = data + s.edges = set() + + @staticmethod + def connect(f, t, cap): + e = Edge(f, t, cap) + f.edges.add(e) + t.edges.add(e) + return e + +def ek(nodes, src, sink): + def path(): + prev = {} + q = deque([src]) + while q: + n = q.popleft() + for e in n.edges: + o = e.other(n) + if o not in prev and e.cap_to(o) > 0: + prev[o] = e + q.append(o) + d = sink + p = deque() + while d in prev and d is not src: + p.appendleft((prev[d], d)) + d = prev[d].other(d) + return p if d is src else [] + + p = path() + val = 0 + while p: + amt = min(e.cap_to(d) for e, d in p) + val += amt + for e, d in p: + e.add_to(d, amt) + p = path() + + cut_src = set() + w = [src] + while w: + n = w.pop() + cut_src.add(n) + for e in n.edges: + o = e.other(n) + if o not in cut_src and e.cap_to(o) != 0: + w.append(o) + + return (val, cut_src) diff --git a/impl/algorithms/network-flows/python3/makefile b/impl/algorithms/network-flows/python3/makefile new file mode 100644 index 0000000..ef1e50a --- /dev/null +++ b/impl/algorithms/network-flows/python3/makefile @@ -0,0 +1,6 @@ +.PHONY: test + +SOURCES := flows.py test.py + +test: $(SOURCES) + @python3 test.py diff --git a/impl/algorithms/network-flows/python3/test.py b/impl/algorithms/network-flows/python3/test.py new file mode 100644 index 0000000..7c9d54d --- /dev/null +++ b/impl/algorithms/network-flows/python3/test.py @@ -0,0 +1,21 @@ +from flows import * + +src, a, b, c, d, e, f, sink = nodes = [Node() for _ in range(8)] + +src_to_a = Node.connect(src, a, 4) +Node.connect(src, b, 4) +Node.connect(src, c, 2) +Node.connect(a, d, 3) +Node.connect(b, e, 3) +Node.connect(c, e, 3) +Node.connect(c, f, 1) +Node.connect(d, sink, 4) +Node.connect(e, sink, 5) +Node.connect(f, sink, 5) + +val, cut_src = ek(nodes, src, sink) + +assert val == 8 +assert src_to_a.cap_to(src) == 3 + +print("Pass") diff --git a/impl/algorithms/network-flows/python3/usage.py b/impl/algorithms/network-flows/python3/usage.py new file mode 100644 index 0000000..2c6a33e --- /dev/null +++ b/impl/algorithms/network-flows/python3/usage.py @@ -0,0 +1,15 @@ +# pass a value here to store data in the node (optional) +src = Node('foo') +a = Node('bar') +b = Node() +sink = Node(1234) + +Node.edge(src, a, 5) # add an edge from `src' to `a' with capacity 5 +Node.edge(a, b, 3) +edge = Node.edge(b, sink, 6) # can capture an edge object for later use + +val, cut_src = ek(nodes, src, sink) + +assert val == 3 # flow value + +print(edge.cap_to(b)) # amount of flow from `b' to `sink' in the saturated network diff --git a/impl/dijkstra/java/Graph.java b/impl/dijkstra/java/Graph.java deleted file mode 100644 index eefd961..0000000 --- a/impl/dijkstra/java/Graph.java +++ /dev/null @@ -1,50 +0,0 @@ -import java.util.*; - -public class Graph { - public Set nodes = new HashSet<>(); - - // only needed for repeated search - public void reset() { - for (Node n : nodes) { - n.seen = false; - n.dist = Long.MAX_VALUE; - n.prev = null; - } - } - - public void search(Node root) { - Set q = new HashSet<>(); - root.dist = 0l; - q.add(root); - - while (!q.isEmpty()) { - Node a = Collections.min(q); - q.remove(a); - if (a.seen) continue; - a.seen = true; - for (Node b : a.adj.keySet()) { - long d = a.dist + a.adj.get(b); - if (d < b.dist) { - b.dist = d; - b.prev = a; - q.add(b); - } - } - } - } -} - -class Node implements Comparable { - public boolean seen; - public long dist = Long.MAX_VALUE; - public Node prev; - public Map adj = new HashMap<>(); - - public boolean equals(Object o) { - return dist == ((Node) o).dist; - } - - public int compareTo(Node n) { - return Long.compare(dist, n.dist); - } -} diff --git a/impl/dijkstra/java/Test.java b/impl/dijkstra/java/Test.java deleted file mode 100644 index 69a1a40..0000000 --- a/impl/dijkstra/java/Test.java +++ /dev/null @@ -1,58 +0,0 @@ -import java.util.Map; -import java.util.HashMap; -import java.util.Collections; - -public class Test { - public static void main(String[] args) { - Graph graph = new Graph(); - Node a = new Node(); - Node b = new Node(); - Node c = new Node(); - Node d = new Node(); - Node e = new Node(); - Node f = new Node(); - - Collections.addAll(graph.nodes, new Node[] {a,b,c,d,e,f}); - - a.adj.put(c, 6l); - b.adj.put(a, 9l); - c.adj.put(a, 6l); - d.adj.put(b, 2l); - d.adj.put(c, 11l); - e.adj.put(b, 14l); - e.adj.put(d, 9l); - e.adj.put(f, 7l); - f.adj.put(c, 15l); - f.adj.put(d, 10l); - - graph.search(e); - - Map actDists = new HashMap<>(); - for (Node n : graph.nodes) { - actDists.put(n, n.dist); - } - Map expDists = new HashMap<>(); - expDists.put(a, 20l); - expDists.put(b, 11l); - expDists.put(c, 20l); - expDists.put(d, 9l); - expDists.put(e, 0l); - expDists.put(f, 7l); - assert actDists.equals(expDists); - - Map actPrevs = new HashMap<>(); - for (Node n : graph.nodes) { - actPrevs.put(n, n.prev); - } - Map expPrevs = new HashMap<>(); - expPrevs.put(a, b); - expPrevs.put(b, d); - expPrevs.put(c, d); - expPrevs.put(d, e); - expPrevs.put(e, null); - expPrevs.put(f, e); - assert actPrevs.equals(expPrevs); - - System.out.println("Pass"); - } -} diff --git a/impl/dijkstra/java/Usage.java b/impl/dijkstra/java/Usage.java deleted file mode 100644 index ebf88bb..0000000 --- a/impl/dijkstra/java/Usage.java +++ /dev/null @@ -1,29 +0,0 @@ -import java.util.Collections; - -public class Usage { - public static void main(String[] args) { - Graph graph = new Graph(); - Node a = new Node(); - Node b = new Node(); - Node c = new Node(); - - // add nodes to graph - graph.nodes.add(a); - graph.nodes.add(b); - graph.nodes.add(c); - - a.adj.put(b, 5l); // make an edge from `a' to `b' of weight 5 - b.adj.put(c, 3l); - c.adj.put(a, 10l); - c.adj.put(b, 0l); - - // search the whole graph, starting at node `a' - graph.search(a); - - System.out.println(b.dist); - System.out.println(c.prev == b); - - // be sure to reset the graph before performing another search - graph.reset(); - } -} diff --git a/impl/dijkstra/java/makefile b/impl/dijkstra/java/makefile deleted file mode 100644 index 9ab38df..0000000 --- a/impl/dijkstra/java/makefile +++ /dev/null @@ -1,10 +0,0 @@ -.PHONY: test - -SOURCES := Graph.java Test.java Usage.java - -test: output/Test.class - @java -enableassertions -cp output Test - -output/Test.class: $(SOURCES) - @mkdir -p output - javac -d output Test.java Usage.java diff --git a/impl/dijkstra/python3/dijkstra.py b/impl/dijkstra/python3/dijkstra.py deleted file mode 100644 index 3f8e6db..0000000 --- a/impl/dijkstra/python3/dijkstra.py +++ /dev/null @@ -1,24 +0,0 @@ -import math - -class Node: - def __init__(s, data = None): - s.data = data - s.adj = {} - - def search(s): - dists = {s: 0} - prev = {} - expl = set() - q = {s} - while q: - a = min(q, key = lambda n: dists.get(n, math.inf)) - q.remove(a) - if a not in expl: - expl.add(a) - for n in a.adj: - d = dists[a] + a.adj[n] - if d < dists.get(n, math.inf): - dists[n] = d - prev[n] = a - q.add(n) - return (dists, prev) diff --git a/impl/dijkstra/python3/makefile b/impl/dijkstra/python3/makefile deleted file mode 100644 index 9827325..0000000 --- a/impl/dijkstra/python3/makefile +++ /dev/null @@ -1,6 +0,0 @@ -.PHONY: test - -SOURCES := dijkstra.py test.py - -test: $(SOURCES) - @python3 test.py diff --git a/impl/dijkstra/python3/test.py b/impl/dijkstra/python3/test.py deleted file mode 100644 index 444637f..0000000 --- a/impl/dijkstra/python3/test.py +++ /dev/null @@ -1,35 +0,0 @@ -from dijkstra import * - -a, b, c, d, e, f = [Node() for _ in range(6)] - -a.adj[c] = 6 -b.adj[a] = 9 -c.adj[a] = 6 -d.adj[b] = 2 -d.adj[c] = 11 -e.adj[b] = 14 -e.adj[d] = 9 -e.adj[f] = 7 -f.adj[c] = 15 -f.adj[d] = 10 - -(dists, prev) = e.search() - -assert dists == { - a: 20, - b: 11, - c: 20, - d: 9, - e: 0, - f: 7, -} - -assert prev == { - a: b, - b: d, - c: d, - d: e, - f: e, -} - -print("Pass") diff --git a/impl/dijkstra/python3/usage.py b/impl/dijkstra/python3/usage.py deleted file mode 100644 index 1556caf..0000000 --- a/impl/dijkstra/python3/usage.py +++ /dev/null @@ -1,15 +0,0 @@ -a = Node() -b = Node('foo bar') -c = Node() -d = Node([1, 2, 3]) -e = Node() - -a.adj[b] = 5 # make an edge from `a' to `b' with weight 5 -b.adj[c] = 5 -c.adj[d] = 5 -a.adj[d] = 30 - -(dists, prev) = a.search() # search the graph outward from `a' - -assert dists[d] == 15 -assert prev[c] == b diff --git a/impl/hull-2d/java/Hull.java b/impl/hull-2d/java/Hull.java deleted file mode 100644 index 6b99aa9..0000000 --- a/impl/hull-2d/java/Hull.java +++ /dev/null @@ -1,49 +0,0 @@ -import java.util.*; - -public class Hull { - static class Point { - long x; - long y; - Point(long x, long y) { - this.x = x; - this.y = y; - } - } - - static long dir(Point a, Point b, Point c) { - return (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x); - } - static List hull(Set pts) { - if (pts.isEmpty()) { - return new ArrayList<>(); - } - Point s = Collections.min(pts, new Comparator() { - public int compare(Point a, Point b) { - int yCmp = Long.compare(a.y, b.y); - return yCmp == 0 ? Long.compare(a.x, b.x) : yCmp; - } - }); - LinkedList srt = new LinkedList<>(pts); - srt.remove(s); - Collections.sort(srt, new Comparator() { - double cos(Point p) { - return (p.x - s.x) / Math.sqrt((s.x - p.x)*(s.x - p.x) + (s.y - p.y)*(s.y - p.y)); - } - public int compare(Point a, Point b) { - return -Double.compare(cos(a), cos(b)); - } - }); - srt.addFirst(s); - if (srt.size() < 3) { - return srt; - } - LinkedList out = new LinkedList<>(srt.subList(0, 3)); - for (Point p : srt.subList(3, srt.size())) { - while (dir(out.get(out.size() - 2), out.get(out.size() - 1), p) <= 0) { - out.removeLast(); - } - out.add(p); - } - return out; - } -} diff --git a/impl/hull-2d/java/Test.java b/impl/hull-2d/java/Test.java deleted file mode 100644 index 868f782..0000000 --- a/impl/hull-2d/java/Test.java +++ /dev/null @@ -1,16 +0,0 @@ -import java.util.*; - -public class Test { - public static void main(String[] args) { - Hull.Point a = new Hull.Point(0, 0); - Hull.Point b = new Hull.Point(-2, 2); - Hull.Point c = new Hull.Point(0, 4); - Hull.Point d = new Hull.Point(2, 2); - Hull.Point e = new Hull.Point(0, 2); - - List hull = Hull.hull(new HashSet<>(Arrays.asList(new Hull.Point[] {a, b, c, d, e}))); - assert hull.equals(Arrays.asList(new Hull.Point[] {a, d, c, b})); - - System.out.println("Pass"); - } -} diff --git a/impl/hull-2d/java/Usage.java b/impl/hull-2d/java/Usage.java deleted file mode 100644 index e045ee6..0000000 --- a/impl/hull-2d/java/Usage.java +++ /dev/null @@ -1,21 +0,0 @@ -import java.util.*; - -public class Usage { - public static void main(String[] args) { - Hull.Point a = new Hull.Point(0, 3); - Hull.Point b = new Hull.Point(-2, 2); - Hull.Point c = new Hull.Point(1, -6); - - // construct a set of points for input - Set points = new HashSet<>(Arrays.asList(new Hull.Point[] {a, b, c})); - // or manually: - Set points2 = new HashSet<>(); - points2.add(a); - points2.add(b); - points2.add(c); - - List out = Hull.hull(points); - System.out.println(out.get(0) == c); - System.out.println(out.contains(b)); - } -} diff --git a/impl/hull-2d/java/makefile b/impl/hull-2d/java/makefile deleted file mode 100644 index e950b22..0000000 --- a/impl/hull-2d/java/makefile +++ /dev/null @@ -1,10 +0,0 @@ -.PHONY: test - -SOURCES := Hull.java Test.java Usage.java - -test: output/Test.class - @java -enableassertions -cp output Test - -output/Test.class: $(SOURCES) - @mkdir -p output - javac -d output Test.java Usage.java diff --git a/impl/hull-2d/python3/hull.py b/impl/hull-2d/python3/hull.py deleted file mode 100644 index c4ddd9d..0000000 --- a/impl/hull-2d/python3/hull.py +++ /dev/null @@ -1,15 +0,0 @@ -import math - -def hull(points): - if not points: - return [] - sx, sy = s = min(points, key = lambda p: p[::-1]) - cos = lambda p: (p[0] - sx) / math.sqrt((sx - p[0])**2 + (sy - p[1])**2) - dir_ = lambda ax, ay, bx, by, cx, cy: (bx - ax)*(cy - ay) - (by - ay)*(cx - ax) - points = [s] + sorted(points - {s}, key = cos, reverse = True) - stack = points[:3] - for p in points[3:]: - while dir_(*stack[-2], *stack[-1], *p) <= 0: - stack.pop() - stack.append(p) - return stack diff --git a/impl/hull-2d/python3/makefile b/impl/hull-2d/python3/makefile deleted file mode 100644 index ecb77a0..0000000 --- a/impl/hull-2d/python3/makefile +++ /dev/null @@ -1,6 +0,0 @@ -.PHONY: test - -SOURCES := hull.py test.py - -test: $(SOURCES) - @python3 test.py diff --git a/impl/hull-2d/python3/test.py b/impl/hull-2d/python3/test.py deleted file mode 100644 index 8976d21..0000000 --- a/impl/hull-2d/python3/test.py +++ /dev/null @@ -1,6 +0,0 @@ -from hull import * - -points = {(0,0), (-2,2), (0,4), (2,2), (0,2)} -assert hull(points) == [(0,0), (2,2), (0,4), (-2,2)] - -print("Pass") diff --git a/impl/hull-2d/python3/usage.py b/impl/hull-2d/python3/usage.py deleted file mode 100644 index d8fff87..0000000 --- a/impl/hull-2d/python3/usage.py +++ /dev/null @@ -1,2 +0,0 @@ -points = {(0,0), (-2,2), (0,4), (2,2), (0,2)} -assert hull(points) == [(0,0), (2,2), (0,4), (-2,2)] diff --git a/impl/network-flows/java-junk/FlowNet.java b/impl/network-flows/java-junk/FlowNet.java deleted file mode 100644 index de0f1b6..0000000 --- a/impl/network-flows/java-junk/FlowNet.java +++ /dev/null @@ -1,117 +0,0 @@ -import java.util.*; - -class FlowNet { - Map nodes = new HashMap<>(); - long value; - Node source, sink; - - void addNode(Node n) { - nodes.put(n, false); - } - - Edge edgeFor(Node one, Node two) { - return one.edgeTo.containsKey(two) ? one.edgeTo.get(two) : two.edgeTo.get(one); - } - - long findMax() { - long value = 0; - - AP augPath = getPath(); - while (augPath != null) { - Node from, to = augPath.path.pop(); - while (!augPath.path.isEmpty()) { - from = to; - to = augPath.path.pop(); - edgeFor(from, to).sendTo(to, augPath.value); - } - value += augPath.value; - augPath = getPath(); - } - - return value; - } - - // for min cut - Set srcSide() { - Set srcSide = new HashSet<>(); - for (Node n : nodes.keySet()) { - if (nodes.get(n)) { - srcSide.add(n); - } - } - return srcSide; - } - - AP getPath() { - Map prev = new HashMap<>(); - Queue q = new LinkedList<>(); - q.offer(source); - prev.put(source, null); - while (!q.isEmpty()) { - Node n = q.poll(); - for (Node other : n.adj) { - Edge e = edgeFor(n, other); - if (e.capTo(other) > 0 && !prev.containsKey(other)) { - prev.put(other, n); - q.offer(other); - } - } - } - for (Node n : nodes.keySet()) { - nodes.put(n, prev.keySet().contains(n)); - } - - if (nodes.get(sink)) { - LinkedList path = new LinkedList<>(); - long value = Long.MAX_VALUE; - - Node n = sink, p = prev.get(n); - path.push(n); - while (p != null) { - value = Math.min(value, edgeFor(p, n).capTo(n)); - path.push(p); - n = p; - p = prev.get(p); - } - - AP ap = new AP(); - ap.path = path; - ap.value = value; - return ap; - } else { - return null; - } - } - - class AP { - LinkedList path; - long value; - } -} - -class Node { - Set adj = new HashSet<>(); - Map edgeTo = new HashMap<>(); -} - -class Edge { - Node from, to; - long cap, flow; - - Edge(Node f, Node t, long c) { - from = f; - to = t; - cap = c; - f.adj.add(t); - t.adj.add(f); - f.edgeTo.put(t, this); - } - - long capTo(Node n) { - return n == to ? cap - flow : flow; - } - - void sendTo(Node n, long amt) { - flow += n == to ? amt : -amt; - } -} diff --git a/impl/network-flows/java-junk/Main.java b/impl/network-flows/java-junk/Main.java deleted file mode 100644 index 1410824..0000000 --- a/impl/network-flows/java-junk/Main.java +++ /dev/null @@ -1,28 +0,0 @@ -public class Main { - public static void main(String[] args) { - FlowNet net = new FlowNet(); - Node s = new Node(); - Node t = new Node(); - Node u = new Node(); - Node v = new Node(); - for (Node n : new Node[] {s,t,u,v}) { - net.addNode(n); - } - net.source = s; - net.sink = t; - new Edge(s, u, 20); - new Edge(s, v, 10); - new Edge(u, v, 30); - new Edge(u, t, 10); - new Edge(v, t, 20); - System.out.println(net.findMax()); - for (Node n : net.srcSide()) { - char name = 0; - if (n.equals(s)) name = 's'; - if (n.equals(t)) name = 't'; - if (n.equals(u)) name = 'u'; - if (n.equals(v)) name = 'v'; - System.out.println(name); - } - } -} diff --git a/impl/network-flows/java-junk/usage b/impl/network-flows/java-junk/usage deleted file mode 100644 index 008896d..0000000 --- a/impl/network-flows/java-junk/usage +++ /dev/null @@ -1,22 +0,0 @@ -Usage: - - FlowNet net = new FlowNet(); - - Node s = new Node(); - Node t = new Node(); - Node u = new Node(); - Node v = new Node(); - for (Node n : new Node[] {s,t,u,v}) { - net.addNode(n); - } - net.source = s; - net.sink = t; - - new Edge(s, u, 20); - new Edge(s, v, 10); - new Edge(u, v, 30); - new Edge(u, t, 10); - new Edge(v, t, 20); - - System.out.println(net.findMax()); - System.out.println(net.srcSide()); diff --git a/impl/network-flows/python3/flows.py b/impl/network-flows/python3/flows.py deleted file mode 100644 index a904a32..0000000 --- a/impl/network-flows/python3/flows.py +++ /dev/null @@ -1,68 +0,0 @@ -from collections import deque - -class Edge: - def __init__(s, frm, to, cap): - s.frm = frm - s.to = to - s.cap = cap - s.flow = 0 - - def cap_to(s, node): - return {s.frm: s.flow, s.to: s.cap - s.flow}[node] - - def add_to(s, node, amt): - s.flow += {s.frm: -amt, s.to: amt}[node] - - def other(s, node): - return {s.frm: s.to, s.to: s.frm}[node] - -class Node: - def __init__(s, data = None): - s.data = data - s.edges = set() - - @staticmethod - def edge(f, t, cap): - e = Edge(f, t, cap) - f.edges.add(e) - t.edges.add(e) - return e - -def ek(nodes, src, sink): - def path(): - prev = {} - q = deque([src]) - while q: - n = q.popleft() - for e in n.edges: - o = e.other(n) - if o not in prev and e.cap_to(o) > 0: - prev[o] = e - q.append(o) - d = sink - p = deque() - while d in prev and d is not src: - p.appendleft((prev[d], d)) - d = prev[d].other(d) - return p if d is src else [] - - p = path() - val = 0 - while p: - amt = min(e.cap_to(d) for e, d in p) - val += amt - for e, d in p: - e.add_to(d, amt) - p = path() - - cut_src = set() - w = [src] - while w: - n = w.pop() - cut_src.add(n) - for e in n.edges: - o = e.other(n) - if o not in cut_src and e.cap_to(o) != 0: - w.append(o) - - return (val, cut_src) diff --git a/impl/network-flows/python3/makefile b/impl/network-flows/python3/makefile deleted file mode 100644 index ef1e50a..0000000 --- a/impl/network-flows/python3/makefile +++ /dev/null @@ -1,6 +0,0 @@ -.PHONY: test - -SOURCES := flows.py test.py - -test: $(SOURCES) - @python3 test.py diff --git a/impl/network-flows/python3/test.py b/impl/network-flows/python3/test.py deleted file mode 100644 index d68b85d..0000000 --- a/impl/network-flows/python3/test.py +++ /dev/null @@ -1,21 +0,0 @@ -from flows import * - -src, a, b, c, d, e, f, sink = nodes = [Node() for _ in range(8)] - -src_to_a = Node.edge(src, a, 4) -Node.edge(src, b, 4) -Node.edge(src, c, 2) -Node.edge(a, d, 3) -Node.edge(b, e, 3) -Node.edge(c, e, 3) -Node.edge(c, f, 1) -Node.edge(d, sink, 4) -Node.edge(e, sink, 5) -Node.edge(f, sink, 5) - -val, cut_src = ek(nodes, src, sink) - -assert val == 8 -assert src_to_a.cap_to(src) == 3 - -print("Pass") diff --git a/impl/network-flows/python3/usage.py b/impl/network-flows/python3/usage.py deleted file mode 100644 index 2c6a33e..0000000 --- a/impl/network-flows/python3/usage.py +++ /dev/null @@ -1,15 +0,0 @@ -# pass a value here to store data in the node (optional) -src = Node('foo') -a = Node('bar') -b = Node() -sink = Node(1234) - -Node.edge(src, a, 5) # add an edge from `src' to `a' with capacity 5 -Node.edge(a, b, 3) -edge = Node.edge(b, sink, 6) # can capture an edge object for later use - -val, cut_src = ek(nodes, src, sink) - -assert val == 3 # flow value - -print(edge.cap_to(b)) # amount of flow from `b' to `sink' in the saturated network diff --git a/impl/structures/disjoint-sets/python3/disjoint_sets.py b/impl/structures/disjoint-sets/python3/disjoint_sets.py new file mode 100644 index 0000000..32c5071 --- /dev/null +++ b/impl/structures/disjoint-sets/python3/disjoint_sets.py @@ -0,0 +1,26 @@ +class Node: + pass + +class DisjointSets(dict): + def add(self, val): + if val not in self: + self[val] = n = Node() + n.val = val + n.parent = None + n.size = 1 + + def find(self, val): + n = self[val] + while n.parent: + newp = n.parent.parent or n.parent + n.parent = newp + n = newp + return n + + def union(self, v1, v2): + r1 = self.find(v1) + r2 = self.find(v2) + if r1 != r2: + ch, p = sorted([r1, r2], key = lambda n: n.size) + ch.parent = p + p.size += ch.size diff --git a/impl/structures/disjoint-sets/python3/usage.py b/impl/structures/disjoint-sets/python3/usage.py new file mode 100644 index 0000000..86612b4 --- /dev/null +++ b/impl/structures/disjoint-sets/python3/usage.py @@ -0,0 +1,10 @@ +# Make a new disjoint sets instance +ds = DisjointSets() + +# Add singleton values to the structure +ds.add("my value") +ds.add(0) + +# Union and Find operations +ds.union("my value", 0) +assert ds.find("my value") is ds.find(0) diff --git a/latex/algorithms/dijkstra.tex b/latex/algorithms/dijkstra.tex new file mode 100644 index 0000000..17f3e4d --- /dev/null +++ b/latex/algorithms/dijkstra.tex @@ -0,0 +1,43 @@ +{ + \newcommand{\ThisImpl}{\ImplPath/dijkstra} + + \TopicHeader{Weighted Shortest Path: Dijkstra's Agorithm} + + Dijkstra's algorithm is useful for computing the shortest path between nodes in a graph where edges have nonnegative weights. + The worst-case performance of the algorithm is $O(E + V \log V)$, if the graph has $E$ edges and $V$ nodes. + + All implementations here provide as output, for each node, the following information: + \begin{itemize} + \item whether the node was reachable from the search root + \item the shortest distance between the root and the node + \item the previous node along the shortest path, i.e. the node that was being explored when this node was first found + \end{itemize} + Note that the path of nodes from the root to any reachable node can be computed by following the previous-node links iteratively until the search root is reached. + + The implementations use directed graphs, so in order to emulate searching an undirected graph, two directed edges going in opposite directions should be made for each edge in the desired undirected graph. + + \TopicSubHeader{Python 3} + + \inputminted{python}{\ThisImpl/python3/dijkstra.py} + + The \texttt{Node} constructor accepts a \texttt{data} parameter. + Any value can be passed in, and it will be stored in the node to facilitate associating the nodes with data specific to the problem. + Here's a usage example: + + \inputminted{python}{\ThisImpl/python3/usage.py} + + In this implementation, two dictionaries are returned which provide the distance and previous-node information. + To see if a node is reachable, simply check whether the node is a key in the first (distance) dictionary. + + \TopicSubHeader{Java} + + \centerline{\texttt{Graph.java}} + \inputminted{java}{\ThisImpl/java/Graph.java} + + The code may be used as follows. + If you need to attach some data to the nodes, you could add a field to the \texttt{Node} class or use a \texttt{Map} to keep track of the association. + + \inputminted{java}{\ThisImpl/java/Usage.java} + + The visited flag, previous-node links, and distances are accessible as fields on the \texttt{Node} objects after the search. +} diff --git a/latex/algorithms/hull-2d.tex b/latex/algorithms/hull-2d.tex new file mode 100644 index 0000000..2f6f010 --- /dev/null +++ b/latex/algorithms/hull-2d.tex @@ -0,0 +1,30 @@ +{ + \newcommand{\ThisImpl}{\ImplPath/hull-2d} + + \TopicHeader{Convex Hull (2D)} + + Convex hull algorithms are given a collection of points and output a subset of just the outermost points. + Formally, the output is the points of a convex polygon within which lie all points in the input. + + The implementations here use a technique called Graham scan to compute the convex hull with time complexity $O(n \log n)$, where $n$ is the number of input points. + The output points are ordered: the bottom-most (and then leftmost) point appears first, then the remaining hull points in counterclockwise order. + + \TopicSubHeader{Java} + + \centerline{\texttt{Hull.java}} + \inputminted{java}{\ThisImpl/java/Hull.java} + + This may be used as follows: + + \inputminted{java}{\ThisImpl/java/Usage.java} + + \TopicSubHeader{Python 3} + + \inputminted{python}{\ThisImpl/python3/hull.py} + + To run the algorithm, pass a set of $(x, y)$ pairs (\texttt{tuple}s) to \texttt{hull}. + + A sample usage: + + \inputminted{python}{\ThisImpl/python3/usage.py} +} diff --git a/latex/algorithms/kruskals.tex b/latex/algorithms/kruskals.tex new file mode 100644 index 0000000..74b9a4a --- /dev/null +++ b/latex/algorithms/kruskals.tex @@ -0,0 +1,42 @@ +{ + \newcommand{\ThisImpl}{\ImplPath/kruskals} + + \TopicHeader{Minimum Spanning Tree: Kruskal's Algorithm} + + Kruskal's algorithm finds a spanning tree in a weighted undirected graph (or a collection of + spanning trees if the graph isn't connected) whose total edge cost is minimal. The algorithm + runs in $O(E \log V)$ (or equivalently $O(E \log E)$) time, where $V$ is the number of + vertices and $E$ the number of edges in the graph. + + \TopicSubHeader{Python 3} + + \inputminted{python}{\ThisImpl/python3/kruskals.py} + + \paragraph{Note} This implementation uses the disjoint sets type defined on page + \pageref{structures-disjoint-sets-python3}. + + The input for this algorithm is a graph, and in this implementation the graph is + represented by a dictionary mapping edges to their costs. The cost values are just + numbers, but the edges are iterables of two nodes. Because the edges need to be used as + dictionary keys, they need to be hashable, and because the graph is undirected the + programmer should be careful not to accidentally look up the edge $(b, a)$ when only + $(a, b)$ is present. In Python, the simplest way to accomplish this is to use immutable + sets, so that the order in which the two nodes are listed within an edge doesn't make a + difference. Tuples also work, but this approach requires the programmer to be consistent + about the order of nodes within each edge. + + The nodes can be of any type, as long as hashing and equality behave correctly. Many + primitive types will work fine, as well as typical class types you define. + + The algorithm returns a set of edges, that is, a subset of the keys in the input + dictionary. The costs of the edges can then be looked up using the graph dictionary + passed to the implementation. + + Here's a simple usage example: + + \inputminted{python}{\ThisImpl/python3/usage.py} + + \TopicSubHeader{Java} + + Coming soon\ldots +} diff --git a/latex/algorithms/network-flows.tex b/latex/algorithms/network-flows.tex new file mode 100644 index 0000000..7806098 --- /dev/null +++ b/latex/algorithms/network-flows.tex @@ -0,0 +1,26 @@ +{ + \newcommand{\ThisImpl}{\ImplPath/network-flows} + + \TopicHeader{Network Flows: Edmonds--Karp} + + Network flows algorithms find the maximum flow through and the minimum cut of a flow network. + This terminology and relevant algorithms are covered in Algorithms (CS 280) at Oberlin. + + The implementations here use the Edmonds--Karp algorithm on flow networks with integer capacities. + The time complexity of the algorithm is $O\left( VE^2 \right)$, where $V$ and $E$ are the number of vertices and edges in the network, respectively. + + \TopicSubHeader{Java} + + Coming soon... + + \TopicSubHeader{Python 3} + + \inputminted{python}{\ThisImpl/python3/flows.py} + + To use the code: + + \inputminted{python}{\ThisImpl/python3/usage.py} + + The \texttt{cut\_src} variable here is a set of node objects representing the source side of the minimum cut. + If the set \texttt{n} contains all nodes in the network, the sink side is computed by \texttt{n - cut\_src}. +} diff --git a/latex/structures/disjoint-sets.tex b/latex/structures/disjoint-sets.tex new file mode 100644 index 0000000..768a125 --- /dev/null +++ b/latex/structures/disjoint-sets.tex @@ -0,0 +1,25 @@ +{ + \newcommand{\ThisImpl}{\DsImplPath/disjoint-sets} + + \TopicHeader{Disjoint Sets} + + This data structure is a forest of disjoint sets with path halving and union by size. The + \textit{union} and \textit{find} operations are essentially constant-time amortized. + + \TopicSubHeader{Python 3} \label{structures-disjoint-sets-python3} + + \inputminted{python}{\ThisImpl/python3/disjoint_sets.py} + + Basic usage: + + \inputminted{python}{\ThisImpl/python3/usage.py} + + The values in the sets may be of any type, as long as they are hashable. The data + structures uses hashing and equality testing to locate nodes. Generally, this means you + can safely use immutable built-in types or your own custom object types as values in the + sets. + + \TopicSubHeader{Java} \label{structures-disjoint-sets-java} + + Coming soon\ldots +} diff --git a/makefile b/makefile index af0e338..0e6560b 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,5 @@ # These are more than the files we really need to depend on, but keep things simple. -ALG_FILES := $(shell find algorithms -type f) +ALG_FILES := $(shell find latex -type f) IMPL_FILES := $(shell find impl -type f) BUILD_CMD := ( cd output && pdflatex -shell-escape ../doc.tex ) diff --git a/todo b/todo index 903d8cc..2bdb5a9 100644 --- a/todo +++ b/todo @@ -1,5 +1,5 @@ -- add tests for implementations and automate - implement configurable sections (select specific algorithms) +- implement configurable languages - copy useful stuff from https://en.wikibooks.org/wiki/Algorithm_implementation - DFS, BFS, Bellman Ford, area of polygons, minimum spanning tree, brute force TSP - canonical problems (24: ecna17 twentyfour, coin problem) diff --git a/util/run_all_tests.py b/util/run_all_tests.py index 148e18b..2caeb6c 100644 --- a/util/run_all_tests.py +++ b/util/run_all_tests.py @@ -1,7 +1,7 @@ import subprocess from pathlib import Path -for alg in Path('impl').iterdir(): +for alg in Path('impl', 'algorithms').iterdir(): if alg.is_dir(): for lang in alg.iterdir(): if lang.is_dir() and lang.joinpath('makefile').exists():