From a65c19be295bff1d93f4c8f68210bb6329805f0c Mon Sep 17 00:00:00 2001 From: mercury Date: Wed, 3 Dec 2003 17:38:51 +0000 Subject: [PATCH] Initial revision --- ClearGraph.jpg | Bin 0 -> 3932 bytes Graph.java | 535 +++++++++++++++++++++++++++++++++++++++ GraphDriver.java | 28 ++ GraphException.java | 11 + GraphGUI.java | 93 +++++++ GraphInterface.java | 142 +++++++++++ GraphNode.java | 81 ++++++ GraphScreen.java | 267 +++++++++++++++++++ KeyedItem.java | 38 +++ Node.java | 39 +++ QueueException.java | 7 + QueueInterface.java | 57 +++++ QueueReferenceBased.java | 92 +++++++ Quit.jpg | Bin 0 -> 3432 bytes ShortestPath.jpg | Bin 0 -> 4164 bytes 15 files changed, 1390 insertions(+) create mode 100644 ClearGraph.jpg create mode 100644 Graph.java create mode 100644 GraphDriver.java create mode 100644 GraphException.java create mode 100644 GraphGUI.java create mode 100644 GraphInterface.java create mode 100644 GraphNode.java create mode 100644 GraphScreen.java create mode 100644 KeyedItem.java create mode 100644 Node.java create mode 100644 QueueException.java create mode 100644 QueueInterface.java create mode 100644 QueueReferenceBased.java create mode 100644 Quit.jpg create mode 100644 ShortestPath.jpg diff --git a/ClearGraph.jpg b/ClearGraph.jpg new file mode 100644 index 0000000000000000000000000000000000000000..000aa37adcc90a02cf788d8d65bd48a91bc15467 GIT binary patch literal 3932 zcmcIlc|4R`A3tL@+h7<@jl69nHOP17Zn9QP?v1Ut3LW@>XT}y-_$&yf6 zyU~J3MTL~*CLxv7ywA*N@qX@o@B7dDo6kAVbAIdhoZoZ4!|&q11akH^L>mATg8@6} z2l#K%w>JB;d2FsfJCILpyFpMLpA@mWMgfn;B#8I|EbSh1-Rj8YST>!Clq5(u_yb$B& z>_A`#(>R=2M?nWgw)rR*2Av{`4uX`T-3!bB;GjW;h9s~A3_t@UzyY?L0`;vq*FXWLAQF1juF)!L$KTg1QC&0`LIF z|Et?G3S=y(Y%&c{V+67MXCMwpi;Ig(h)YXINMofWrLc;!($cbucm)MT1qD1-S}3y* z(c(X67)DA8BZHBXk&(m6$jIOXlMGH2g8g3-@NWUEICP{`IBW?J!@}TL7{3QP05rcI zW)7i5!h`|=76=?70|XL<7MGBef-d-6BMgYazZ>NM3;~0SA;geqlsFPDZ2%duaKvJH zJ*0&bNny#ZJ^Coz(Tw8ts%nbPx5);EoY+=Nw0ermSnqoZ*Jxhm)xArtj=55;YqNO9 zN^Nd^wDFR-y7r*{2^)>$+54`YsDJco@a0fO7ktz!miRJJr4>pe z%t~uJO`kuG>gR(sK20~}tHKAFEy312(-U3VMlse?)o+=t(m}8%qe?D(JJzq^a|EHQ>XeNw`bbX zi4X2gC#vSXUZB;Gc>|tLQydK-BV**=wtgOR#8DRX-IR1Nxa-{e@^a}l!^x`V0%kJC zzvT0kkH!1m=MFxjq`k>+$i|x;YW6LAIna|F`5>>WFMqh^hPF{goh=SQO}cJo1`MUNxy zg_maOrTVKaSuU6g$%k!3g;sdsv&padM71OK z0v|r$KM`@@;`v)>94_>SNT9Hpfg~QCOlQz}VFWtMmo2zRZqR_32mo1N0Z|AWkRdW8 zfXxsIvH=eoT;Ur;C_v#KJP5!T=ztZ1greFgY5UFAwgjgjlAt7{2WTM&L-qJSIcS|A zK*&L2KLifF!4c35-5G>HkSL^3P$)E79F0avNJvUTTs2ou31P$UK z6dDdqVqBOQ9E$)*h=eShPzof7gmC&tGti164(K(QM;=q-#J*RzG;~SHyh`EL8d+%^ z%PKKea;5U>+WN$o?v;?lE5{wTw#mM>rQL1W{PF$?0T0oF_MH%M(ce(fO1rK8p$AT1 zthm!P_({{0=s^!lNX@(c$CIJYTC40mcZMHK%dfoqba-0CS#TtDoG64i9M1efn-)kX z2r+8co}>C1#bdbJt-Y%26`jeP_XcW)ma+3vT+}luS8LJSHlsePy-KdfAcn>(FEzHF zuR-OxnT%(Z)GgC&@1KY}o_%dDWSd|3^H=(ugZaNeo=bBxe9%|e;E{W3X3uL!{jQDq zU6y_U7c2v{8}ssBcn(dCZ2cBNRvBrd?$ES}36qF&bO!2NXnHqeX%b6EC!&~O-BouXPM`u;t zdQ8mKul7D;VmI*mX333%p#u|b_wiXrz8X~WfniM4N2Pfs3mw|)78NYQ@IktS;)_3) zw1jAO6%@PkABsBQ^Mp+&yfMvcP~)n+TCOOAB z4BnF4v96ojmoOc&`cSodbLE@klW);B4=>L&<#d0}br>&gJfA(>x|4muCMEauyF!hF z%cqhSgq-VXeRyRPaqJN%qkAX_uh(}-Z)N(?#&u@iVly6gd(3`&^~%g-n+;_j911OxspuWD+AXakfl1w*KY+UVbKAqM-g+u--!`|rzz#G_ z1U>zB|B(t@MUQ%P|LO)^sgLR29J8hFUrzU{n8AnjMs}~|&fJ^e4u`uRsjAKyZuK5a z^E)xQH;lrN)7t4&J7kjYWJg>v};uwGDTVSWzvDcUor&gV+5_SeXTqDhJ;|AAA{Rw;K8u{m8 z%*-ve>8lo>)LXQ|J%f6jwuBz5KudIA1 zyJ_uph5L!jkc$Y6Lby(dFFF(D;CI?Vx#n8cxXtn}t76VONw`A?b^~072cnf*-(>tfDN3(LnvOLGl(9yAqO4CJua&Pd(a}=6t)uJIJ!&$dYvPg9ZaL3k zYiX!HHC`qPA1EIhVHX&#dd+%~qumRu{Qe2uyYCA9%T}43W?nKoRxn%>9LXgb?(cca zrm*u857^;|UVX{pU2QQcIok?i29iiC^z1z<;_XkhQz|CEvK&^+y1d*Jwky>y&TvNm zb!cW}ghmzTs7hU5OYioL!CHU1@mocAXxqUXih?zCuUrSB9isMCO-7YY!$$%mcON#- z{~8ci6Td3L?9+65K((n$jklSjbjR4%nvv&456(t-s=xeHJ5ryu{eEw8gO7V%G~$dV JTa=9d + * Implements: GraphInterface.
+ * @author Matt Markham
+ */ + +public class Graph implements GraphInterface +{ + //size integer variable to keep track of number of nodes in the graph + private int size; + //Double two-dimensional array for the adjacency matrix + private double[][] adjacent; + //boolean to determine whether a graph is directed + private boolean directed; + //Vertex storage list + private ArrayList vertexList; + //int to determin generic unweighted value + private final int UNWEIGHTED_VALUE = 1; + //temporary Arraylist for dfs + private ArrayList tempAL=new ArrayList(); + //ArrayList containing the current shortest path + private ArrayList path; + + +/** + * Default Constructor, creates an undirected graph.
+ */ + + public Graph () + { + size = 0; + adjacent = new double[size][size]; + vertexList = new ArrayList(); + directed=false; + path = new ArrayList(); + } + + +/** + * Constructor, takes a boolean to determine directed or undirected.
+ */ + + public Graph (boolean param) + { + size = 0; + adjacent = new double[size][size]; + vertexList = new ArrayList(); + directed=param; + path = new ArrayList(); + } + + public void makeEmpty() + { + size=0; + adjacent = new double[size][size]; + vertexList.clear(); + } + + public boolean isEmpty() + { + return (size==0); + } + + + public int numVertices() + { + return size; + } + + + public int numEdges() + { + int c=0; + for(int x=0; x + * Preconditions: must be passed a new size value.
+ * Postconditions: resizes adjacency matrix.
+ * Throws: None. + */ + + private void resizeAdjacent ( int size ) + { + + double[][] temp = new double[size][size]; + + for( int x = 0; x < size - 1; x++) + + for ( int y = 0; y < size - 1; y++ ) + + temp[x][y] = adjacent[x][y]; + + for ( int x = 0; x < size; x++ ) + { + + temp[x][size - 1] = Double.POSITIVE_INFINITY; + + temp[size - 1][x] = Double.POSITIVE_INFINITY; + + } + + adjacent = temp; + } + + public void addEdge(Comparable searchKey1, Comparable searchKey2, double weight) throws GraphException + { + for(int x=0; xsize) + throw new GraphException ("index out of bounds!"); + else + return (GraphNode)vertexList.get(index); + } + + public GraphNode getVertex(Comparable searchKey) throws GraphException + { + for (int x=0;x + * Preconditions: Must be passed searchKey.
+ * Postconditions: returns integer index of given searchKey.
+ * Throws:Nonde. + */ + + private int findVertex (Comparable searchKey) + { + for (int d=0; d + * Preconditions: None.
+ * Postconditions: Makes all vertices unmarked.
+ * Throws:None. + */ + + private void clearMarks() + { + for (int x=0;x index ) + newrow = row - 1; + if ( col > index ) + newcol = col - 1; + temp[newrow][newcol] = adjacent[row][col]; + } + } + } + adjacent = temp; + size--; + return (GraphNode) vertexList.remove ( index ); + } + + public ArrayList dfs (Comparable searchKey) throws GraphException + { + ArrayList dfsList = new ArrayList(); + dfsList=dfsRec(getVertex(searchKey), dfsList); + clearMarks(); + return dfsList; + } + + +/** + * Private recursive method called to generate a breadth-first search.
+ * Preconditions: Must be passed a graphNode and an ArrayList.
+ * Postconditions: Returns an Arraylist for the bfs.
+ * Throws:None. + */ + + private ArrayList dfsRec(GraphNode vertex, ArrayList searchRecList) + { + searchRecList.add(vertex); + vertex.setMarked(true); + int i=vertexList.indexOf(vertex); + for(int j=0; j + * Preconditions: A Graphics object and the radius of the vertecies.
+ * Postconditions: Draws all of the neccessary components onto the GraphScreen.
+ * @author Andrew Coleman
+ */ + public void draw ( Graphics g, int radius ) + { + /* draw all of the edges in orange */ + g.setColor ( Color.orange ); + for ( int i = 0; i < adjacent.length; i++ ) + { + int firstx = getVertex ( i ).getX(); + int firsty = getVertex ( i ).getY(); + String firstkey = getVertex ( i ).getKey().toString(); + for ( int j = 0; j < adjacent[i].length; j++ ) + { + if ( adjacent[i][j] != Double.POSITIVE_INFINITY ) + { + int secondx = ((GraphNode) getVertex ( j )).getX(); + int secondy = ((GraphNode) getVertex ( j )).getY(); + String secondkey = (String)((GraphNode) getVertex ( j )).getKey(); + DecimalFormat format = new DecimalFormat(); + format.setMaximumFractionDigits ( 3 ); + int difference = 10; + /* the final x and y coordinates for the string for each edge */ + int xcoord, ycoord; + if ( firstx < secondx ) + xcoord = (secondx - firstx) / 2 + firstx; + else + xcoord = (firstx - secondx) / 2 + secondx; + if ( firsty < secondy ) + ycoord = (secondy - firsty) / 2 + firsty; + else + ycoord = (firsty - secondy) / 2 + secondy; + /* if i < j, then the edge goes from j to i */ + if ( i < j ) + { + g.drawLine ( firstx, firsty - 10, secondx, secondy - 10 ); + g.drawString ( firstkey + " to " + secondkey + ", Weight: " + format.format ( adjacent[i][j] ), xcoord, ycoord - 11 ); + } + /* if i > j, then the edge goes from i to j */ + else if ( i > j ) + { + g.drawLine ( firstx, firsty + 10, secondx, secondy + 10 ); + g.drawString ( firstkey + " to " + secondkey + ", Weight: " + format.format ( adjacent[i][j] ), xcoord, ycoord + 11 ); + } + } + } + } + + /* draw the shortest path stored in green */ + g.setColor ( Color.green ); + for ( int i = 1; i < path.size(); i++ ) + { + int firstx = ((GraphNode) path.get ( i - 1 )).getX(); + int firsty = ((GraphNode) path.get ( i - 1 )).getY(); + int secondx = ((GraphNode) path.get ( i )).getX(); + int secondy = ((GraphNode) path.get ( i )).getY(); + g.drawLine ( firstx, firsty, secondx, secondy ); + } + + /* draw all of the vertices in dark gray with a light gray border and the comparable data in the middle */ + for ( int i = 0; i < numVertices(); i++ ) + { + g.setColor ( Color.lightGray ); + GraphNode temp = getVertex ( i ); + g.fillOval ( temp.getX() - radius, temp.getY() - radius, radius * 2, radius * 2 ); + g.setColor ( Color.darkGray ); + int difference = 2; + g.fillOval ( temp.getX() - radius + difference, temp.getY() - radius + difference, (radius - difference) * 2, (radius - difference) * 2 ); + g.setColor ( Color.white ); + g.drawString ( (String) temp.getKey(), temp.getX(), temp.getY() ); + } + } + + /** + * This method is used to set the arraylist containing the shortest path to be displayed.
+ * Preconditions: An arraylist containing a valid shortest path.
+ * Postconditions: Sets the current path to the given arraylist.
+ * @author Andrew Coleman
+ */ + public void setShortestPathDisplay ( ArrayList path ) + { + this.path = path; + } + + /** + * Used to calculate the shortest distance between two vertices using Djisktra's algorithm.
+ * Preconditions: Two comparables representing the data in the two vertices.
+ * Postconditions: Returns an ArrayList containing the GraphNodes in the path.
+ * Throws: Throws a GraphException if the comparables are the same or there is no connecting path.
+ * @author Andrew Coleman
+ */ + public ArrayList shortestPath ( Comparable firstkey, Comparable lastkey ) throws GraphException { + if ( firstkey.compareTo ( lastkey ) == 0 ) + throw new GraphException ( "Cannot find shortest path to same vertex!" ); + /* the arraylist containing the raw path array */ + ArrayList path = new ArrayList ( vertexList.size() ); + /* mark the first vertex */ + getVertex ( firstkey ).setMarked ( true ); + /* indexes of the first and last search keys */ + int firstindex = findIndex ( firstkey ); + int secondindex = findIndex ( lastkey ); + /* the row of the adjacency matrix at firstindex */ + double[] weight = new double[vertexList.size()]; + /* initialize the weight/path arrays */ + for ( int i = 0; i < weight.length; i++ ) + { + path.add ( vertexList.get ( i ) ); + weight[i] = adjacent[firstindex][i]; + /* if the weight is not infinity we must change the path to reflect the changes */ + if ( weight[i] < Double.POSITIVE_INFINITY ) + path.set ( i, getVertex ( firstkey ) ); + } + + /* loop through all the elements in the graph, must mark them all */ + for ( int i = 1; i < vertexList.size(); i++ ) + { + /* index of smallest vertex */ + int smallest = -1; + /* smallest weight thus far that is not marked */ + double smallestweight = Double.POSITIVE_INFINITY; + for ( int j = 0; j < weight.length; j++ ) + /* run through the weight array and find the smallest weight */ + if ( smallestweight >= weight[j] && !((GraphNode) vertexList.get ( j )).isMarked() ) + { + /* note smallest vertex */ + smallest = j; + smallestweight = weight[j]; + } + /* mark smallest vertex */ + GraphNode smallnode = (GraphNode) vertexList.get ( smallest ); + smallnode.setMarked ( true ); + /* update the weight/path arrays */ + for ( int j = 0; j < weight.length; j++ ) + /* if a new weight to that vertex is less than the current weight, change the weight in the array and change the path arraylist */ + if ( weight[j] > weight[smallest] + adjacent[smallest][j] ) + { + weight[j] = weight[smallest] + adjacent[smallest][j]; + path.set ( j, vertexList.get ( smallest ) ); + } + } + /* backwards path */ + ArrayList result = new ArrayList(); + GraphNode node = getVertex ( lastkey ); + int lastindex = findIndex ( lastkey ); + /* while the node is not the first node */ + while ( lastindex != firstindex ) + { + result.add ( node ); + node = (GraphNode) path.get ( lastindex ); + int newlastindex = findIndex ( node.getKey() ); + /* if the indexes are the same, then there is no way to get to that vertex */ + if ( newlastindex == lastindex ) + throw new GraphException ( "No connecting path!" ); + else + lastindex = newlastindex; + } + /* gotta add the first one */ + result.add ( getVertex ( firstkey ) ); + /* the result to return */ + ArrayList sortedresult = new ArrayList ( result.size() ); + /* reverse the arraylist */ + for ( int i = result.size() - 1; i >= 0; i-- ) + sortedresult.add ( result.get ( i ) ); + return sortedresult; + } + + /** + * This method finds the index of a given comparable object.
+ * Preconditions: A valid comparable key in the graph.
+ * Postcondtions: Returns the index of the key, or -1 if the key is not in the graph.
+ */ + private int findIndex ( Comparable key ) + { + for ( int i = 0; i < vertexList.size(); i++ ) + if ( ((GraphNode) vertexList.get ( i )).getKey().compareTo ( key ) == 0 ) + return i; + return -1; + } + +} diff --git a/GraphDriver.java b/GraphDriver.java new file mode 100644 index 0000000..59fc1c1 --- /dev/null +++ b/GraphDriver.java @@ -0,0 +1,28 @@ +import java.util.ArrayList; + +public class GraphDriver +{ + public static void main ( String[] args ) + { + Graph mygraph = new Graph(); + mygraph.addVertex ( new GraphNode ( "A" ) ); + mygraph.addVertex ( new GraphNode ( "B" ) ); + mygraph.addVertex ( new GraphNode ( "C" ) ); + mygraph.addVertex ( new GraphNode ( "D" ) ); + mygraph.addVertex ( new GraphNode ( "E" ) ); + + mygraph.addEdge ( "A", "D", 9.0 ); + mygraph.addEdge ( "A", "B", 8.0 ); + mygraph.addEdge ( "A", "E", 4.0 ); + mygraph.addEdge ( "B", "C", 1.0 ); + mygraph.addEdge ( "C", "B", 2.0 ); + mygraph.addEdge ( "C", "D", 3.0 ); + mygraph.addEdge ( "D", "C", 2.0 ); + mygraph.addEdge ( "D", "E", 7.0 ); + mygraph.addEdge ( "E", "C", 1.0 ); + + ArrayList bft = mygraph.shortestPath ( "A", "C" ); + for ( int i = 0; i < bft.size(); i++ ) + System.out.println ( bft.get ( i ).toString() ); + } +} diff --git a/GraphException.java b/GraphException.java new file mode 100644 index 0000000..a7a1e3d --- /dev/null +++ b/GraphException.java @@ -0,0 +1,11 @@ +public class GraphException extends RuntimeException +{ + public GraphException() + { + } // end default constructor + + public GraphException(String s) + { + super(s); + } // end constructor +} // end GraphException \ No newline at end of file diff --git a/GraphGUI.java b/GraphGUI.java new file mode 100644 index 0000000..a0e575d --- /dev/null +++ b/GraphGUI.java @@ -0,0 +1,93 @@ +import javax.swing.*; +import java.awt.event.*; +import java.awt.*; +import java.util.*; + +/** This is the parent window in the graph, it only sets flags based on menu choices and provides a place for the GraphScreen to draw itself */ +public class GraphGUI extends JFrame { + + /** the height of the frame */ + private final int HEIGHT = 550; + + /** the width of the frame */ + private final int WIDTH = 640; + + /** the JPanel that does all of the work */ + private GraphScreen graphscreen; + + /** the status bar */ + private JLabel information; + + /** + * The default constructor.
+ * Preconditions: None.
+ * Postconditions: Sets up the window and displays everything.
+ */ + public GraphGUI() { + super ( "GraphGUI" ); + setSize ( WIDTH, HEIGHT ); + getContentPane().setLayout ( new BorderLayout() ); + + addWindowListener ( new WindowAdapter() { + public void windowClosing ( WindowEvent event ) { + System.exit ( 0 ); + } + }); + + /* Center the window in the middle of the screen */ + Dimension screensize = getToolkit().getScreenSize(); + int screenwidth = screensize.width; + int screenheight = screensize.height; + setLocation ( screenwidth / 2 - WIDTH / 2, screenheight / 2 - HEIGHT / 2); + + /* These are the individual menu items in the menu bar. */ + JMenuItem cleargraph = new JMenuItem ( new ImageIcon ( "ClearGraph.jpg" ) ); + cleargraph.setBackground ( Color.black ); + cleargraph.setToolTipText ( "Makes a new, empty, pretty graph." ); + cleargraph.addActionListener ( new ActionListener() { + public void actionPerformed ( ActionEvent event ) { + graphscreen.clearGraph(); + } + }); + JMenuItem shortestpath = new JMenuItem ( new ImageIcon ( "ShortestPath.jpg" ) ); + shortestpath.setBackground ( Color.black ); + shortestpath.setToolTipText ( "Determines the shortest path between two vertices." ); + shortestpath.addActionListener ( new ActionListener() { + public void actionPerformed ( ActionEvent event ) { + graphscreen.findShortestPath(); + } + }); + JMenuItem quit = new JMenuItem ( new ImageIcon ( "Quit.jpg" ) ); + quit.setBackground ( Color.black ); + quit.setToolTipText ( "All your base are belong to us." ); + quit.addActionListener ( new ActionListener() { + public void actionPerformed ( ActionEvent event ) { + System.exit ( 0 ); + } + }); + + /* the menu that keeps the menuitems */ + JMenu graph = new JMenu ( "GraphGUI" ); + graph.add ( cleargraph ); + graph.add ( shortestpath ); + graph.add ( quit ); + JMenuBar toolbar = new JMenuBar(); + toolbar.add ( graph ); + setJMenuBar ( toolbar ); + + /* set up the two remaining graphical components */ + information = new JLabel ( "Ready" ); + graphscreen = new GraphScreen ( information ); + + getContentPane().add ( graphscreen, BorderLayout.CENTER ); + getContentPane().add ( information, BorderLayout.SOUTH ); + + setResizable ( false ); + setVisible ( true ); + } + + /** Start the party */ + public static void main ( String[] args ) { + GraphGUI mygraph = new GraphGUI(); + } +} diff --git a/GraphInterface.java b/GraphInterface.java new file mode 100644 index 0000000..57682c2 --- /dev/null +++ b/GraphInterface.java @@ -0,0 +1,142 @@ +import java.util.*; + +public interface GraphInterface +{ + + //two constructors + //default constructor makes a directed graph + //constructor that takes a boolean (true for directed) + + +/** + * Makes the current graph empty.
+ * Preconditions: None.
+ * Postconditions: makes graph empty.
+ * Throws: None. + */ + public void makeEmpty(); + +/** + * Indicates if graph is empty.
+ * Preconditions: None.
+ * Postconditions: returns boolean true if graph is empty, false if not.
+ * Throws: None. + */ + public boolean isEmpty(); + +/** + * Returns an integer corresponding to thenumber of vertices in the graph.
+ * Preconditions: None.
+ * Postconditions: returns int.
+ * THrows: None. + */ + public int numVertices(); + +/** + * Returns an integer corresponding to the numberof edges in the graph.
+ * Preconditions: None.
+ * Postconditions: returns int.
+ * Throws: NOne. + */ + public int numEdges(); + +/** + * Adds a vertex to the graph.
+ * Preconditions: Must be passed GraphNode to be inserted into the graph.
+ * Postconditions: Adds the passed-in node to the graph.
+ * Throws: None. + */ + + //add a vertex to the Graph + public void addVertex(GraphNode myItem) throws GraphException; + +/** + *Adds an edge to the Graph.
+ *Preconditions: must be passed the searchkeys of the nodes that are to be connected.
+ *Postconditions: adds edge to graph.
+ *Throws: GraphException if searchkeys don't exist. + */ + + public void addEdge(Comparable searchKey1, Comparable searchKey2) throws GraphException; + +/** + * Adds a weighted edge to the graph.
+ * Preconditions: must be passed two search keys and a weight.
+ * Postconditions: adds weighted edge to the graph.
+ * Throws: GraphException if given invalid endpoints or weight value + */ + + public void addEdge(Comparable searchKey1, Comparable searchKey2, double weight) throws GraphException; + +/** + * Retrieves the weight of a given edge.
+ * Preconditions: must be given searchkeys of the endpoints of the edge.
+ * Postconditions: returns double value for the weight.
+ * Throws: GraphException if given invalid endpoints. + */ + + public double getWeight(Comparable searchKey1, Comparable searchKey2) throws GraphException; + +/** + * Removes an edge from the graph.
+ * Preconditions: must be passed searchKey valued for edge's endpoints.
+ * Postconditions: removes edge from graph.
+ * Throws: GraphException if invalid vertices. + */ + + public void removeEdge(Comparable searchKey1, Comparable searchKey2) throws GraphException; + +/** + * Removes a vetex from the graph.
+ * Preconditions: must be passed a searchKey to delete.
+ * Postconditions: removes given searchKey.
+ * Throws: GraphException if given invalid searchkey. + */ + + public GraphNode removeVertex(Comparable searchKey) throws GraphException; + +/** + * Gets vertex from graph.
+ * Preconditions: must be passed a valid searchKey.
+ * Postconditions: returns a graphNode for the given searchKey.
+ * Throws: GraphException if given an invaid searchKey. + */ + + public GraphNode getVertex(Comparable searchKey) throws GraphException; + +/** + * Gets vertex from graph.
+ * Preconditions: must be passed integer index of node in list.
+ * Postconditions: returns GraphNode at given index.
+ * Throws: GraphException if given invalid index value. + */ + + public GraphNode getVertex(int index) throws GraphException; + +/** + * Obtains a depth-first search of the grap from the given starting vertex.
+ * Preconditions: must be passed a searchKey for the starting point of the search.
+ * Postconditions: returns ArrayList of vertices along the search path.
+ * Throws: GraphException if given invalid searchKey + */ + + public ArrayList dfs(Comparable searchKey) throws GraphException; + +/** + * Obtains a breadth-first search of the graph.
+ * Preconditions: must be passed searchKey of the starring point of the search.
+ * Postconditions: returns an ArrayList of the vertices in the search.
+ * Throws: GraphException if the searchKey is invalid. + */ + + public ArrayList bfs(Comparable searchKey) throws GraphException; + +/** + * Returns the shortest path between two vertices in the graph.
+ * Preconditions: must be passed searchKeys of the starting and ending vertices.
+ * Postconditions: returns an ArrayList of vertices between the starting and ending search Keys.
+ * Throws: GraphException if enteres searchKeys are invalid. + */ + public ArrayList shortestPath(Comparable searchKey1, Comparable searchKey2) throws GraphException; + +} \ No newline at end of file diff --git a/GraphNode.java b/GraphNode.java new file mode 100644 index 0000000..2b6c3a5 --- /dev/null +++ b/GraphNode.java @@ -0,0 +1,81 @@ +/** + * GraphNode.java, an object that stores information about a graph node for + * Tree Applicatons.
+ * Extends: KeyedItem.
+ * Author: Matt Markham + */ + +public class GraphNode extends KeyedItem +{ + private int xCoord, yCoord; + boolean mark; + + GraphNode (Comparable item) + { + super(item); + mark=false; + } + + GraphNode (Comparable item, int x, int y) + { + super(item); + xCoord=x; + yCoord=y; + mark=false; + } + +/** + * Returns the x-coordinate of the current Graph Node.
+ * Preconditions: None.
+ * Postconditions: Returns int value of this nodes x position.
+ * Throws: None + */ + + + public int getX() + { + return xCoord; + } + +/** + * Returns the y-coordiante of the current Graph Node.
+ * Preconditions: None.
+ * Postconditions: Returns int value of this nodes y position.
+ * Throws: None + */ + + public int getY() + { + return yCoord; + } + +/** + * Returns true if node is marked, false if it is not.
+ * Preconditions: None.
+ * Postconditions: returns true if node is marked, false if it is not. + * Throws: None. + */ + + public boolean isMarked() + { + return mark; + } + + +/** + * Sets value of mark to true or false.
+ * Preconditions: Must be passed in a boolean.
+ * Postconditions: Sets mark to passed-in boolean.
+ * Throws: None. + */ + + public void setMarked(boolean setbool) + { + mark=setbool; + } + + public boolean equals (GraphNode node) + { + return ((node.getX() == xCoord)&&(node.getY()==yCoord) && (node.getKey().compareTo(this.getKey())==0)); + } +} \ No newline at end of file diff --git a/GraphScreen.java b/GraphScreen.java new file mode 100644 index 0000000..910621c --- /dev/null +++ b/GraphScreen.java @@ -0,0 +1,267 @@ +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.text.DecimalFormat; + +/** This class defines a custom JPanel that will draw vertices, edges, and the shortest path of a Graph object. This custom JPanel is used because it is much easier to intercept x and y coordinates in this panel than in the frame and then having to compensate for the other graphical components. */ + +public class GraphScreen extends JPanel implements MouseListener { + /** defines the radius of the vertices in pixels */ + private final int radius = 20; + + /** the path of vertices for the currently displayed shortest path */ + private ArrayList path; + + /** the graph to add vertices/edges to */ + private Graph mygraph; + + /** the status bar in the parent jframe */ + private JLabel information; + + /* a flag to check if you are calculating the shortest path */ + private boolean shortestpath; + + /* a flag used to check if the mouseclick intercepted was the second in the series (add edge, shortest path) */ + private boolean issecondvertex; + + /* the data stored in the first vertex clicked in the series */ + private Comparable firstvertex; + + + /** + * Default constructor for the GraphScreen panel.
+ * Preconditions: A JLabel status bar in the parent JFrame.
+ * Postconditions: Initializes all variables.
+ */ + public GraphScreen ( JLabel information ) { + super ( true ); + this.information = information; + addMouseListener ( this ); + mygraph = new Graph(); + mygraph.setShortestPathDisplay ( new ArrayList() ); + information.setText ( " Click to add vertex" ); + issecondvertex = false; + firstvertex = null; + shortestpath = false; + } + + /** mousePressed and mouseReleased could be used to move vertices around the graph, + but as per explicitly stated in the program requirements, the GraphNodes have no + setX ( int x ) and setY ( int y ) methods. */ + public void mousePressed ( MouseEvent event ) {} + public void mouseReleased ( MouseEvent event ) {} + + /** not used, just here to fully implement the MouseListener interface */ + public void mouseEntered ( MouseEvent event ) {} + public void mouseExited ( MouseEvent event ) {} + + /** + * Intercepts a mouse click in the drawing window.
+ * Preconditions: A mouse click.
+ * Postconditions: This method could add a vertex, add an edge, or calculate where the shortest path needs to be based upon flags and user interaction.
+ */ + public void mouseClicked ( MouseEvent event ) { + /* If it is a right mouse click, then we must add an edge */ + if ( SwingUtilities.isRightMouseButton ( event ) ) { + /* the node that is contained within radius pixels of the MouseEvent */ + GraphNode temp = findVertex ( event.getX(), event.getY() ); + /* if temp is null then the user clicked in blank space */ + if ( temp != null ) { + /* if this is the second right mouse click, and it is not the same vertex */ + if ( issecondvertex && firstvertex.compareTo ( temp.getKey() ) != 0 ) { + /* lets add an edge, and hopefully it will work */ + try { + addEdge ( firstvertex, temp.getKey() ); + information.setText ( " Edge added between " + firstvertex.toString() + " and " + temp.getKey().toString() ); + } + /* error exists between keyboard and chair */ + catch ( GraphException exception ) { + information.setText ( " Could not add the edge" ); + JOptionPane.showMessageDialog ( null, exception.toString(), "Exception caught", JOptionPane.WARNING_MESSAGE ); + } + /* we will set all the flags to normal */ + issecondvertex = false; + firstvertex = null; + } + /* this must be the first right click intercepted */ + else { + /* save the data for use later */ + firstvertex = temp.getKey(); + issecondvertex = true; + information.setText ( " Right click on another vertex to create an edge" ); + } + } + /* we don't care and set everything to normal since the user clicked in blank space */ + else { + firstvertex = null; + issecondvertex = false; + information.setText ( " Right click on vertices to add and edge" ); + } + } + + /* not the right mouse button, so this could be shortest path or add a vertex */ + /* if the shortest path flag is set, we must find two different vertices */ + else if ( shortestpath ) { + /* if there are less than 2 vertices in the graph, it is impossible to find the shortest path */ + if ( mygraph.numVertices() > 2 ) { + GraphNode temp = findVertex ( event.getX(), event.getY() ); + /* if this is the second click and the user clicked on a node and the node is not the same node */ + if ( issecondvertex && temp != null && firstvertex.compareTo ( temp.getKey() ) != 0 ) { + /* set the shortest path arraylist to the path in the graph */ + try { + mygraph.setShortestPathDisplay ( mygraph.shortestPath ( firstvertex, temp.getKey() ) ); + information.setText ( " Shortest path is displayed in green" ); + repaint(); + } + catch ( GraphException exception ) { + JOptionPane.showMessageDialog ( null, exception.toString(), "Exception caught", JOptionPane.WARNING_MESSAGE ); + mygraph.setShortestPathDisplay ( new ArrayList() ); + information.setText ( " Could not create the shortest path" ); + } + shortestpath = false; + issecondvertex = false; + firstvertex = null; + } + /* the user is clicking on blank space */ + else if ( temp == null ) { + information.setText ( " You must first click on vertices" ); + shortestpath = false; + issecondvertex = false; + firstvertex = null; + } + /* must be the first click in the series then */ + else { + firstvertex = temp.getKey(); + issecondvertex = true; + information.setText ( " Click on the ending vertex of the path" ); + } + } + /* not enough vertices to make a shortest path */ + else { + information.setText ( " There must be at least 2 vertices in the graph before calculating the shortest path" ); + shortestpath = false; + firstvertex = null; + issecondvertex = false; + } + } + + /* well, the only thing left is to add a new vertex */ + else { + /* if the user clicked in blank space for once */ + if ( findVertex ( event.getX(), event.getY() ) == null ) { + information.setText ( " Click to add vertex" ); + shortestpath = false; + issecondvertex = false; + firstvertex = null; + /* ask the user what they want to call it */ + String name = JOptionPane.showInputDialog ( null, "Enter name: ","Add new graph node", JOptionPane.QUESTION_MESSAGE ); + /* as long as the name appears somewhat valid anyways */ + if ( name != null && !name.equals ( "" ) ) { + int x = event.getX(), y = event.getY(); + /* these if's check to make sure the user didn't click too close to the edge */ + if ( x + radius > getWidth() ) + x = getWidth() - radius; + if ( x - radius < 0 ) + x = radius; + if ( y - radius > getHeight() ) + y = getHeight() - radius; + if ( y - radius < 0 ) + y = radius; + try { + mygraph.addVertex ( new GraphNode ( name, x, y ) ); + information.setText ( " Vertex added at (" + x + ", " + y + ")" ); + repaint(); + } + catch ( Exception exception ) { + JOptionPane.showMessageDialog ( null, exception.toString(), "Exception caught!", JOptionPane.WARNING_MESSAGE ); + information.setText ( " Vertex could not be added" ); + } + } + } + /* the user clicked on a vertex */ + else { + information.setText ( " Vertex already added at location" ); + JOptionPane.showMessageDialog ( null, "Vertex is already at that point!", "Waitaminit!", JOptionPane.WARNING_MESSAGE ); + } + } + + } + + /** + * Empties the graph and sets all flags to default values.
+ * Preconditions: None.
+ * Postconditions: Resets all internally used variables to default values.
+ */ + public void clearGraph() { + mygraph.makeEmpty(); + information.setText ( " Click to add vertex" ); + mygraph.setShortestPathDisplay ( new ArrayList() ); + issecondvertex = false; + firstvertex = null; + shortestpath = false; + repaint(); + } + + /** + * Used to set the flag to find the shortest path.
+ * Preconditions: None.
+ * Postconditions: Sets the shortestpath flag to true.
+ */ + public void findShortestPath() { + shortestpath = true; + information.setText ( " Click on the starting vertex" ); + } + + /** + * Attempts to locate a GraphNode within x +/- radius pixels and y +/- radius pixels of the given coordinate.
+ * Preconditions: Two integers representing coordinates inside the GraphScreen.
+ * Postconditions: Returns null if no GraphNode is close to the coord. or the GraphNode closest to that point.
+ */ + private GraphNode findVertex ( int x, int y ) { + /* look through all of the nodes one by one */ + for ( int i = 0; i < mygraph.numVertices(); i++ ) { + GraphNode result = mygraph.getVertex ( i ); + int tempx = result.getX(); + int tempy = result.getY(); + if ( tempx + radius >= x && tempx - radius <= x && tempy - radius <= y && tempy + radius >= y ) + return result; + } + return null; + } + + /** + * Adds an edge between two vertices.
+ * Preconditions: Two comparable objects that are in the graph.
+ * Postconditions: Adds an edge between the two vertices containing the comparable objects.
+ */ + public void addEdge ( Comparable first, Comparable second ) { + String temp = JOptionPane.showInputDialog ( null, "Enter a weight", "Add an edge", JOptionPane.QUESTION_MESSAGE ); + double result = -1.0; + if ( temp != null ) { + try { + result = Double.parseDouble ( temp ); + if ( result < 0 ) + throw new GraphException ( "Invalid weight for edge." ); + mygraph.addEdge ( first, second, result ); + information.setText ( " New edge added between vertices {" + (String) first + "} and {" + (String) second + "}" ); + repaint(); + } + catch ( Exception exception ) { + information.setText ( " Edge could not be added" ); + JOptionPane.showMessageDialog ( null, exception.toString(), "Exception Caught", JOptionPane.WARNING_MESSAGE ); + } + } + } + + + /** + * Draws the background for the custom JPanel.
+ */ + public void paint ( Graphics g ) { + /* draw a black background */ + g.setColor ( Color.black ); + g.fillRect ( 0, 0, this.getWidth(), this.getHeight() ); + mygraph.draw ( g, radius ); + } +} diff --git a/KeyedItem.java b/KeyedItem.java new file mode 100644 index 0000000..e6fa1a7 --- /dev/null +++ b/KeyedItem.java @@ -0,0 +1,38 @@ +public abstract class KeyedItem +{ + private Comparable searchKey; + + public KeyedItem(Comparable key) + { + searchKey = key; + } // end constructor + + public Comparable getKey() + { + return searchKey; + } // end getKey + + public String toString() + { + return searchKey.toString(); + } + +} // end KeyedItem + + +/* +How to use: +public class CD extends KeyedItem +{ + //title not present here + String artist; + ... + public CD(String title, String artist, double price, int tracks) + { + super(title); + this.artist=artist; + ... + } + +} +*/ \ No newline at end of file diff --git a/Node.java b/Node.java new file mode 100644 index 0000000..370b798 --- /dev/null +++ b/Node.java @@ -0,0 +1,39 @@ +public class Node +{ + private Object item; + private Node next; //reference to another node + + public Node(Object item) + { + this.item = item; + next = null; + } // end constructor + + public Node(Object item, Node next) + { + this.item = item; + this.next = next; + } // end constructor + + public void setItem(Object item) + { + this.item = item; + } // end setItem + + public Object getItem() + { + return item; + } // end getItem + + public void setNext(Node next) + { + this.next = next; + } // end setNext + + public Node getNext() + { + return next; + } // end getNext + +} // end class Node + diff --git a/QueueException.java b/QueueException.java new file mode 100644 index 0000000..f904660 --- /dev/null +++ b/QueueException.java @@ -0,0 +1,7 @@ +public class QueueException extends RuntimeException +{ + public QueueException(String s) + { + super(s); + } // end constructor +} // end QueueException \ No newline at end of file diff --git a/QueueInterface.java b/QueueInterface.java new file mode 100644 index 0000000..3c24f86 --- /dev/null +++ b/QueueInterface.java @@ -0,0 +1,57 @@ +public interface QueueInterface +{ + /** + * Determines whether a queue is empty. + * Precondition: None. + * Postcondition: Returns true if the queue is empty; + * otherwise returns false. + */ + public boolean isEmpty(); + + /** + * Adds an item at the back of a queue. + * Precondition: item is the item to be inserted. + * Postcondition: If the operation was successful, newItem + * is at the back of the queue. Some implementations + * may throw QueueException if item cannot be added to the queue. + */ + public void enqueue(Object item) throws QueueException; + + /** + * Retrieves and removes the front of a queue. + * Precondition: None. + * Postcondition: If the queue is not empty, the item + * that was added to the queue earliest is returned and + * the item is removed. If the queue is empty, the + * operation is impossible and QueueException is thrown. + */ + public Object dequeue() throws QueueException; + + /** + * Removes all items of a queue. + * Precondition: None. + * Postcondition: The queue is empty. + */ + public void dequeueAll(); + + /** + * Retrieves the item at the front of a queue. + * Precondition: None. + * Postcondition: If the queue is not empty, the item + * that was added to the queue earliest is returned. + * If the queue is empty, the operation is impossible + * and QueueException is thrown. + */ + public Object peek() throws QueueException; + + /** + * Determines the length of a queue. + * Precondition: None. + * Postcondition: Returns the number of items that are + * currently in the queue. + * Throws: None. + */ + public int size(); + + +} // end QueueInterface \ No newline at end of file diff --git a/QueueReferenceBased.java b/QueueReferenceBased.java new file mode 100644 index 0000000..7485cf1 --- /dev/null +++ b/QueueReferenceBased.java @@ -0,0 +1,92 @@ +public class QueueReferenceBased implements QueueInterface +{ + // circular references + private Node tail; + private int size; + + public QueueReferenceBased() + { + tail = null; + size=0; + } // end default constructor + + // queue operations: + public boolean isEmpty() + { + return tail == null; + } // end isEmpty + + public int size() + { + return size; + } + + public void dequeueAll() + { + if (tail!=null) + tail.setNext(null); + + tail = null; + size=0; + } // end dequeueAll + + public void enqueue(Object item) + { + Node myNode = new Node(item); + + // insert the new node + if (isEmpty()) + { + // insertion into empty queue + myNode.setNext(myNode); + } + else + { + // insertion into nonempty queue + myNode.setNext(tail.getNext()); + tail.setNext(myNode); + } // end if + + tail = myNode; // new node is at back + size++; + } // end enqueue + + public Object dequeue() throws QueueException + { + if (!isEmpty()) + { + // queue is not empty; remove front + Node head = tail.getNext(); + if (head == tail) + { // special case? + tail.setNext(null); + tail = null; // yes, one node in queue + } + else + { + tail.setNext(head.getNext()); + } // end if + size--; + return head.getItem(); + } + else + { + throw new QueueException("Queue empty"); + } // end if + } // end dequeue + + public Object peek() throws QueueException + { + if (!isEmpty()) + { + // queue is not empty; retrieve front + Node head = tail.getNext(); + return head.getItem(); + } + else + { + throw new QueueException("Queue empty"); + } // end if + } // end peek + +} // end QueueCircular \ No newline at end of file diff --git a/Quit.jpg b/Quit.jpg new file mode 100644 index 0000000000000000000000000000000000000000..62b3ecefb9af469212d057ad9325bed154fdc0d0 GIT binary patch literal 3432 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 literal 0 HcmV?d00001 diff --git a/ShortestPath.jpg b/ShortestPath.jpg new file mode 100644 index 0000000000000000000000000000000000000000..437ea45b38dce82fe8b9760e4a3574df8dc011a2 GIT binary patch literal 4164 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 j&U(C&5AZt$m$