CMSC 350 UMGC Class Dependencies and Directed Graph Java Program

User Generated

qqvyyba1139

Computer Science

cmsc 350

University of Maryland Global Campus

CMSC

Description

I need assistance in writing a program that accepts information contained in a file about the class dependencies in a Java program and creates a directed graph from that information.

I have attached all required documents in order to successfully write code. If you have any additional questions feel free to ask.

Unformatted Attachment Preview

10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition % ⏮ 7/5/20, 7:30 PM Object-Oriented Data Structures Using Java, 4th Edition NEXT PREV 9 The Priority Queue ADT ! $ # 11 Sorting and Searching Algorithms ⏭ CHAPTER 10: The Graph ADT © Ake13bk/Shutterstock KNOWLEDGE GOALS You should be able to define the following terms related to graphs: graph vertex edge undirected graph directed graph adjacent vertices path cycle connected vertices connected graph disconnected graph connected component complete graph describe the purpose of each of the methods defined in the WeightedGraphInterface explain how to implement a graph using arrays explain how to implement a graph using linked lists discuss the benefits and drawbacks of the two primary graph implementation approaches explain the difference between a depth-first search and a breadth-first search of a graph describe the shortest-path algorithm for graphs SKILL GOALS You should be able to trace the depth-first “is-path” algorithm, listing the vertices in the order marked and the order visited trace the breadth-first “is-path” algorithm, listing the vertices in the order marked and the order visited trace the “shortest paths” algorithm, listing the shortest path and distance to all vertices from a given vertex implement a graph using an adjacency matrix to represent the edges implement a graph using adjacency lists implement the depth-first searching strategy for a graph using a stack for auxiliary storage implement the breadth-first searching strategy for a graph using a queue for auxiliary storage https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 1 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM implement a shortest-paths operation for a graph, using a priority queue to access the edge with the minimum weight In Chapter 7 we looked at how a branching structure—the binary search tree —facilitates searching for data. In this chapter we see how another branching structure, the graph, is defined and implemented to support a variety of applications. Graphs consist of vertices and edges. The information held in a graph often is related to the structure of the graph itself, the relationships among the vertices and edges. 10.1 INTRODUCTION TO GRAPHS In Chapter 7 we studied trees. Trees provide a very useful way of representing relationships in which a hierarchy exists. That is, a node is pointed to by at most one other node (its parent), as pictured below. If we remove the restriction that each node may have only one parent node, we have a data structure called a graph. A graph is made up of a set of nodes called vertices and a set of lines called edges (or arcs) that connect the vertices. The information held in a graph is related to the structure of the graph itself, the relationships among the vertices and edges. In this section we introduce the vocabulary of these relationships. Graph Definitions The many definitions related to graphs presented in this section are collected in Figure 10.4 at the end of the section and can also be found in the glossary. The set of edges describes relationships among the vertices. For instance, if the vertices are the names of cities, the edges that link the vertices could represent roads between pairs of cities. Because the road that runs between Houston and Austin also runs between Austin and Houston, the edges in this graph have no direction. This is called an undirected graph. If the edges that link the vertices represent flights from one city to another, however, the direction of each edge is important. The existence of a flight (edge) from Houston to Austin does not assure the existence of a flight (edge) from Austin to Houston. A graph whose edges are directed from one vertex to another is called a directed graph, or digraph. From a programmer’s perspective, vertices represent whatever is the subject of our study: people, houses, cities, courses, and so on. Mathematically, however, vertices are the abstract concept upon which graph theory rests. In fact, a great deal of formal mathematics is associated with graphs. In other computing courses, you may analyze graphs and prove theorems about them. This text introduces the graph as an abstract data type, teaches some basic terminology, discusses how a graph might be implemented, and describes how algorithms that manipulate graphs make use of stacks, queues, and priority queues. Formally, a graph G is defined as follows: G = (V, E) where V(G) is a finite, nonempty set of vertices and E(G) is a set of edges (written as pairs of vertices). The set of vertices is specified by listing them in set notation, within { } braces. The following set defines the six vertices of Graph1 pictured in Figure 10.1a: V(Graph1) = {A, B, C, D, E, F} https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 2 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM Figure 10.1 Examples of graphs The set of edges is specified by listing a sequence of edges. Each edge is denoted by writing the names of the two vertices it connects in parentheses, with a comma between them. For instance, the vertices in Graph1 are connected by the five edges described below: E(Graph1) = {(A, B), (A, D), (B, C), (B, D), (E, F)} Because Graph 1 is an undirected graph, the order of the vertices in each edge is unimportant. The set of edges in Graph 1 can also be described as follows: E(Graph1) = {(B, A), (D, A), (C, B), (D, B), (F, E)} If the graph is a digraph, the direction of the edge is indicated by which vertex is listed first. For instance, in Graph 2 pictured in Figure 10.1b, the edge (5, 7) represents a link from vertex 5 to vertex 7. There is no corresponding edge (7, 5) in Graph 2. In pictures of digraphs, the arrows indicate the direction of the relationship. We do not have duplicate vertices or edges in a graph. This point is implied in the definition, because sets do not have repeated elements. If two vertices in a graph are connected by an edge, they are said to be adjacent. In Graph 1 vertices A and B are adjacent, but vertices A and C are not. If the vertices are connected by a directed edge, then the first vertex is said to be adjacent to the second, and the second vertex is said to be adjacent from the first. For example, in Graph 2 vertex 5 is adjacent to vertices 7 and 9, while vertex 1 is adjacent from vertices 3 and 11. The picture of Graph 3 in Figure 10.1c may look familiar; it is the tree we looked at in Chapter 9 in connection with the nonlinked representation of a binary tree. A tree is a special case of a directed graph in which each vertex may only be adjacent from one other vertex (its parent) and one vertex (the root) is not adjacent from any other vertex. A path from one vertex to another consists of a sequence of vertices that connect them. For a path to exist, there must be an uninterrupted sequence of edges from the first vertex, through any number of vertices, to the second vertex. For example, in Graph 2, there is a path from vertex 5 to vertex 3, but not from vertex 3 to vertex 5. In a tree, such as Graph 3, there is a unique path from the root to every other vertex in the tree. A cycle is a path that begins and ends at the same vertex. For example, in Graph 1 the path A-B-D-A is a cycle. In an undirected graph we say that two vertices are connected (connected vertices) if there is a path between them. Note that in Graph 1, vertex A is connected to vertices B, C, and D but not to E or F. A connected component is a maximal set of vertices that are connected to each other, along with the edges associated with those vertices. Graph 1 consists of two connected components {A, B, C, D} and {E, F}. Each vertex of an undirected graph belongs to one connected component. So does each edge. An undirected graph is a connected graph when it consists of a single connected component. Otherwise it is a disconnected graph. A complete graph is one in which every vertex is adjacent to every other ver- https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 3 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM tex. Figure 10.2 shows two complete graphs. If there are N vertices, there are N * (N – 1) edges in a complete directed graph and N * (N – 1) / 2 edges in a complete undirected graph. Figure 10.2 Two complete graphs A weighted graph is a graph in which each edge carries a value. Weighted graphs can be used to represent applications in which the value of the connection between the vertices is important, not just the existence of a connection. For instance, in the weighted graph pictured in Figure 10.3, the vertices represent cities and the edges indicate the Air Busters Airlines flights that connect the cities. The weights attached to the edges represent the air distances between pairs of cities. To see whether we can get from Denver to Washington, we look for a path between them. If the total travel distance is determined by the sum of the distances between each pair of cities along the way, we can calculate the travel distance by adding the weights attached to the edges that constitute the path between them. There may be multiple paths between two vertices. Later in this chapter, we talk about a way to find the shortest path between two vertices (Figure 10.4). Figure 10.3 A weighted graph Figure 10.4 Graph terminology 10.2 THE GRAPH INTERFACE We described a graph at the abstract level as a set of vertices and a set of edges that connect some or all of the vertices to one another. What kind of operations are defined on a graph? In this chapter we specify and implement a small set of useful graph operations for a directed, weighted graph. Many other operations on graphs can be defined as well. We have chosen operations that are useful when building applications to answer typical questions one might ask about graphs. For example: Does a path exist between vertex A and vertex D? Can we fly from Atlanta to Detroit? What is the total weight along this path from A to D? How much does it cost to fly from Atlanta to Detroit? What is the total distance? What is the shortest path from A to D? What is the cheapest way to get from Atlanta to Detroit? If I start at vertex A, where can I go? What cities are accessible if I https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 4 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM start in Atlanta? How many connected components are in the graph? What groups of cities are connected to each other? Graph Operations The operations specified for our Graph ADT are the building blocks used by applications that need to answer questions about any given graph. By carefully combining the provided simple operations a programmer can implement the more complex algorithms needed to obtain information from graph data. Note that we do not implement operations that directly answer any of the above questions within our Graph ADT. Instead we provide more primitive operations that can be used, in combination with each other, to answer these questions and more. In Section 10.4, “Application: Graph Traversals,” we show how this is accomplished for a few of the above questions. Others are left as exercises. Our specification for the Graph ADT, found in the WeightedGraphInter‐ face listing below, includes 11 public methods. As expected, it includes methods to check whether the graph is empty or full, and methods to add vertices and edges. Here we describe the remaining, more unusual methods. The hasVertex method can be used to check whether the argument object is used as a vertex in the graph. This issue is important because many of the other methods that accept a vertex as an argument assume, as a precondition, that the given vertex exists within the graph. Equivalence of vertices is determined using the vertices’ equals method—so if the vertex class has overwritten the Object class equals method, then equality will be as defined within the vertex class; otherwise, it is defined using the default approach of comparing references. The weightIs method returns the weight of the edge between two given vertices; if there is no such edge, it returns a special value indicating that fact. The special value could vary from one application to another. For example, the value -1 could be used for a graph whose edges represent distances, because there is no such thing as a negative distance. The special value for a specific application could be passed to the Graph constructor, if that capability is provided by the class that implements the interface. The weightIs method lets us determine whether a given edge is in the graph, which is useful, for example, to see if two vertices are connected. Of course, if two vertices are connected it is used to determine the weight of the edge between them, useful for answering questions about the total weight of a path between two vertices or the shortest path in terms of weight. A very interesting method is getToVertices, which returns a queue of vertex objects. The idea is that an application may need to know which vertices a given vertex is adjacent to in order to determine what paths exist through the graph. This method returns the collection of such vertices as a queue. The application can then dequeue the vertices one at a time, as needed. The return value of the method is of “type” QueueInterface. The implementation programmer can choose to use any queue class that implements this interface. Another approach to solving the “get to vertices” problem is to have the method return a Java Iterator object. We have used iterators with many of our previous collection ADTs, but in this case we will use the queue. Marking Vertices During execution of a graph-related algorithm we often need to mark vertices as they are processed or stored for processing. This prevents us from repeatedly examining the same vertices over and over again. At the beginning of any such processing it is important to “clear all the marks,” so that marks left over from previous work do not interfere with the new problem solution. To answer many of the questions we listed above, an application must be able to traverse the graph—that is, it must visit the vertices of the graph and perform some operation at each vertex. Because so many paths through a graph are possible, it is not unusual for the traversal algorithm to attempt to visit a vertex more than once. In such cases it is important for the application to “know” that it has previously visited the vertex. To facilitate this, our interface includes several methods related to marking vertices as visited. The markVertex and isMarked methods are used to mark vertices and check for marks, respectively. The clearMarks method clears all of the marks throughout the graph; it is used to prepare for a new traversal. Finally, the getUnmarked method returns an unmarked vertex. This ability is useful for beginning a traversal or for continuing a traversal, when the application is not sure it has visited every vertex. The ability to mark vertices is used to answer questions about shortest paths and the number of connected components. Below is the WeightedGraphInterface. It is found in the ch10.graphs package. //--------------------------------------------------------------------------// WeightedGraphInterface.java by Dale/Joyce/Weems Chapter 10 // // Interface for classes that implement a directed graph with weighted edges. // Vertices are objects of class T and can be marked as having been visited. // Edge weights are integers. // Equivalence of vertices is determined by the vertices' equals method. https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 5 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM // // General precondition: Except for the addVertex and hasVertex methods, // any vertex passed as an argument to a method is in this graph. //--------------------------------------------------------------------------package ch10.graphs; import ch04.queues.*; public interface WeightedGraphInterface { boolean isEmpty(); // Returns true if this graph is empty; otherwise, returns false. boolean isFull(); // Returns true if this graph is full; otherwise, returns false. void addVertex(T vertex); // Preconditions: This graph is not full. // vertex is not already in this graph. // vertex is not null. // // Adds vertex to this graph. boolean hasVertex(T vertex); // Returns true if this graph contains vertex; otherwise, returns false. void addEdge(T fromVertex, T toVertex, int weight); // Adds an edge with the specified weight from fromVertex to toVertex. int weightIs(T fromVertex, T toVertex); // If edge from fromVertex to toVertex exists, returns the weight of edge; // otherwise, returns a special "null-edge" value. QueueInterface getToVertices(T vertex); // Returns a queue of the vertices that vertex is adjacent to. void clearMarks(); // Unmarks all vertices. void markVertex(T vertex); // Marks vertex. boolean isMarked(T vertex); // Returns true if vertex is marked; otherwise, returns false. T getUnmarked(); // Returns an unmarked vertex if any exist; otherwise, returns null. } 10.3 IMPLEMENTATIONS OF GRAPHS Array-Based Implementation A simple way to represent V(graph), the vertices in the graph, is to use an array where the array elements are the vertices. For example, if the vertices represent city names, the array might hold strings. A simple way to represent E(graph), the edges in a graph, is to use an adjacency matrix, a two-dimensional array of edge values (weights), where the indices of a weight correspond to the vertices connected by the edge. Thus, a graph consists of an integer variable numVertices, a one-dimensional array vertices, and a two-dimensional array edges. Figure 10.5 depicts the implementation of the graph of Air Busters’ flights among the seven cities shown in Figure 10.3. For simplicity, we omit the additional boolean data needed to mark vertices as “visited” during a traversal from the figure. Although the city names in Figure 10.5 are in alphabetical order, there is no requirement that the elements in this array be sorted. Figure 10.5 Matrix representation of graph of flight connections between cities At any time, within this representation of a graph, numVertices is the number of vertices in the graph. V(graph) is contained in vertices[0] to vertices [numVertices 1]. E(graph) is contained in the square matrix edges[0][0] to edges[numVertices - 1][numVertices 1]. The names of the cities are contained in vertices. The weight of each edge in edges represents the air distance between two cities that are connected by a flight. For example, the value in edges[1][3] tells us that there is a direct flight between Austin and Dallas, and that the air distance is 200 miles. A NULL_EDGE value (0) in edges[1][6] tells us that the airline has no direct flights between Austin and Washington. Because this is a weighted graph in which the weights are air distances, we use int for the edge value https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 6 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM type. If it were not a weighted graph, the edge value type could be boolean, with each position in the adjacency matrix being true if an edge exists between the pair of vertices, and false if no edge exists. Here is the beginning of the definition of class WeightedGraph. We assume that the edge value type is int and that a null edge is indicated by a 0 value. //--------------------------------------------------------------------------// WeightedGraph.java by Dale/Joyce/Weems Chapter 10 // // Implements a directed graph with weighted edges. // Vertices are objects of class T and can be marked as having been visited. // Edge weights are integers. // Equivalence of vertices is determined by the vertices' equals method. // // General precondition: Except for the addVertex and hasVertex methods, // any vertex passed as an argument to a method is in this graph. //--------------------------------------------------------------------------package ch10.graphs; import ch04.queues.*; public class WeightedGraph implements WeightedGraphInterface { public static final int NULL_EDGE = 0; private static final int DEFCAP = 50; // default capacity private int numVertices; private int maxVertices; private T[] vertices; private int[][] edges; private boolean[] marks; // marks[i] is mark for vertices[i] public WeightedGraph() // Instantiates a graph with capacity DEFCAP vertices. { numVertices = 0; maxVertices = DEFCAP; 1 vertices = (T[]) new Object[DEFCAP]; marks = new boolean[DEFCAP]; edges = new int[DEFCAP][DEFCAP]; } public WeightedGraph(int maxV) // Instantiates a graph with capacity maxV. { numVertices = 0; maxVertices = maxV; vertices = (T[]) new Object[maxV];1 marks = new boolean[maxV]; edges = new int[maxV][maxV]; } The class constructors have to allocate the space for vertices, marks (the boolean array indicating whether a vertex has been marked), and edges. The default constructor sets up space for a graph with DEFCAP (50) vertices. The parameterized constructor lets the user specify the maximum number of vertices. The addVertex operation puts a vertex into the next free space in the array of vertices and increments the number of vertices. Because the new vertex has no edges defined yet, it also initializes the appropriate row and column of edges to contain NULL_EDGE (0 in this case). public void addVertex(T vertex) // Preconditions: This graph is not full. // vertex is not already in this graph. // vertex is not null. // // Adds vertex to this graph. { vertices[numVertices] = vertex; for (int index = 0; index < numVertices; index++) { edges[numVertices][index] = NULL_EDGE; edges[index][numVertices] = NULL_EDGE; } numVertices++; } To add an edge to the graph, we must first locate the fromVertex and toVertex that define the edge being added. These values become the arguments to addEdge and are of the generic T class. Of course, the client really passes references to the vertex objects, because that is how we manipulate objects in Java. We are implementing our graphs “by reference” so this strategy should not pose a problem for the client. To index the correct matrix slot, we need the index in the vertices array that corresponds to each vertex. Once we know the indices, it is a simple matter to set the weight of the edge in the matrix. To find the index of each vertex, let us write a private search method that receives a vertex and returns its location (index) in vertices. Based on the general precondition stated in the opening comment of the Weighted‐ Graph class, we assume that the fromVertex and toVertex arguments passed to addEdge are already in V(graph). This assumption simplifies the search method, which we code as helper method indexIs. Here is the code for indexIs and addEdge: private int indexIs(T vertex) // Returns the index of vertex in vertices. { int index = 0; while (!vertex.equals(vertices[index])) index++; return index; } public void addEdge(T fromVertex, T toVertex, int weight) https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 7 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM // Adds an edge with the specified weight from fromVertex to toVertex. { int row; int column; row = indexIs(fromVertex); column = indexIs(toVertex); edges[row][column] = weight; } The weightIs operation is the mirror image of addEdge. public int weightIs(T fromVertex, T toVertex) // If edge from fromVertex to toVertex exists, returns the weight of edge; // otherwise, returns a special "null-edge" value. { int row; int column; row = indexIs(fromVertex); column = indexIs(toVertex); return edges[row][column]; } The last graph operation that we address is getToVertices. This method receives a vertex as an argument, and it returns a queue of vertices that the vertex is adjacent to (or, another way of looking at it, that are adjacent from the designated vertex). That is, it returns a queue of all the vertices that we can get to from this vertex in one step. Using an adjacency matrix to represent the edges, it is a simple matter to determine the vertices to which the vertex is adjacent. We merely loop through the appropriate row in edges; whenever a value is found that is not NULL_EDGE, we add the corresponding vertex to the queue. public QueueInterface getToVertices(T vertex) // Returns a queue of the vertices that are adjacent from vertex. { QueueInterface adjVertices = new LinkedQueue(); int fromIndex, toIndex; fromIndex = indexIs(vertex); for (toIndex = 0; toIndex < numVertices; toIndex++) if (edges[fromIndex][toIndex] != NULL_EDGE) adjVertices.enqueue(vertices[toIndex]); return adjVertices; } We leave writing isFull, isEmpty, hasVertex, and the marking operations (clearMarks, markVertex, isMarked, and getUnmarked) for you as exercises. Linked Implementation The advantages of representing the edges in a graph with an adjacency matrix are twofold: speed and simplicity. Given the indices of two vertices, determining the existence (or the weight) of an edge between them is an O(1) operation. The problem with adjacency matrices is that their use of space to 2 store the edge information is O(N ), where N is the maximum number of vertices in the graph. If the maximum number of vertices is large but the number of actual vertices is small, or if a graph is sparse (the ratio between the number of edges and number of vertices is small), adjacency matrices waste a lot of space. We can save space by allocating memory as needed at run time, using linked structures. Adjacency lists are linked lists, one list per vertex, that identify the vertices to which each vertex is connected. They can be implemented in several different ways. Figures 10.6 and 10.7 show two different adjacency list representations of the graph in Figure 10.3. In Figure 10.6, the vertices are stored in an array. Each component of this array contains a reference to a linked list of edge information. Each item in these linked lists contains an index number, a weight, and a reference to the next item in the adjacency list. Look at the adjacency list for Denver. The first item in the list indicates that there is a 1,400-mile flight from Denver to Atlanta (the vertex whose index is 0) and the second item indicates a 1,000mile flight from Denver to Chicago (the vertex whose index is 2). No arrays are used in the implementation illustrated in Figure 10.7. Instead, the list of vertices is also implemented as a linked list. Now each item in the adjacency list contains a reference to the vertex information rather than the index of the vertex. Because Figure 10.7 includes so many of these references, we have used text to describe the vertex that each reference designates rather than draw them as arrows. We leave the implementation of the Graph class methods using the linked approaches as a programming exercise. Space Efficiency When a graph has a large number of vertices but a small number of edges the adjacency list approaches to implementation are much more space efficient than the adjacency matrix approach. https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 8 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM Figure 10.6 Adjacency list representation of graphs 10.4 APPLICATION: GRAPH TRAVERSALS The graph specification given in Section 10.2, “The Graph Interface,” includes only the most basic operations; it does not include any traversal operations. As you might imagine, we can traverse a graph in many different orders. Therefore, we treat traversal as a graph application rather than as an innate operation. The basic operations given in our specification allow us to implement different traversals independent of how the graph itself is implemented. Code Files The application UseGraph in the ch10.apps package contains the code for all the algorithms presented in Sections 10.4 and 10.5, “Application: Graph Traversals” and “Application: The Single-Source Shortest-Paths Problem.” Each algorithm is implemented as a separate private method within the application. The main method of UseGraph acts as a test driver for this code. Because UseGraph uses the WeightedGraph class partially developed in Section 10.3, “Implementations of Graphs,” in order to use the program you must finish coding that class. See Exercise 17. In Section 7.1, “Trees,” we discussed two ways of traversing a general tree, depth-first, that repeatedly goes as deep as it can in the tree and then backs up, and breadth-first, that fans out across the tree level by level. With graphs, both depth-first and breadth-first strategies are also extremely important. We discuss algorithms for employing both strategies within the context of determining whether two cities are connected in our airline example. Note that these same basic approaches can be used to solve many other problems, when traversing the vertices of a graph in an organized fashion is required. Figure 10.7 Alternate adjacency list representation of graphs Depth-First Searching One question we can answer using the graph in Figure 10.3 is “Can I get from city X to city Y on my favorite airline?” This is equivalent to asking, “Does a path exist in the graph from vertex X to vertex Y?” Using a depthfirst strategy, we develop an algorithm IsPathDF that determines whether a path exists from startVertex to endVertex. We need a systematic way to keep track of the cities as we investigate them. With a depth-first search, we examine the first vertex that is adjacent from startVertex; if it is endVertex, the search is over. Otherwise, we examine all of the vertices that can be reached in one step (are adjacent) from this first vertex. While we examine these vertices, we need to store the remaining vertices adjacent from startVertex that have not yet been examined. If a path does not exist from the first vertex, we come back and try the second vertex, third vertex, and so on. Because we want to travel as far as we can down one path, backtracking if the endVertex is not found, a stack is a good structure for storing the vertices. However, there is a problem with the approach we just described. Graphs can contain cycles. We can fly from Washington to Dallas to Denver and then back to Washington. Do you see why this is an issue in the approach described above? Not only is it likely that we will unnecessarily repeat the pro- https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 9 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM cessing of a city (a vertex), it is even possible to get caught in an infinite loop (e.g., when processing Washington we push Atlanta onto the stack, and when processing Atlanta we push Washington onto the stack). We did not have this issue when designing our tree traversals in Chapter 7 because trees by definition do not have cycles. How can we fix this? You probably recall that addressing this problem was the reason we included the ability to mark vertices within our Graph ADT. Our solution is to mark vertices to indicate that they have been placed on the stack. And, of course, to use those marks to prevent pushing a vertex onto the stack more than once. This way we eliminate repetitiously processing a vertex and the possibility of an infinite loop. Here is the algorithm: IsPathDF (startVertex, endVertex): returns boolean Set found to false Clear all marks Mark the startVertex Push the startVertex onto the stack do Set current vertex = stack.top() stack.pop() if current vertex equals endVertex Set found to true else for each adjacent vertex if adjacent vertex is not marked Mark the adjacent vertex and Push it onto the stack while !stack.isEmpty() AND !found return found Let us apply this algorithm to the sample airline-route graph in Figure 10.3. We will trace the algorithm applied to the case where Austin is the starting city and Washington is the desired destination. In Figure 10.8 we repeat the airport graph, abbreviating the names of the cities using their first two letters and dropping the distances, because they are not pertinent to our current question. We use boldface to indicate that a city has been processed— that it has been checked to see if it is the destination and if not then its adjacent cities have been placed on the stack if appropriate. We also sequentially number the entry edge into a city when it is processed, the edge that is followed to lead to the processing of the city. Finally, we indicate that a city has been marked by placing a checkmark beside it. Figure 10.8 IsPathDF algorithm trace Okay, so we want to fly from Austin to Washington. We initialize our search by marking our starting city and pushing it onto the stack (Figure 10.8a). At the beginning of the do-while loop we retrieve the current city, Austin, from the stack using the top method and then remove it from the stack using the pop method. Because Austin is not our final destination we examine the places we can reach directly from it—Dallas and Houston (we will process adjacent vertices in alphabetical order); neither of these is marked so we mark them and push them onto the stack (Figure 10.8b). That completes the processing of Austin. https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 10 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM At the beginning of the second iteration we retrieve and remove the top vertex from the stack—Houston. Houston is not our destination, so we resume our search from there. There is only one flight out of Houston, to Atlanta; Atlanta has not yet been marked so we mark it and push it onto the stack (Figure 10.8c), finishing our processing of Houston. Again we retrieve and remove the top vertex from the stack. Atlanta is not our destination, so we continue searching from there. Atlanta has flights to two cities: Houston and Washington. Houston was already marked so at this stage we mark only Washington and push it onto the stack (Figure 10.8d). Finally, we retrieve and remove the top vertex from the stack, Washington. This is our destination, so the search is complete (Figure 10.8e). As indicated by the final part of Figure 10.8e we had a very successful and efficient search, processing only four cities during our search including the source and destination cities. Although the algorithm (since it is correct!) guarantees success in that it will always correctly identify whether a path exists, it might not always be so efficient. Consider a new question: can we fly from Dallas to Austin. A quick check of the graph tells us that yes we can, in fact it is a one hop trip. Yet in this case the algorithm will process every vertex in the graph before determining that the path exists. The reader is encouraged to verify this for themselves by tracing the algorithm on the new problem. Method isPathDF receives a graph object, a starting vertex, and a target vertex. It uses the depth-first algorithm described above to determine whether a path exists from the starting city to the ending city, displaying the names of all cities processed in the search. Nothing in the method depends on the implementation of the graph. The method is implemented as a graph application; it uses the Graph ADT operations without knowing how the graph is represented. We use a graph of strings, since we can represent a city by its name. In the following code, we assume that a stack and a queue implementation have been imported into the client class. (The isPathDF method is included in the UseGraph.java application, available in the ch10.apps package.) private static boolean isPathDF(WeightedGraphInterface graph, String startVertex, String endVertex) // Returns true if a path exists on graph, from startVertex to endVertex; // otherwise returns false. Uses depth-first search algorithm. { StackInterface stack = new LinkedStack(); QueueInterface vertexQueue = new LinkedQueue(); boolean found = false; String currVertex; // vertex being processed String adjVertex; // adjacent to currVertex graph.clearMarks(); graph.markVertex(startVertex); stack.push(startVertex); do { currVertex = stack.top(); stack.pop(); System.out.println(currVertex); if (currVertex.equals(endVertex)) found = true; else { vertexQueue = graph.getToVertices(currVertex); while (!vertexQueue.isEmpty()) { adjVertex = vertexQueue.dequeue(); if (!graph.isMarked(adjVertex)) { graph.markVertex(adjVertex); stack.push(adjVertex); } } } } while (!stack.isEmpty() && !found); return found; } Depth-First or Breadth-First The best search approach to use for a particular problem depends upon the structure of the graph and the nature of the problem. In general, if you are likely to find the end vertex close to the start vertex then breadth-first search is probably preferred. On the other hand, if you expect there may be several target vertices that fulfill your needs but believe they might be far away from the start vertex then depth-first is probably best. For any given problem and environment it might be wise to perform some experiments before selecting an approach. Breadth-First Searching A breadth-first search looks at all possible paths at the same depth before it goes to a deeper level. In our flight example, a breadth-first search checks all possible one-stop connections before checking any two-stop connections. For most travelers, this strategy is the preferred approach for booking flights. When we come to a dead end in a depth-first search, we back up as little as possible. We then try another route from a recent vertex—the route on top of our stack. In a breadth-first search, we want to back up as far as possible to find a route originating from the earliest vertices. The stack is not the right structure for finding an early route, because it keeps track of things in the order opposite of their occurrence—the latest route is on top. To keep track of things in the order in which they happened, we use a FIFO queue. The https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 11 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM route at the front of this queue is a route from an earlier vertex; the route at the back of the queue is from a later vertex. To modify the search to use a breadth-first strategy, we change all calls to stack operations to the analogous FIFO queue operations in the previous algorithm. Just as we did with the depth-first search, we mark a vertex before placing it in the queue, to avoid repetitive processing. Here is the breadth-first algorithm: IsPathBF (startVertex, endVertex): returns boolean Set found to false Clear all marks Mark the startVertex Enqueue the startVertex into the queue do Set current vertex = queue.dequeue() if current vertex equals endVertex Set found to true else for each adjacent vertex if adjacent vertex is not marked Mark the adjacent vertex and Enqueue it into the queue while !queue.isEmpty() AND !found return found Figure 10.9 shows the sequence in which vertices are visited for several depth-first and breadth-first searches using our flights example. The numbers in the figure indicate the order in which entry edges are used to “visit” vertices. Keep in mind our assumption that vertices are placed into the queue (or stack) in alphabetical order. Let us revisit our previous example, searching for a path from Austin to Washington, but this time with a breadth-first search. We start with Austin in the queue. We dequeue Austin and enqueue all the cities that can be reached directly from it: Dallas and Houston. Then we dequeue the front queue element, Dallas (indicated by the circled 1 in Figure 10.9b). Because Dallas is not the destination we seek, we enqueue all the adjacent cities from Dallas that have not yet been marked: Chicago and Denver. (Austin has been visited already, so it is not enqueued.) Again we dequeue the front element from the queue. This element is the other “one-stop” city: Houston (indicated by the circled 2 in Figure 10.9b). Houston is not the desired destination, so we continue the search. There is only one flight out of Houston, and it is to Atlanta. Because we have not marked Atlanta before, it is enqueued. This processing continues until Washington is put into the queue (from Atlanta), and is finally dequeued. We have found the desired city, and the search is complete. Figure 10.9 Examples of search paths As you can see in Figure 10.9b the breadth-first search of a path from Austin to Washington processed every city in the graph before determining that the path existed. It was not as efficient as the depth-first search for the same path (Figure 10.9a). On the other hand, a comparison of Figures 10.9c and 10.9d shows that in another case, searching for a path from Dallas to Austin, the breadth-first search is more efficient. It is really just a matter of luck— the number of steps depends on the graph configuration and the source and destination vertices. Comparing Figures 10.9e and 10.9f shows an example, searching for a path from Washington to Austin, where the same number of entry edges are used in each approach. The source code for the breadth-first search approach is identical to the depth-first search code, except for the replacement of the stack with a FIFO queue. It is also included in the UseGraph.java application, as the method isPathBF, available in the ch10.apps package. 10.5 APPLICATION: THE SINGLE-SOURCE SHORTEST-PATHS PROBLEM We know from the two search operations discussed in Section 10.4, “Application: Graph Traversals,” that there may be multiple paths from one vertex to another. Continuing the example scenario from that section, suppose that we want to find the shortest path from Austin to each of the other cities that https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 12 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM Air Busters serves. By “shortest path” we mean the path whose edge values (weights), added together, have the smallest sum. Consider the following two paths from Austin to Washington: Clearly, the first path is preferable, unless we want to collect extra frequentflyer miles. Let us develop an algorithm that displays the shortest path from a designated starting city to every other city in the graph. As in the two graph search algorithms described earlier, we need an auxiliary structure for storing cities that we process later. By retrieving the city that was most recently put into the structure, the depth-first search tries to keep going “forward.” It tries a one-flight solution, then a two-flight solution, then a three-flight solution, and so on. It backtracks to a fewer-flight solution only when it reaches a dead end. That approach is not suitable for our shortest-paths problem. By retrieving the city that had been in the structure the longest time, the breadth-first search tries all one-flight solutions, then all two-flight solutions, and so on. The breadth-first search finds a path with a minimum number of flights. But a minimum number of flights does not necessarily mean the minimum total distance. That approach is also unsuitable for our shortest-paths problem. Dijkstra’s Algorithm The algorithm presented in this section for finding the shortest past is attributed to computer scientist Edsger W. Dijkstra who conceived of the algorithm in 1956 and published it in 1959. Dijkstra contributed a great deal to many facets of computer science. Unlike the depth-first and breadth-first searches, this shortest-path traversal must use the number of miles (edge weights) between cities. We want to retrieve the vertex that is closest to the current vertex—that is, the vertex connected with the minimum edge weight. By always adding the next shortest path we are guaranteed to identify the correct paths. If we consider minimum distance to be the highest priority, then we know the perfect structure —the priority queue. Our algorithm can use a priority queue whose elements are flights (edges) with the distance from the starting city as the priority. That is, the elements in the priority queue are objects with three attributes: fromVertex, toVertex, and distance. We use a class named Flight to define these objects. The class implements the Comparable interface, using the distance attribute to compare two flights (shorter is better). It provides a constructor that accepts three arguments, one for each attribute, and it provides the standard setter and getter methods for the attributes. The code is found in our support package. Here is the shortest-path algorithm: shortestPaths(graph, startVertex) graph.ClearMarks() Create flight(startVertex, startVertex, 0) pq.enqueue(flight) Caution: do Contains subtle error flight = pq.dequeue() if flight.getToVertex() is not marked Mark flight.getToVertex() Write flight.getFromVertex, flight.getToVertex, flight.getDistance flight.setFromVertex(flight.getToVertex()) Set minDistance to flight.getDistance() Get queue vertexQueue of vertices adjacent from flight.getFromVertex() while more vertices in vertexQueue Get next vertex from vertexQueue if vertex not marked flight.setToVertex(vertex) flight.setDistance(minDistance + graph.weightIs(flight.getFromVertex(), vertex)) pq.enqueue(flight) while !pq.isEmpty() The algorithm for the shortest-path traversal is similar to those we used for the depth-first and breadth-first searches, albeit with three major differences: 1. We use a priority queue rather than a FIFO queue or stack. Note that when processing halts there can still be many flights left on the priority queue, but they are longer flights than the ones that have been identified as being part of the solution. 2. We stop only when there are no more cities to process; there is no destination. 3. It is incorrect if we use a store-by-reference priority queue! https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 13 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM When we code this algorithm, we are likely to make a subtle, but crucial error. This error is related to the fact that our queues store information “by reference” and not “by copy.” Take a minute to review the algorithm to see if you can spot the error before continuing. Recall the feature section in Chapter 5 that discussed the dangers of storing information by reference. In particular, it warned us to be careful when inserting an object into a structure and later making changes to that object. If we use the same reference to the object when we make changes to it, the changes are made to the object that is in the structure. Sometimes this outcome is what we want (see the word frequency counter application in Chapter 7); at other times it causes problems, as in the current example. Here is the incorrect part of the algorithm: while more vertices in vertexQueue Get next vertex from vertexQueue if vertex not marked flight.setToVertex(vertex) flight.setDistance(minDistance + graph.weightIs(flight.getFromVertex(), vertex)) pq.enqueue(flight) Now can you see the problem? This part of the algorithm walks through the queue of vertices adjacent to the current vertex and enqueues Flight objects onto the priority queue pq based on the information discovered there. The flight variable is actually a reference to a Flight object. Suppose the queue of adjacent vertices holds information related to the cities Atlanta and Houston. The first time through this loop, we insert information related to Atlanta in flight and enqueue it in pq. The next time through the loop, however, we make changes to the Flight object referenced by flight. We update it to contain information about Houston using the setter methods; and again we enqueue it in pq. So now pq contains information about Atlanta and Houston, correct? Nope. When we change the information in flight to the Houston information, those changes are reflected in the flight that is already on pq. The flight variable still references that object. In reality, the pq structure now contains two references to the same flight, and that flight contains Houston information. To solve this problem, we must create a new flight object before storing on pq. Here is the corrected version of that part of the algorithm: while more vertices in vertexQueue Get next vertex from vertexQueue if vertex not marked Set newDistance to minDistance + graph.weightIs(flight.getFromVertex(), vertex) Create new Flight(flight.getFromVertex(), vertex, newDistance) pq.enqueue(newFlight) Here is the source code for the shortest-path algorithm (also included in the UseGraph. java application). As before, the code assumes that a priority queue and a queue implementation have been imported into the client class. For the priority queue, we use our HeapPriQ class from Chapter 9. We want a smaller distance to indicate a higher priority, but our HeapPriQ class implements a maximum heap, returning the largest value from the dequeue method. To fix this problem, we could define a new heap class, a minimum heap. But there is an easier way. The current HeapPriQ class bases its decision about what is “larger” on the values returned by the flight’s compareTo method. Thus, we just define the compareTo method of the Flight class to indicate that the current flight is “larger” than the argument flight if its dis‐ tance is smaller. For every flight in the heap’s tree, flight.distance is then less than or equal to the distance value of each of its children. We can still use our maximum heap. private static void shortestPaths(WeightedGraphInterface graph, String startVertex) // Writes the shortest distance from startVertex to every // other reachable vertex in graph. { Flight flight; Flight saveFlight; // for saving on priority queue int minDistance; int newDistance; PriQueueInterface pq = new HeapPriQ(20); String vertex; QueueInterface vertexQueue = new LinkedQueue(); graph.clearMarks(); saveFlight = new Flight(startVertex, startVertex, 0); pq.enqueue(saveFlight); System.out.println("Last Vertex Destination Distance"); System.out.println("------------------------------------"); do { flight = pq.dequeue(); if (!graph.isMarked(flight.getToVertex())) { graph.markVertex(flight.getToVertex()); System.out.println(flight); flight.setFromVertex(flight.getToVertex()); minDistance = flight.getDistance(); vertexQueue = graph.getToVertices(flight.getFromVertex()); while (!vertexQueue.isEmpty()) { vertex = vertexQueue.dequeue(); if (!graph.isMarked(vertex)) { newDistance = minDistance + graph.weightIs(flight.getFromVertex(), vertex); saveFlight = new Flight(flight.getFromVertex(), vertex, newDistance); pq.enqueue(saveFlight); } https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 14 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM } } } while (!pq.isEmpty()); System.out.println(); } The output from this method is a table of city pairs (edges) showing the total minimum distance from startVertex to each of the other vertices in the graph, as well as the last vertex visited before the destination. We assume that printing a vertex means printing the name of the corresponding city. If graph contains the information shown in Figure 10.3, the method called shortestPaths(graph, startVertex); where startVertex corresponds to Washington, would print the following: Last Vertex Destination Distance Washington Washington 0 Washington Atlanta 600 Washington Dallas 1,300 Atlanta Houston 1,400 Dallas Austin 1,500 Dallas Denver 2,080 Dallas Chicago 2,200 The shortest-path distance from Washington to each destination is shown in the two columns to the right. For example, our flights from Washington to Chicago total 2,200 miles. The left-hand column shows which city immediately preceded the destination in the traversal. We want to figure out the shortest path from Washington to Chicago. First we find our destination, Chicago, in the Destination (middle) column, and see from the left-hand column that the next-to-last vertex in the path is Dallas. Now we look up Dallas in the Destination (middle) column—the vertex before Dallas is Washington. The whole path is Washington–Dallas–Chicago. (We might want to consider another airline for a more direct route!) Unreachable Vertices You may have noticed that in all of our examples so far we have been able to “reach” all of the other vertices in our graphs from our given starting vertex. What if this is not the case? Consider the weighted graph in Figure 10.10, which depicts a new set of airline-flight legs. It is identical to Figure 10.3 except that we removed the Washington–Dallas leg. If we invoke our short‐ estPaths method, passing it this graph and a starting vertex of Washington, we get the following output: Last Vertex Destination Distance Washington Washington 0 Washington Atlanta 600 Atlanta Houston 1,400 A careful study of the new graph confirms that one can reach Atlanta and Houston only, when starting in Washington, at least when flying Air Buster Airlines. Suppose we extend the specification of our shortestPaths method to require that it also prints the unreachable vertices. How can we determine the unreachable vertices after we have generated the information about the reachable ones? Easy! The unreachable vertices are the unmarked vertices. We simply need to check whether any vertices remain unmarked. Our Graph ADT provides the operation getUnmarked specifically for this situation. So we simply add the following code to our method: https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 15 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM Figure 10.10 A new set of airline-flight legs System.out.println("The unreachable vertices are:"); vertex = graph.getUnmarked(); while (vertex != null) { System.out.println(vertex); graph.markVertex(vertex); vertex = graph.getUnmarked(); } Now the output from the call to shortestPaths would be Last Vertex Destination Distance Washington Washington 0 Washington Atlanta 600 Atlanta Houston 1,400 The unreachable vertices are: Austin Chicago Dallas Denver In Exercise 43 we ask you to investigate counting the “connected components” of a graph. This is another interesting application of the getUn‐ marked method. SUMMARY In this chapter, we discussed graphs. After reviewing graph terminology we defined a Graph ADT and discussed implementation approaches, providing a partially finished array-based implementation. The set of abstract methods included in WeightedGraphInterface is designed to allow an application to implement graph-related algorithms. Three such algorithms, depth-first path discovery, breadth-first path discovery, and shortest paths, were presented. The array-based graph implementation provides time-efficient operations. We also discussed a space-efficient reference-based implementation. Time and space efficiency trade-offs are often the key considerations when choosing among alternative implementations of a data structure. Graphs are the most complex structure we have studied. They are very versatile and are a good way to model many real-world objects and situations. Because many different types of applications could potentially use graphs, numerous variations and generalizations of their definitions and implementations exist. In addition, many advanced algorithms for manipulating and traversing graphs have been discovered. They are generally covered in detail in advanced computer science courses on algorithms. EXERCISES 10.1 Introduction to Graphs 1. True/False. Explain your answer—in some cases drawing an example figure suffices for an explanation. 1. A graph can have more vertices than edges. 2. A graph can have more edges than vertices. 3. A graph with just one vertex is connected. 4. An edgeless graph with two or more vertices is disconnected. 5. A graph with three vertices and one edge is disconnected. 6. A connected graph with N vertices must have at least N–1 edges. https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 16 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM 7. A graph with five vertices and four edges is connected. 8. An edge cannot connect a vertex to itself. 9. All graphs are trees. 10. All trees are graphs. 11. A directed graph must be weighted. 2. Each of the following can be modeled as graphs. Describe in each case how you would define the vertices and how you would define the edges. 1. Friendships 2. Television show costars 3. Research literature citations 4. The Internet 5. The Web 3. For each example in Exercise 2, describe how the graph could be weighted; that is, what might the weights on the edges of the graph represent? 4. Draw an undirected graph, if possible (if not possible explain why not) that has 1. five vertices and three edges and is connected. 2. five vertices, three edges, and two connected components. 3. five vertices, two edges, and three connected components. 4. five vertices, two edges, and two connected components. 5. five vertices, six edges, and two connected components. Use the following description of an undirected graph in Exercises 5 and 6 : EmployeeGraph = V(EmployeeGraph) = (V, E) {Susan, Darlene, Mike, Fred, John, Sander, Lance, Jean, Brent, Fran} {(Susan, Darlene), (Fred, Brent), (Sander, Susan), (Lance, Fran), (Sander, E(EmployeeGraph) = Fran), (Fran, John), (Lance, Jean), (Jean, Susan), (Mike, Darlene), (Brent, Lance), (Susan, John)} 5. Draw a picture of EmployeeGraph. 6. Which one of the following phrases best describes the relationship represented by the edges between the vertices in EmployeeGraph? 1. “works for” 2. “is the supervisor of” 3. “is senior to” 4. “works with” Use the following specification of a directed graph in Exercises 7– 9 : ZooGraph = V(ZooGraph) = (V, E) {dog, cat, animal, vertebrate, oyster, shellfish, invertebrate, crab, poodle, monkey, banana, dalmatian, dachshund} {(vertebrate, animal), (invertebrate, animal), (dog, vertebrate), (cat, vertebrate), E(ZooGraph) = (monkey, vertebrate), (shellfish, invertebrate), (crab, shellfish), (oyster, shellfish), (poodle, dog), (dalmatian, dog), (dachshund, https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 17 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM dog)} 7. Draw a picture of ZooGraph. 8. To tell if one element in ZooGraph has relation X to another element, you look for a path between them. Show whether the following statements are true, using your picture or the specification. 1. dalmatian X dog 2. dalmatian X vertebrate 3. dalmatian X poodle 4. banana X invertebrate 5. oyster X invertebrate 6. monkey X invertebrate 9. Which of the following phrases best describes relation X in Exercise 8? 1. “has a” 2. “is an example of” 3. “is a generalization of” 4. “eats” Use the following graph for Exercises 10 and 11: 10. Describe the graph pictured above, using the formal graph notation. V(StateGraph) = E(StateGraph) = 11. In the states graph: 1. Is there a path from Oregon to any other state in the graph? 2. Is there a path from Hawaii to every other state in the graph? 3. From which state(s) in the graph is there a path to Hawaii? 10.2 The Graph Interface 12. Classify the methods defined in the WeightedGraphInterface as observers or transformers or both. 13. It is possible to define more operations for a Graph ADT. Describe two operations that you think would be useful additions to the Weighted‐ GraphInterface, and explain why. 14. Suppose g has been declared to be of type WeightedGraphInter‐ face and instantiated as an object of a class that implements that interface. Assume that g consists of seven vertices A, B, C, D, E, F, G and seven directed edges A-B, B-C, C-D, D-C, D-F, F-B, and G-E with all weights equal to 1. Show the expected output of the section of code below. Assume that capital letters A through G are of type T (the vertex type), that getToVertices returns a queue with the vertices in alphabetical order, that getUnmarked returns unmarked vertices in alphabetical order, that the null edge value is 0, and that printing a vertex displays its corresponding letter. System.out.println(g.isEmpty()); System.out.println(g.weightIs(A,B)); System.out.println(g.weightIs(B,A)); System.out.println(g.weightIs(A,C)); g.clearmarks(); g.markVertex(A); g.markVertex(C); System.out.println(g.ismarked(B)); g.markVertex(g.getUnmarked())) System.out.println(g.isMarked(B)); System.out.println(g.getUnmarked()); System.out.println(g.getToVertices(D).dequeue()); https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 18 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM 10.3 Implementations of Graphs 15. Draw the adjacency matrix for EmployeeGraph (see Exercise 5). Store the vertices in alphabetical order. 16. Draw the adjacency matrix for ZooGraph (see Exercise 7). Store the vertices in alphabetical order. Note: Exercises 18–22, 38, and 42 are dependent on the completion of Exercise 17. 17. Complete the implementation of the Weighted Graph (WeightedGraph.java) that we began in this chapter by providing bodies for the methods isEmpty, isFull, hasVertex, clearMarks, markVertex, isMarked, and getUnmarked in the WeightedGraph.java file. Test the completed implementation using the UseGraph class. When implementing hasVertex, do not forget to use the equals method to compare vertices. 18. For each of the methods in the WeightedGraph implementation, identify its order of growth efficiency. 19. Class WeightedGraph in this chapter is to be extended to include a boolean edgeExists operation that determines whether two vertices are connected by an edge. 1. Write the declaration of this method. Include adequate comments. 2. Implement the method. 20. Class WeightedGraph in this chapter is to be extended to include an int connects operation, passed two arguments a and b of type T representing two vertices in the graph (guaranteed as a precondition that both a and b are vertices) that returns a count of the number of vertices v such that v is connected to both a and b. 1. Write the declaration of this method. Include adequate comments. 2. Implement the method. 21. Class WeightedGraph in this chapter is to be extended to include a removeEdge operation that removes a given edge. 1. Write the declaration of this method. Include adequate comments. 2. Implement the method. 22. Class WeightedGraph in this chapter is to be extended to include a removeVertex operation that removes a vertex from the graph. 1. Deleting a vertex is more complicated than deleting an edge from the graph. Discuss the reasons for this operation’s greater complexity. Write the declaration of this method. Include adequate comments. 2. Implement the method. 23. Graphs can be implemented using arrays or references. For the states graph (see Exercise 10): 1. Show the adjacency matrix that would describe the edges in this graph. Store the vertices in alphabetical order. 2. Show the array of adjacency lists that would describe the edges in this graph. 24. What percent of the adjacency matrix representation of a graph consists of null edges if the graph contains 1. 10 vertices and 10 edges? 2. 100 vertices and 100 edges? 3. 1,000 vertices and 1,000 edges? 25. Assuming an adjacency matrix-based directed graph implementation as described in Section 10.3, “Implementations of Graphs,” describe an algorithm that solves each of the following problems; include an efficiency analysis of your solutions. 1. Return the number of vertices in the graph. https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 19 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM 2. Return the number of edges in the graph. 3. Return the highest number of “out edges” exhibited by a vertex; for example, for the graph in Figure 10.3 this would return 3 because Dallas has three cities adjacent to it 4. Return the highest number of “in edges” exhibited by a vertex. For example, for the graph in Figure 10.3 this would return 3 because Atlanta has three cities it is adjacent from. 5. Return the number of “singleton” vertices, that is, vertices that are not connected to any other vertex. 26. Repeat Exercise 25 using the adjacency list–directed graph implementation approach represented in Figure 10.6. 27. Specify, design, and code an undirected, weighted graph class using an adjacency matrix representation. 28. Specify, design, and code an undirected, unweighted graph class using an adjacency matrix representation. 29. Design and code a reference-based weighted graph class with the vertices stored in an array as in Figure 10.6. Your class should implement our WeightedGraphInterface. 30. Design and code a reference-based weighted graph class with the vertices stored in a linked list as in Figure 10.7. Your class should implement our WeightedGraphInterface. 10.4 Application: Graph Traversals For exercises 31–36 assume getToVertices and getUnmarked return information in alphabetical order. 31. Using the EmployeeGraph (see Exercise 5) describe both the order in which vertices would be marked and the order they would be visited/processed using the depth-first “is there a path” algorithm, when searching for a path: 1. from Susan to Lance 2. from Brent to John 3. from Sander to Darlene 32. Repeat Exercise 31 using the breadth-first algorithm. 33. Using the StateGraph (see Exercise 10) describe both the order in which vertices would be marked and the order they would be visited/processed using the depth-first “is there a path” algorithm, when searching for a path: 1. from Texas to Alaska 2. from Hawaii to California 3. from Hawaii to Texas 34. Repeat Exercise 33 using the breadth-first algorithm. 35. We reconfigure the Air Busters Airlines graph in Figure 10.3 so that the edges from Dallas to Austin and from Denver to Atlanta are dropped and an edge from Denver to Washington with weight 700 is added. Describe both the order in which vertices would be marked and the order they would be visited/processed using the depth-first “is there a path” algorithm, when searching for a path: 1. from Dallas to Houston 2. from Denver to Austin 36. Repeat Exercise 35 using the breadth-first algorithm. 37. Consider the following full graph of seven vertices. https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 20 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM 1. Recreate the drawing of the graph so that it shows only the leadin edges to vertices that would be visited by a depth-first search starting from the vertex at the top of the image. Note that there are many possible answers. 2. Recreate the drawing of the graph so that it shows only the leadin edges to vertices that would be visited by a breadth-first search starting from the vertex at the top of the image. Note that there is only one possible answer. 38. A text file contains information about a directed unweighted graph, where the vertices are strings, by listing the edges line by line. Each line contains the from-vertex, followed by the symbol #, followed by the to-vertex. For example, heap#priority queue represents an edge from the vertex “heap” to the vertex “priority queue” perhaps in this case representing the fact that a heap is used to implement a priority queue. Create an application that is given the name of such a text file as a command line argument, generates the corresponding graph using WeightedGraph from the ch10.graphs package, and then repeatedly prompts the user to enter a from-vertex and a to-vertex, reporting back to the user (a) one or both of the vertices does not exist or (b) whether there exists a path from the given from-vertex to the given tovertex. Create a suitable input file and test your program. Submit a report. 10.5 Application: The Single-Source Shortest-Paths Problem 39. Trace the Shortest Paths algorithm (or code) including the latter section about unreachable vertices and show what it would output for our Air Busters Airlines example (Figure 10.3) if the starting vertex is 1. Dallas 2. Atlanta 40. We reconfigure the Air Busters Airlines graph in Figure 10.3 so that the edges from Dallas to Austin, Denver to Atlanta, Washington to Dallas, and Austin to Houston are dropped and an edge from Denver to Washington with weight 700 is added. Trace the Shortest Paths algorithm (or code) including the latter section about unreachable vertices and show what it would output for this new graph if the starting vertex is 1. Austin 2. Atlanta 41. For the StateGraph (see Exercise 10) list the unreachable vertices for each of the vertices in the graph. 42. Our shortestPaths method is concerned with the minimum distance between two vertices of a graph. Create a minEdges method that returns the minimum number of edges that exist on a path between two given vertices. You can put your new method in our useGraph class and use it to test your code. 43. Counting connected components. Informally, a connected component of a graph is a subset of the vertices of the graph such that all of the vertices in the subset are connected to each other by a path. For example, the following graph consists of three connected components. https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml Page 21 of 22 10 The Graph ADT - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:30 PM 1. Create an undirected, unweighted graph ADT (similar to Exercise 26 but you can use a linked representation if you wish). 2. Test it. 3. Create an application that generates a graph and counts the number of connected components. The graph can be “hard coded,” read from a file, or randomly generated. 4. Create an application that generates graphs with 26 vertices and: 1. 4, 8, 12, 16, 20, 24, 28, 32, 36, and 40 unique random edges. 2. five graphs for each of the number of edges (so you are generating 50 random graphs altogether). 3. outputs the average number of connected components for each edge count. 5. Submit a report that contains your code, the output, and a discussion. 1 An unchecked cast warning may be generated because the compiler can- not ensure that the array contains objects of class T—the warning can safely be ignored. Settings / Support / Sign Out © 2020 O'Reilly Media, Inc. Terms of Service / Privacy Policy ⏮ PREV 9 The Priority Queue ADT https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch10.xhtml NEXT 11 Sorting and Searching Algorithms ⏭ Page 22 of 22 11 Sorting and Searching Algorithms - Object-Oriented Data Structures Using Java, 4th Edition % ⏮ 7/5/20, 7:31 PM Object-Oriented Data Structures Using Java, 4th Edition NEXT PREV 10 The Graph ADT ! $ # Object-Oriented Data Structures Using Java™ ⏭ CHAPTER 11: Sorting and Searching Algorithms Ake13bk/Shutterstock KNOWLEDGE GOALS You should be able to describe the following sorting algorithms: selection sort bubble sort (two versions) insertion sort quick sort merge sort heap sort analyze the efficiency of the six sorting algorithms, in terms of time and space requirements discuss other sorting efficiency considerations: overhead, elimination of method calls, programmer time describe the concept of stability with respect to sorting algorithms describe and discuss the performance of the following search algorithms: sequential search of an unsorted list sequential search of a sorted list binary search searching a high-probability sorted list SKILL GOALS You should be able to implement the following sorting algorithms selection sort bubble sort (two versions) insertion sort quick sort merge sort heap sort determine the stability of a specific implementation of a sorting algorithm identify an appropriate search algorithm, given a description of the way data is organized At many points in this text we have gone to great trouble to keep information in sorted order: famous people sorted by name or year of birth, airline routes sorted by distance, integers sorted from smallest to largest, and words sorted alphabetically. One reason to keep lists sorted is to facilitate searching; given an appropriate implementation structure, a particular list element can be https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch11.xhtml Page 1 of 29 11 Sorting and Searching Algorithms - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:31 PM found faster if the list is sorted. In this chapter we directly examine several strategies for both sorting and searching, contrast and compare them, and discuss sorting/searching concerns in general. Even though sorting and searching are already supported by most modern software development platforms, we believe their study is beneficial for computer science students—for the same reasons you study data structures even though language libraries provide powerful structuring APIs, you should study sorting and searching. Their study provides a good way to learn basic algorithmic and analysis techniques that help form a foundation of problem-solving tools. Studying the fundamentals prepares you for further growth so that eventually you will be able to create your own unique solutions to the unique problems that you are sure to encounter one day. 11.1 SORTING Putting an unsorted list of data elements into order—sorting—is a very common and useful operation. Entire books have been written about sorting algorithms as well as algorithms for searching a sorted list to find a particular element. Sorting Efficiency We elect to measure sorting efficiency by focusing on the number of comparisons made during a sort. Alternately, we could focus on the number of “swaps”—the exchange of two values, or the number of “moves”—changing the location of an element. In Java when we swap or move elements we are actually manipulating references to elements. In some languages this is not the case and the actual elements themselves have to be moved, a potentially costly operation. In such cases programmers sometime mimic the “by-reference” model used by Java, using an array to store the elements and then manipulating array indices rather than the elements themselves. Because sorting a large number of elements can be extremely time consuming, an efficient sorting algorithm is very important. How do we describe efficiency? List element comparison—that is, the operation that compares two list elements to see which is smaller—is an operation central to most sorting algorithms. We use the number of required element comparisons as a measure of the efficiency of each algorithm. For each algorithm we calculate the number of comparisons relative to the size of the list being sorted. We then use order of growth notation, based on the result of our calculation, to succinctly describe the efficiency of the algorithm. In addition to comparing elements, each of our algorithms includes another basic operation: swapping the locations of two elements on the list. The number of element swaps needed to sort a list is another measure of sorting efficiency. In the exercises we ask you to analyze the sorting algorithms developed in this chapter in terms of that alternative measure. Another efficiency consideration is the amount of extra memory space required by an algorithm. For most applications memory space is not an important factor when choosing a sorting algorithm. We look at only two sorts in which space would be a consideration. The usual time versus space tradeoff applies to sorts—more space often means less time, and vice versa. A Test Harness To facilitate our study of sorting we develop a standard test harness, a driver program that we can use to test each of our sorting algorithms. Because we are using this program just to test our implementations and facilitate our study, we keep it simple. It consists of a single class called Sorts. The class defines an array that can hold 50 integers. The array is named values. Several static methods are defined: initValues. Initializes the values array with random numbers between 0 and 99; uses the abs method (absolute value) from the Java library’s Math class and the nextInt method from the Random class. isSorted. Returns a boolean value indicating whether the values array is currently sorted. swap. Swapping data values between two array locations is common in many sorting algorithms—this method swaps the integers between values[index1] and values[index2], where index1 and in‐ dex2 are parameters of the method. printValues. Prints the contents of the values array to the Sys‐ tem.out stream; the output is arranged evenly in 10 columns. Here is the code for the test harness: //--------------------------------------------------------------------------// Sorts.java by Dale/Joyce/Weems Chapter 11 // // Test harness used to run sorting algorithms. //--------------------------------------------------------------------------package ch11.sorts; import java.util.*; import java.text.DecimalFormat; public class Sorts { static final int SIZE = 50; // size of array to be sorted static int[] values = new int[SIZE]; // values to be sorted static void initValues() // Initializes the values array with random integers from 0 to 99. https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch11.xhtml Page 2 of 29 11 Sorting and Searching Algorithms - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:31 PM { Random rand = new Random(); for (int index = 0; index < SIZE; index++) values[index] = Math.abs(rand.nextInt()) % 100; } static public boolean isSorted() // Returns true if the array values are sorted and false otherwise. { for (int index = 0; index < (SIZE - 1); index++) if (values[index] > values[index + 1]) return false; return true; } static public void swap(int index1, int index2) // Precondition: index1 and index2 are >= 0 and < SIZE. // // Swaps the integers at locations index1 and index2 of the values array. { int temp = values[index1]; values[index1] = values[index2]; values[index2] = temp; } static public void printValues() // Prints all the values integers. { int value; DecimalFormat fmt = new DecimalFormat("00"); System.out.println("The values array is:"); for (int index = 0; index < SIZE; index++) { value = values[index]; if (((index + 1) % 10) == 0) System.out.println(fmt.format(value)); else System.out.print(fmt.format(value) + " "); } System.out.println(); } public static void main(String[] args) throws IOException { initValues(); printValues(); System.out.println("values is sorted: " + isSorted()); System.out.println(); swap(0, 1); printValues(); System.out.println("values is sorted: " + isSorted()); System.out.println(); } } In this version of Sorts the main method initializes the values array, prints the array, prints the value of isSorted, swaps the first two values of the array, and again prints information about the array. The output from this class as currently defined would look something like this: the values array is: 20 49 07 50 45 69 20 07 88 02 89 87 35 98 23 98 61 03 75 48 25 81 97 79 40 78 47 56 24 07 63 39 52 80 11 63 51 45 25 78 35 62 72 05 98 83 05 14 30 23 values is sorted: false the values array is: 49 20 07 50 45 69 20 07 88 02 89 87 35 98 23 98 61 03 75 48 25 81 97 79 40 78 47 56 24 07 63 39 52 80 11 63 51 45 25 78 35 62 72 05 98 83 05 14 30 23 values is sorted: false As we proceed in our study of sorting algorithms, we will add methods that implement the algorithms to the Sorts class and change the main method to invoke those methods. We can use the isSorted and printValues methods to help us check the results. Because our sorting methods are implemented for use with this test harness, they can directly access the static values array. In the general case, we could modify each sorting method to accept a reference to an array-based list to be sorted as an argument. 11.2 SIMPLE SORTS In this section we present three “simple” sorts, so called because they use an unsophisticated brute force approach. This means they are not very efficient; but they are easy to understand and to implement. Two of these algorithms were presented earlier in the text—the Selection Sort and the Insertion Sort. Here we revisit those algorithms, plus introduce the Bubble Sort, looking at them more formally than we did in the previous coverage. Selection Sort In Section 1.6, "Comparing Algorithms," we introduced the Selection Sort. Here we present this algorithm more formally. If we were handed a list of names on a sheet of paper and asked to put them in alphabetical order, we might use this general approach: 1. Select the name that comes first in alphabetical order, and write it on a second sheet of paper. 2. Cross the name out on the original sheet. https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch11.xhtml Page 3 of 29 11 Sorting and Searching Algorithms - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:31 PM 3. Repeat steps 1 and 2 for the second name, the third name, and so on until all the names on the original sheet have been crossed out and written onto the second sheet, at which point the list on the second sheet is sorted. This algorithm is simple to translate into a computer program, but it has one drawback: It requires space in memory to store two complete lists. This duplication is clearly wasteful. A slight adjustment to this manual approach does away with the need to duplicate space. Instead of writing the “first” name onto a separate sheet of paper, exchange it with the name in the first location on the original sheet. Repeating this until finished results in a sorted list on the original sheet of paper. Within our program the “by-hand list” is represented as an array. Here is a more formal algorithm. SelectionSort for current going from 0 to SIZE - 2 Find the index in the array of the smallest unsorted element Swap the current element with the smallest unsorted one Figure 11.1 shows the steps taken by the algorithm to sort a five-element array. Each section of the figure represents one iteration of the for loop. The first part of a section represents the “find the smallest unsorted array element” step. To do so, we repeatedly examine the unsorted elements, asking if each one is the smallest we have seen so far. The second part of a figure section shows the two array elements to be swapped, and the final part shows the result of the swap. Figure 11.1 Example of a selection sort (sorted elements are shaded) https://learning.oreilly.com/library/view/object-oriented-data-structures/9781284089103/xhtml/ch11.xhtml Page 4 of 29 11 Sorting and Searching Algorithms - Object-Oriented Data Structures Using Java, 4th Edition 7/5/20, 7:31 PM Figure 11.2 A snapshot of the selection sort algorithm During the progression, we can view the array as being divided into a sorted part and an unsorted part. Each time we perform the body of the for loop, the sorted part grows by one element and the unsorted part shrinks by one element. The exception is the very last step, when the sorted part grows by two elements. Do you see why? When all of the array elements except the last one are in their correct locations, the last one is in its correct location by default. This is why our for loop can stop at index SIZE - 2 instead of at the end of the array, index SIZE - 1. We implement the algorithm with a method selectionSort that becomes part of our Sorts class. This method sorts the values array that is declared in that class. It has access to the SIZE constant that indicates the number of elements in the array. Within the selectionSort method we use a variable, current, to mark the beginning of the unsorted part of the array. Thus the unsorted part of the array goes from index current to index SIZE - 1. We start out by setting current to the index of the first position (0). Figure 11.2 provides a snapshot of the array during the selection sort algorithm. We use a helper method to find the index of the smallest value in the unsorted part of the array. The minIndex method receives the first and last indices of the unsorted part, and returns the index of the smallest value in this part of the array. We also use the swap method that is part of our test harness. Here is the code for the minIndex and selectionSort methods. Because they are placed directly in our test harness class, a class with a main method, they are declared as static methods. static int minIndex(int startIndex, int endIndex) // Returns the index of the smallest value in // values[startIndex]to values[endIndex]. { int indexOfMin = startIndex; for (int index = startIndex + 1; index startIndex; index--) if (values[index] < values[index - 1]) { swap(index, index - 1); sorted = false; } return sorted; } static void shortBubble() // Sorts the values array using the bubble sort algorithm. // The process stops as soon as values is sorted. { int current = 0; boolean sorted = false; while ((current < (SIZE - 1)) && !sorted) { sorted = bubbleUp2(current, SIZE - 1); current++; } } The analysis of shortBubble is more difficult. Clearly, if the array is already sorted to begin with, one call to bubbleUp tells us so. In this best-case scenario, shortBubble is O(N); only N – 1 comparisons are required for the sort. But what if the original array was actually sorted in descending order before the call to shortBubble? This is the worst possible case: shortBubble requires as many comparisons as bubbleSort and selec‐ tionSort, not to mention the “overhead”—all the extra swaps and setting and resetting the sorted flag. Can we calculate an average case? In the first call to bubbleUp, when current is 0, there are SIZE - 1 comparisons; on the second call, when current is 1, there are SIZE - 2 comparisons. The number of comparisons in any call to bubbleUp is SIZE - current - 1. If we let N indicate SIZE and K indicate the number of calls to bubbleUp executed before shortBubble finishes its work, the total number of comparisons required is A little algebra changes this to 2 (2KN – 2K – K) / 2 In O notation, the term that is increasing the fastest relative to N is 2KN. We know that K is between 1 and N – 1. On average, over all possible input or2 ders, K is proportional to N. Therefore, 2KN is proportional to N ; that is, 2 the shortBubble algorithm is also O(N ). 2 Why do we even bother to discuss the bubble sort algorithm if it is O(N ) and requires extra data movements? Due to the extra intermediate swaps performed by the bubble sort, it can quickly sort an array that is “almost” sorted. If the shortBubble variation is used, a bubble sort can be very efficient in this situation. Insertion Sort In Section 6.4, “Sorted Array-Based List Implementation,” we described the Insertion Sort algorithm and how it could be used to maintain a list in sorted order. Here we present essentially the same algorithm, although for the present discussion we assume we start with an unsorted array and use Insertion Sort to change it into a sorted array. The principle of the insertion sort is quite simple: Each successive element in the array to be sorted is inserted into its proper place with respect to the other, already sorted elements. As with the previously mentioned sorting strategies, we divide our array into a sorted part and an unsorted part. (Unlike with the selection and bubble sorts, there may be values in the unsorted part that are less than values in the sorted part.) Initially, the sorted portion contains only one element: the first element in...
Purchase answer to see full attachment
User generated content is uploaded by users for the purposes of learning and should be used following Studypool's honor code & terms of service.

Explanation & Answer

Attached.

UML

Test Case
Test Case
1

Input
ClassA
ClassJ
ClassB
ClassC
ClassE
ClassH
ClassJ
ClassI

Output
ClassC ClassE

File successfully read!

ClassD ClassG
ClassA
ClassB ClassF

Adding vertex and
relations...
ClassA -> ClassC -> ClassE
-> ClassJ
ClassB -> ClassD -> ClassG
ClassC -> ClassA
ClassE -> ClassB -> ClassF
-> ClassH
ClassJ -> Cla...


Anonymous
Nice! Really impressed with the quality.

Studypool
4.7
Trustpilot
4.5
Sitejabber
4.4

Similar Content

Related Tags