330 likes | 339 Views
Learn about graph representation in Java programming, including nodes, edges, and implementation concepts. Understand how to create mutable graphs and maintain invariants to ensure data integrity.
E N D
Lecture 7: A Tale of Two Graphs David Evans http://www.cs.virginia.edu/~evans CS201j: Engineering Software University of Virginia Computer Science
Graph ADT public class Graph { // OVERVIEW: // A Graph is a mutable type that // represents an undirected // graph. It consists of nodes that are // named by Strings, and edges that // connect a pair of nodes. // A typical Graph is: // < Nodes, Edges > // where // Nodes = { n1, n2, …, nm } // and // Edges = { {from_1, to_1}, // …, {from_n, to_n} } B D A C Nodes = { A, B, C, D } Edges = { { A, B }, { A, C }, { B, C }, { A, D } } { … } means its a set – order doesn’t matter CS 201J Fall 2002
B Representation Ideas D • Set of Nodes, Set of Edges e.g., Nodes = { A, B, C, D } Edges = { <A, B>, <A, C>, <A, D>, <B, C> } • Set of Nodes and Neighbors e.g., Graph = { <A, {B, C, D}>, <B, {A, C}>, <C, {A, B}>, <D, {A}> } Each entry is pair of node name, and names of nodes it is connected to. A C CS 201J Fall 2002
Representation Ideas • Set of Nodes and Matrix of booleans e.g., Nodes = [ A, B, C, D ] Edges = [ [ 0 1 1 1 ] [ 1 0 1 0 ] [ 1 1 0 0 ] [ 1 0 0 0 ] ] B No edge from A to A D A C Edge from B to C CS 201J Fall 2002
Implementation 1 class Edge { // OVERVIEW: Record type for representing an edge. String node1, node 2; Edge (String n1, String n2) { node1 = n1; node2 = n2; } } class Graph { // OVERVIEW: A Graph is a mutable type that represents an … Vector nodes; // A Vector of String objects Vector edges; // A Vector of Edge object … } CS 201J Fall 2002
class Edge { String node1, node 2; } class Graph { Vector nodes; // A Vector of String objects Vector edges; // A Vector of Edge object … } Rep Invariant Function from rep to boolean RI (c) = c.nodes != null && c.edges != null && !c.nodes.containsNull && !c.edges.containsNull && elements of c.nodes are String objects && elements of c.edges are Edge objects && no duplicates in c.nodes && no duplicates in c.edges && every node mentioned in c.edges is also in c.nodes Is this precise enough? CS 201J Fall 2002
Rep Invariant RI (c) = c.nodes != null && c.edges != null && !c.nodes.containsNull && !c.edges.containsNull && elements of c.nodes are String objects && elements of c.edges are Edge objects && no duplicates in c.nodes // No duplicate edges, node1/node2 are interchangable: && ((c.edges[i].node1 = c.edges[j].node1 && c.edges[i].node2 = c.edges[j].node2) || (c.edges[i].node1 = c.edges[j].node2 && c.edges[i].node2 = c.edges[j].node1)) i == j && every node mentioned in c.edges is also in c.nodes CS 201J Fall 2002
Abstraction Function public class Graph { // OVERVIEW: // A Graph is a mutable type that // represents an undirected // graph. It consists of nodes that are // named by Strings, and edges that // connect a pair of nodes. // A typical Graph is: // < Nodes, Edges > // where // Nodes = { n1, n2, …, nm } // and // Edges = { {from_1, to_1}, // …, {from_n, to_n} } • Function from rep to abstract notion (use notation from overview) AF (c) = < Nodes, Edges > where … CS 201J Fall 2002
class Edge { String node1, node 2; } class Graph { Vector nodes; // A Vector of String objects Vector edges; // A Vector of Edge object … } Abstraction Function AF (c) = < Nodes, Edges > where Nodes = { c.nodes[i] | 0 <= i < c.nodes.size () } The set of nodes is the elements of the c.nodes Vector Edges = { { c.edges[i].node1, c.edges[i].node2 } | 0 <= i < c.edges.size () } The set of edges is the elements of the c.edges Vector CS 201J Fall 2002
class Edge { String node1, node 2; } class Graph { Vector nodes; // A Vector of String objects Vector edges; // A Vector of Edge object … } Implementing Constructor • public Graph () • // EFFECTS: Initializes this to a graph with no nodes or • // edges: < {}, {} >. • nodes = new Vector (); • edges = new Vector (); • } • How do we know this satisfies the rep invariant? CS 201J Fall 2002
class Edge { String node1, node 2; } class Graph { Vector nodes; // A Vector of String objects Vector edges; // A Vector of Edge object … } Implementing addNode • public void addNode (String name) { • // REQUIRES: name is not the name of a node in this • // MODIFIES: this • // EFFECTS: adds a node named name to this: • // this_post = < this_pre.nodes U { name }, this_pre.edges > • nodes.addElement (name); • } • How do we know this still satisfies the rep invariant? CS 201J Fall 2002
class Edge { String node1, node 2; } class Graph { Vector nodes; // A Vector of String objects Vector edges; // A Vector of Edge object … } Implementing addEdge • public void addEdge (String fnode, String tnode) • // REQUIRES: fnode and tnode are names of nodes in this. • // MODIFIES: this • // EFFECTS: Adds an edge from fnode to tnode to this: • // this_post = < this_pre.nodes, • // this_pre.edges U { {fnode, tnode} } > • edges.addElement (new Edge (fnode, tnode)); • } • How do we know this still satisfies the rep invariant? • Would • edges.addElement (new Edge (tnode, fnode)); • be correct? CS 201J Fall 2002
class Edge { String node1, node 2; } class Graph { Vector nodes; // A Vector of String objects Vector edges; // A Vector of Edge object … } Implementing getNeighbors • public StringSet getNeighbors (String node) • // REQUIRES: node is a node in this • // EFFECTS: Returns the StringSet consisting of all nodes in this • // that are directly connected to node: • // \result = { n | {node, n} is in this.edges • StringSet res = new StringSet (); • Enumeration edgeenum = edges.elements (); • while (edgeenum.hasMoreElements ()) { • Edge e = (Edge) edgeenum.nextElement (); • if (e.node1.equals (node)) { res.insert (e.node2); } • else if (e.node2.equals (node)) { res.insert (e.node1); } • } CS 201J Fall 2002
B Representation Ideas D • Set of Nodes, Set of Edges e.g., Nodes = { A, B, C, D } Edges = { <A, B>, <A, C>, <A, D>, <B, C> } • Set of Nodes and Neighbors e.g., Graph = { <A, {B, C, D}>, <B, {A, C}>, <C, {A, B}>, <D, {A}> } Each entry is pair of node name, and names of nodes it is connected to. A C CS 201J Fall 2002
Implementation 2 class NodeNeighbors { // OVERVIEW: Record type for representing an edge. String node; StringSet neighbors; // A Set of String objects NodeNeighbors (String n) { node = n; neighbors = new StringSet (); } } class Graph { // OVERVIEW: A Graph is a mutable type that represents an … Vector nodes; // A Vector of NodeNeighbors objects … } CS 201J Fall 2002
class NodeNeighbors { String node; StringSet neighbors; } class Graph { Vector nodes; // A Vector of NodeNeighbors objects } Rep Invariant Function from rep to boolean RI (c) = c.nodes != null && !c.nodes.containsNull && elements of c.nodes are NodeNeighbors objects && no duplicates in c.nodes && for each node in c.nodes, each node in c.nodes[i].neighbors is a node in c.nodes c.nodes[i].neighbors does not contain duplicates CS 201J Fall 2002
class NodeNeighbors { String node; StringSet neighbors; } class Graph { Vector nodes; // A Vector of NodeNeighbors objects } Abstraction Function AF (c) = < Nodes, Edges > where Nodes = { c.nodes[i].node | 0 <= i < c.nodes.size () } The set of nodes is the elements of the c.nodes Vector Edges = { { c.nodes[i].node, c.nodes[i].neighbors[e] } | 0 <= i < c.nodes.size (), 0 <= e <= c.nodes[i].neighbors.size () } CS 201J Fall 2002
class NodeNeighbors { String node; Vector neighbors; // A Vector of String objects } class Graph { Vector nodes; // A Vector of NodeNeighbors objects } Implementing Constructor • public Graph () • // EFFECTS: Initializes this to a graph with no nodes or • // edges: < {}, {} >. • nodes = new Vector (); • } CS 201J Fall 2002
class NodeNeighbors { String node; StringSet neighbors; } class Graph { Vector nodes; // A Vector of NodeNeighbors objects } Implementing addNode • public void addNode (String name) { • // REQUIRES: name is not the name of a node in this • // MODIFIES: this • // EFFECTS: adds a node named name to this: • // this_post = < this_pre.nodes U { name }, this_pre.edges > • nodes.addElement (new NodeNeighbors (name)); • } • How do we know this still satisfies the rep invariant? CS 201J Fall 2002
class NodeNeighbors { String node; StringSet neighbors; } class Graph { Vector nodes; // A Vector of NodeNeighbors objects } Implementing addEdge • public void addEdge (String fnode, String tnode) • // REQUIRES: fnode and tnode are names of nodes in this. • // MODIFIES: this • // EFFECTS: Adds an edge from fnode to tnode to this: • // this_post = < this_pre.nodes, • // this_pre.edges U { {fnode, tnode} } > • NodeNeighbors n1 = lookupNode (fnode); • NodeNeighbors n2 = lookupNode (tnode); • n1.neighbors.insert (tnode); • n2.neighbors.insert (fnode); • } We need to implement lookupNode also. • How do we know this still satisfies the rep invariant? CS 201J Fall 2002
class NodeNeighbors { String node; StringSet neighbors; } class Graph { Vector nodes; // A Vector of NodeNeighbors objects } Implementing getNeighbors • public StringSet getNeighbors (String node) • // REQUIRES: node is a node in this • // EFFECTS: Returns the StringSet consisting of all nodes in this • // that are directly connected to node: • // \result = { n | {node, n} is in this.edges • NodeNeighbors n = lookupNode (node); • return n.neighbors; • } Almost…but we have exposed our rep! CS 201J Fall 2002
Rep Exposure What if client does this? Graph g = new Graph (); g.addNode (“A”); g.addNode (“B”); g.addEdge (“A”, “B”); StringSet neighbors = g.getNeighbors (“A”); neighbors.insert (“C”); Does the rep invariant for g still hold? CS 201J Fall 2002
Rep Exposure • If mutable components of the representation are accessible to clients, the implementation exposes the rep! • Clients can mutate the representation directly – without using data type operations Why is this bad? CS 201J Fall 2002
Problems with Rep Exposure • Client mutations could break the rep invariant • Client code may break if ADT implementation changes • No longer possible to reason about the invariant being true by just checking the ADT implementation CS 201J Fall 2002
class NodeNeighbors { String node; StringSet neighbors; } class Graph { Vector nodes; // A Vector of NodeNeighbors objects } Implementing getNeighbors • public StringSet getNeighbors (String node) • // REQUIRES: node is a node in this • // EFFECTS: Returns the StringSet consisting of all nodes in this • // that are directly connected to node: • // \result = { n | {node, n} is in this.edges • NodeNeighbors n = lookupNode (node); • } • return n.neighbors; • return n.neighbors.copy (); If we return a copy, the client doesn’t have access to the actual neighbors object in the representation. CS 201J Fall 2002
Which implementation is better? • Depends what we care about • Code complexity • Normally the most important criteria • Nodes/Edges: getNeighbors is harder • NodeNeighbors: toString is harder, addEdge a little harder • Memory Use • Nodes/Edges: 2 vectors, each edge requires 2 strings • NodeNeighbors: 1 vector, number of nodes StringSets, each edge requires 1 string CS 201J Fall 2002
Which implementation is better? • Performance • Both have poor performance: linear search through all the nodes to find one • NodeNeighbors getNeighbors does less work • Other methods Nodes/Edges usually less work • If we expect clients to call getNeighbors a lot, NodeNeighbors might be better CS 201J Fall 2002
Performance Comparison > time java GraphTest // Using Nodes/Edges impl 1.220u 0.020s 0:01.25 99.2% > time java GraphTest // Using NodeNeighbors impl 0.660u 0.040s 0:00.79 88.6% Very rough comparison…but NodeNeighbors appears to be twice as fast for this test case. What is the test case doing? CS 201J Fall 2002
GraphTest.java public class GraphTest { static public void main (String args[]) { Graph g = new Graph (); int numnodes = 1000; for (int i = 0; i < numnodes; i++) { g.addNode ("node" + i); } for (int i = 0; i < numnodes - 1; i++) { g.addEdge ("node" + i, "node" + (i + 1)); } for (int i = 0; i < numnodes - 2; i++) { g.addEdge ("node" + i, "node" + (i + 2)); } for (int i = 0; i < numnodes; i++) { StringSet neighbors = g.getNeighbors ("node" + i); } } } CS 201J Fall 2002
Picking Implementations Focus on complexity of implementation Your time is more valuable than the computer’s! CS 201J Fall 2002
PS4 - Design • PS2: you used an ADT we provided • PS3: you implemented an ADT • PS4: you will design and implement ADTs to solve a problem • First part: just design (due Thursday) • Second part: implementation (due Oct 3) CS 201J Fall 2002
Design • Break a problem into components • Abstract data types • Each component should: • Make sense on its own • Be independent from other components • Be small enough to understand and implement • This is hard. Designing well takes lots of practice. CS 201J Fall 2002
Charge • PS4: Design Document • Due next Thursday • No code to write yet – just think carefully about your design • Tuesday: design, modular dependency diagrams (read Ch 13) • Wednesday, 8pm: AC’s will hold recitation on useful programming techniques CS 201J Fall 2002