/* CS 1501 Graph code segments for PFS from Sedgewick text -- these are segments from the Sedgewick text, but with comments added by me to make them more readable. This handout should help you to understand PFS better and it should also help you to understand the differences in the adjacency list and adjacency matrix implementations. Be sure to read over it VERY carefully, looking at the comments as if they were notes handed out in lecture. */ PQ pq(maxV); visit(int k) // PFS, adjacency lists { struct node *t; // Below is the special case of the root, since it is the only node whose // priority will still be "unseen" when it is selected into the tree. In // this case, set its dad to be 0, since it has no parent in the tree. if (pq.update(k, -unseen) != 0) dad[k] = 0; // Below we have the main part of the code. Compare this to BFS -- you // will note some similarities. The primary differences between PFS and // BFS are: // 1) PFS uses a PQ for the fringe, while BFS uses a FIFO queue // 2) PFS allows vertices in the fringe to be "modified" while in the fringe, // since their priorities may be updated when new edges are found. while (!pq.empty()) { // Remove the next vertex from the PQ (adding it to the tree). Make its // val positive (by negating it) to indicate that it is no longer in the // fringe. id++; k = pq.remove(); val[k] = -val[k]; // Special case for root -- if the val is -unseen here it must been unseen // before the line above -- the only vertex that is unseen before being // removed from the PQ is the root. Its val is then set to 0, indicating // that no edge connects into it. if (val[k] == -unseen) val[k] = 0; // Go through the adjacency list of the current vertex, k for (t = adj[k]; t != z; t = t->next) // If the neighbor, v, of k is not yet visited if (val[t->v] < 0) // Check to see if the "new" priority for v (from the point of k) is // better than the previous "best" priority for v. The value of // priority depends on the algorithm being used: // FOR MST: Priority is the edge weight from a vertex in T to v // (which at this point is still in T'). Thus, in this case // priority = t->w or t.w if you are using Java // FOR Shortest Path: Priority is the length of the current shortest // path from the source to v. Since the shortest path to // k has already been found (we just added k to the shortest // path tree, the priority to consider is // priority = val[k] + t->w // or, in words, the shortest path from source to k plus the // edge from k to v. // If the new priority is an improvement, change the priority for v to // the new value and update its val and dad arrays to reflect that change if (pq.update(t->v, priority)) { val[t->v] = -(priority); dad[t->v] = k; } } } // The algorithm shown here is the same as that shown above, but it looks // different since it is utilizing an adjacency matrix rather than an adjacency // list. The primary differences between the two are: // 1) The adjacency list algo traverses a linked list to find the neighbors of k, // while the adjacency matrix algo traverses a row in the matrix // 2) The adjacency list algo has an explicit PQ to store the "fringe" vertices, // whereas the adjacency matrix algo does not have this PQ, relying on the // val array alone for the priorities. This difference is explained below: // Consider the time to traverse the neighbors of k in an adjacency list. // It is proportional to d, the degree of k (where degree is the number // of neighbors of k). Simply put, the list is as long as the number of // neighbors of k. Clearly, this could be as long as v-1 but for sparse // graphs is often much less than v. // Now consider how to determine the "best" vertex to add to the tree // (i.e. the vertex that will be k at the next iteration). This is // the vertex with the best priority after k's neighbors have been // processed. One way to do his is to traverse the val array, looking // at all of the negative (fringe) values for the best priority. // However, this will take time v, since the val array is of length v, // which is often greater than d. Using a heap-implemented PQ can find // the next best priority vertex in time lg(v), which will improve the // overall run-time of the adjacency list algorithms when used with // sparse graphs. // // Consider now the time to traverse the neighbors of k in an adjacency // matrix. Since a row in the matrix must be traversed, this will always // take time v, which is the same amount of time required to find the // "best" next vertex to add using the val array. Thus, in this case, // using an explicit PQ will not save any time for the overall algorithm. void search() // PFS, adjacency matrix { int k, t, min = 0; // Initialize all vertices to unseen for (k = 1; k <= V; k++) { val[k] = unseen; dad[k] = 0; } // Since regular vertices start at index 1, use val[0] as a sentinel, and // give it a value such that all other priorities will always be BETTER than // its priority. val[0] = unseen-1; // Start at index 1, and each time through the loop set k to the next min // (best priority) vertex, setting min back to 0 for the next iteration. for (k = 1; k != 0; k = min, min = 0) { // Negate val to take k out of the fringe val[k] = -val[k]; // Special case for root -- set its val to 0 if (val[k] == -unseen) val[k] = 0; // Go through neighbors of k, checking priorities (as described above) // and updating them if improved by k. At the same time, keep track of // the overall "best" priority seen and assign the index to min -- this // will be the next vertex chosen. for (t = 1; t <= V; t++) if (val[t] < 0) // t is still in the fringe { if (a[k][t] && (val[t] < -priority)) // edge k,t exists and the // priority using k as t's parent in the tree will be better // than the current priority { val[t] = -priority; dad[t] = k; } // Update the priority and // the parent for t if (val[t] > val[min]) min = t; // Keep track of best priority // seen in index min } } }