Oct 5, 2022
A graph is a pair $(V, E)$ where
Example: A computer network
(edges store ping, nodes store addresses)
Proof: Each edge is counted twice
In a directed graph with no self-loops and no parallel edges:
$$m \leq n(n-1)$$
$n$ choices for the first vertex; $(n-1)$ choices for the second vertex. $$m \leq n(n-1)$$
Hey, isn't this a data structures class?
trait Graph[V, E] {
def vertices: Iterator[Vertex]
def edges: Iterator[Edge]
def addVertex(label: V): Vertex
def addEdge(orig: Vertex, dest: Vertex, label: E): Edge
def removeVertex(vertex: Vertex): Unit
def removeEdge(edge: Edge): Unit
}
trait Vertex[V, E] {
def outEdges: Seq[Edge]
def inEdges: Seq[Edge]
def incidentEdges: Iterator[Edge] = outEdges ++ inEdges
def edgeTo(v: Vertex): Boolean
def label: V
}
trait Edge[V, E] {
def origin: Vertex
def destination: Vertex
def label: E
}
Data Model
class DirectedGraphV1[V, E] extends Graph[V, E]
{
val vertices = mutable.Buffer[Vertex]()
val edges = mutable.Buffer[Edge]()
/* ... */
}
def addVertex(label: V): Vertex =
vertices.append(new Vertex(label))
What's the complexity?
def addEdge(orig: Vertex, dest: Vertex, label: E): Edge =
edges.append(new Edge(orig, dest, label))
What's the complexity?
def removeEdge(edge: Edge): Unit =
edges.subtractOne(edge)
What's the complexity? ($O(n)$)
Data Model
class DoublyLinkedList[T] extends Seq[T] {
def append(element: T): Node =
/* O(1) with tail pointer */
def remove(node: Node): Unit =
/* O(1) */
def iterator: Iterator[T]: Unit =
/* O(1) + O(1) per call to next */
}
class DirectedGraphV2[V, E] extends Graph[V, E] {
val vertices = DoublyLinkedList[Vertex]()
class Vertex(label: V) = {
var node: DoublyLinkedList[Vertex].Node = null
/* ... */
}
def addVertex(label: V): Vertex = {
val vertex = new Vertex(label)
val node = vertices.append(vertex)
vertex.node = node
return vertex
}
/* ... */
}
What's the complexity?
class DirectedGraphV2[V, E] extends Graph[V, E] {
val edges = DoublyLinkedList[Edge]()
class Edge(orig: Vertex, dest: Vertex, label: E) = {
var node: DoublyLinkedList[Edge].Node = null
/* ... */
}
def addEdge(orig: Vertex, dest: Vertex, label: E): Vertex = {
val edge = new Edge(orig, dest, label)
val node = edges.append(vertex)
edge.node = node
return edge
}
/* ... */
}
What's the complexity?
class DirectedGraphV2[V, E] extends Graph[V, E] {
val edges = DoublyLinkedList[Edge]()
def removeEdge(edge: Edge): Unit = {
edges.remove(edge.node)
}
/* ... */
}
What's the complexity?
class DirectedGraphV2[V, E] extends Graph[V, E] {
val vertices = DoublyLinkedList[Vertex]()
def removeVertex(vertex: Vertex): Unit = {
vertices.remove(vertex.node)
}
/* ... */
}
What if there's an edge to/from vertex?
class DirectedGraphV2[V, E] extends Graph[V, E] {
val vertices = DoublyLinkedList[Vertex]()
def removeVertex(vertex: Vertex): Unit = {
vertices.remove(vertex.node)
for(edge <- vertex.incidentEdges){
removeEdge(edge)
}
}
/* ... */
}
What's the complexity? ($O(1) + O(T_{incidentEdges}(n, m))$)
class DirectedGraphV2[V, E] extends Graph[V, E] {
val vertices = DoublyLinkedList[Vertex]()
val edges = DoublyLinkedList[Edge]()
class Vertex(label: V) = {
/* ... */
def outEdges =
vertices.filter { _.orig = this }
def inEdges =
vertices.filter { _.dest = this }
}
/* ... */
}
What's the complexity? ($O(m) = O(n^2)$)
Idea: Store the in/out edges for each vertex.
class DirectedGraphV3[V, E] extends Graph[V, E] {
val vertices = DoublyLinkedList[Vertex]()
class Vertex(label: V) = {
var node: DoublyLinkedList[Vertex].Node = null
val inEdges = DoublyLinkedList[Edge]()
val outEdges = DoublyLinkedList[Edge]()
/* ... */
}
/* ... */
}
class DirectedGraphV3[V, E] extends Graph[V, E] {
/* ... */
def addEdge(orig: Vertex, dest: Vertex, label: E): Vertex = {
val edge = new Edge(orig, dest, label)
val node = edges.append(vertex)
edge.node = node
orig.outEdges.append(edge)
dest.inEdges.append(edge)
return edge
}
/* ... */
}
What's the complexity?
class DirectedGraphV3[V, E] extends Graph[V, E] {
/* ... */
def removeEdge(edge: Edge): Unit = {
edges.remove(edge.node)
edge.orig.outEdges.subtractOne(edge)
edge.dest.inEdges.subtractOne(edge)
}
/* ... */
}
What's the complexity?
class DirectedGraphV4[V, E] extends Graph[V, E] {
/* ... */
class Edge(orig: Vertex, dest: Vertex, label: E) = {
var node: DoublyLinkedList[Edge].Node = null
var origNode: DoublyLinkedList[Edge].Node = null
var destNode: DoublyLinkedList[Edge].Node = null
/* ... */
}
/* ... */
}
class DirectedGraphV4[V, E] extends Graph[V, E] {
/* ... */
def addEdge(orig: Vertex, dest: Vertex, label: E): Vertex = {
val edge = new Edge(orig, dest, label)
val node = edges.append(vertex)
edge.node = node
edge.origNode = orig.outEdges.append(edge)
edge.destNode = dest.inEdges.append(edge)
return edge
}
/* ... */
}
What's the complexity?
class DirectedGraphV4[V, E] extends Graph[V, E] {
/* ... */
def removeEdge(edge: Edge): Unit = {
edges.remove(edge.node)
edge.orig.outEdges.remove(edge.origNode)
edge.dest.inEdges.remove(edge.destNode)
}
/* ... */
}
What's the complexity?
class DirectedGraphV4[V, E] extends Graph[V, E] {
/* ... */
def removeVertex(vertex: Vertex): Unit = {
vertices.remove(vertex.node)
for(edge <- vertex.incidentEdges){
removeEdge(edge)
}
}
/* ... */
}
What's the complexity?