Before I start, yes this is a homework.
I would not have posted here if I haven't been trying as hard as I could to solve this one for the last 14 hours and got nowhere.
The problem is as follows:
I want to check whether I can delete an edge from a connected undirected graph without disconnecting it or not in O(V) time, not just linear.
What I have reached so far:
A cycle edge can be removed without disconnecting the graph, so I simply check if the graph has a cycle.
I have two methods that could be used, one is DFS and then checking if I have back edges; the other is by counting Vs and Es and checking if |E| = |V| - 1, if that's true then the graph is a tree and there's no node we can delete without disconnecting it.
Both of the previous approaches solve the problem, but both need O(|E|+|V|), and the book says there's a faster way(that's probably a greedy approach).
Can I get any hints, please?
EDIT:
More specifically, this is my question; given a connected graph G=(V,E), can I remove some edge e in E and have the resulting graph still be connected?
Any recursive traversal of the graph, marking nodes as they're visited and short-circuiting to return true if you ever run into a node that is already marked will do the trick. This takes O(|V|) to traverse the entire graph if there is no edge that can be removed, and less time if it stops early to return true.
edit
Yes, a recusive traversal of the entire graph requires O(|V|+|E|) time, but we only traverse the entire graph if there are no cycles -- in which case |E| = |V|-1 and that only takes O(|V|) time. If there is a cycle, we'll find it after traversing at most |V| edges (and visiting at most |V|+1 nodes), which likewise takes O(|V|) time.
Also, obviously when traversing from a node (other than the first), you don't consider the edge you used to get to the node, as that would cause you to immediately see an already visited node.
You list all edges E and take an edge and mark one by one the two end vertices visited. If during traversing we find that the two vertices have been visited previously by some edges and we can remove that edge.
We have to take edges at most |V| time to see whether this condition satisfy.
Worst case may go like this, each time we take an edge it will visit atleast new vertex. Then there are |V| vertices and we have to take |V| edges for that particular edge to be found.
Best case may be the one with |V| / 2 + 1 e
Have you heard of spanning trees? A connected graph with V-1 edges.
We can remove certain edges from a connected graph G (like the ones which are creating cycle) until we get a connected tree. Notice that question is not asking you to find a spanning tree.
Question is asking if you can remove one or more edges from graph without loosing connectivity. Simply count number of edges and break when count grows beyond V-1 because the graph has scope to remove more edges and become spanning tree. It can be done in O(V) times if the graph is given in adjacency list.
From what I'm reading, DFS without repetition is considered O(|V|), so if you take edge e, and let the two vertices it connects be u and v, if you run DFS from u, ignoring e, you can surmise that e is not a bridge if v is discovered, and given that DFS without repetition is O(|V|), then this would, I guess be considered O(|V|).
Related
I'm attempting ch23 in CLRS on MSTs, here's a question:
Given a graph G and a minimum spanning tree T , suppose that we decrease the weight of one of the edges not in T . Give an algorithm for finding the minimum spanning tree in the modified graph.
A solution I found was to add this new changed edge in T, then exactly one simple cycle is created in T, traverse this cycle and delete the max-weight edge in this cycle, voila, the new updated MST is found!
My question is, how do I only traverse nodes on this simple-cycle? Since DFS/BFS traversals might go out of the cycle if I, say, start the traversal in T from one endpoint of this newly added edge in T.
One solution I could think of was to find the biconnected components in T after adding the new edge. Only one BCC will be found, which is this newly formed simple-cycle, then I can put in a special condition in my DFS code saying to only traverse edges/nodes in this BCC, and once a back-edge is found, stop the traversal.
Edit: graph G is connected and undirected btw
Your solution is basically good. To make it more formal you can use Tarjan's bridge-finding algorithm
This algorithm find the cut-edges (aka bridges) in the graph in linear time. Consider E' to be the cut-edges set. It is easy to prove that every edge in E' can not be on circle. So, E / E' are must be the cycle in the graph.
You can use hash-map or array build function to find the difference between your E and the cut-edges set
From here you can run simple for-loop to find the max weight edge which you want to remove.
Hope that help!
This is a question from Algorithm Design by Steven Skiena (for interview prep):
An articulation vertex of a graph G is a vertex whose deletion disconnects G. Let G be a graph with n vertices and m edges. Give a simple O(n + m) that finds a deletion order for the n vertices such that no deletion disconnects the graph.
This is what I thought:
Run DFS on the graph and keep updating each node's oldest reachable ancestor (based on which we decide if it's a bridge cut node, parent cute node or root cut node)
If we find a leaf node(vertex) or a node which is not an articulation vertex delete it.
At the end of DFS, we'd be left with all those nodes in graph which were found to be articulation vertices
The graph will remain connected as the articulation vertices are intact. I've tried it on a couple of graphs and it seems to work but it feels too simple for the book.
in 2 steps:
make the graph DAG using any traversal algorithm
do topology sort
each step finishes without going beyond O(m+n)
Assuming the graph is connected, then any random node reaches a subgraph whose spanning tree may be deleted in post-order without breaking the connectedness of the graph. Repeat in this manner until the graph is all gone.
Utilize DFS to track the exit time of each vertex;
Delete vertices in the order of recorded exit time;
If we always delete leaves of a tree one by one, rest of the tree remain connected. One particular way of doing this is to assign a pre-order number to each vertex as the graph is traversed using DFS or BFS. Sort the vertices in descending order (based on pre-order numbers). Remove vertices in that order from graph. Note that the leaves are always deleted first.
I know the answer to this particular question is O(V + E) and for a Graph like a tree, it makes sense because each Vertex is being explored once only.
However let's say there is a cycle in the graph.
For example, let's take up an undirected graph with four vertices A-B-C-D.
A is connected to both B and C, and Both B and C are connected to D. So there are four edges in total. A->B, A->C, B->D, C->D and vice versa.
Let's do DFS(A).
It will explore B first and B's neighbor D and D's neighbor C. After that C will not have any edges so it will come back to D and B and then A.
Then A will traverse its second edge and try to explore C and since it is already explored it will not do anything and DFS will end.
But over here Vertex "C" has been traversed twice, not once. Clearly worst case time complexity can be directly proportional to V.
Any ideas?
If you do not maintain a visited set, that you use to avoid revisitting already visited nodes, DFS is not O(V+E). In fact, it is not complete algorithm - thus it might not even find a path if there is a one, because it will be stuck in an infinite loop.
Note that for infinite graphs, if you are looking for a path from s to t, even with maintaining a visited set, it is not guaranteed to complete, since you might get stuck in an infinite branch.
If you are interested in keeping DFS's advantage of efficient space consumption, while still being complete - you might use iterative deepening DFS, but it will not trivially solve the problem if you are looking to discover the whole graph, and not a path to a specific node.
EDIT: DFS pseudo code with visited set.
DFS(v,visited):
for each u such that (v,u) is an edge:
if (u is not in visited):
visited.add(u)
DFS(u,visited)
It is easy to see that you invoke the recursion on a vertex if and only if it is not yet visited, thus the answer is indeed linear in the number of vertices and edges.
You can visit each vertex and edge of the graph a constant number of times and still be O(V+E). An alternative way of looking at it is that the cost is charged to the edge, not to the vertex.
Here are two excises about safe vertex deletions
5-28. An articulation vertex of a graph G is a vertex whose deletion disconnects G. Let G be a graph with n vertices and m edges. Give a simple O(n + m) algorithm for finding a vertex of G that is not an articulation vertex—i.e. , whose deletion does not disconnect G.
5-29. Following up on the previous problem, give an O(n + m) algorithm that finds a deletion order for the n vertices such that no deletion disconnects the graph. (Hint: think DFS/BFS.)
For 5-28, here is my thought:
I will just do a dfs, but not complete. The very first vertex which finished being processed will be a non-articulation vertex as it must be a leaf, or a leaf with a back edge pointing back to its ancestor (it is also not a articulation vertex).
For 5-29
I am not yet sure how to do it nicely. What comes into my mind is that in the graph, any vertex in a cycle can't deleted safely. Also, if there is no cycle, then deleting vertex backwards up from a dfs tree is also safe.
Could anyone give me some hints or tell me whether my thinking is correct or wrong?
I think your solution to 5-28 is correct, it guarantees to find an node which is not articulation in O(n+m) time.
For 5-29, I think one way to do it is based on your solution to 5-28. While doing dfs, keep tract of when did each node leaves the stack(the time finished being processed). As you said, the one leaves first must be a leaf node so delete it will not disconnect the graph. Then you can delete the node leaves the stack secondly, it must also be a leaf node when we removed the first node. So we can delete the nodes in the reverse order of when they are popped from stack while doing DFS. Doing this only need one pass of DFS, thus running time is O(n+m).
Another simple way is to do it with BFS. For 5.28, deleting any node with maximum depth will not make the graph disconnect. Because each other nodes can be reached by a node with less depth. So for 5.29, we can delete all nodes by their sort depth in descending order. And also, we only need 1 BFS so the running time is O(n+m). I think it's easier for people to understand this approach.
5-29:
Expanding on your idea from 5-28, when you finish processing a vertex, it is a non-articulation vertex, so delete it. Then continue the DFS, and every time you finish processing another vertex, delete it too. Since you deleted the previous vertices that finished processing, every time you finish processing a vertex it is actually the first time you finish processing a vertex (for the graph without the previously deleted ones).
Another method, easier to prove, and a bit less efficient (but still O(V + E)) - Crete a DFS tree from the graph, then do topological sorting, then remove the vertices one by one starting from the last one in the sorted graph and moving back to the first one. At every step you remove the last one, and you know for sure (because it's a topological sorted graph) that it doesn't point to any other node, meaning no edges will be deleted except the edges leading to it. That means all other nodes are still reachable from the first node, and if the graph were bi-directional, then all nodes can reach the first node too, making it connected.
For the first problem, I will just delete the vertex you want to test from the graph and then run a DFS/BFS starting from any other vertex, counting the number of visited vertices. If it's less than (original size - 1), then the tested vertex is an articulation vertex.
Same idea applies to the second problem. You randomly pick a vertex and delete it, which will in general cut the graph into two blocks. If the deleted vertex is not an articulation vertex, then one of the two blocks must be empty. Otherwise, both blocks have some vertices, in which case, all vertices in BOTH blocks have to be listed in front of this vertex in the final "safe-deletion" order, while it is not important to decide which block to be completely removed first. So we can write a little recursive function like this:
vertex[] safe_order_cut (vertex[] v)
if (v.length==0) return empty_vertex_list;
vertex x = randomly_pick(v);
vertex v1[], v2[];
cut_graph(v,x,v1,v2);
return safe_order_cut(v1) + safe_order_cut(v2) + x;
The connectivity problem (and related cut vertex problems) has been extensively studied in graph theory. If you are interested, you can read the wiki pages for more algorithms.
I have a directed cyclic graph. Some edges are FIXED and may not be removed. The other edges may be removed to break a cycle.
What is the best way to do remove the cycles in this graph?
The traversal should be as much as a DFS as possible and start at a given node.
What you can do is use Dijkstra's algorithm: start with a graph containing only the FIXED edges. Then apply an adaptation of the algorithm starting with the graph you already have:
Start with the starting node, and all FIXED edges in the component of the starting node. Assume this is a tree.
Add the node closest to the tree.
Also add any FIXED edges in the component of the node you just added.
If all nodes are in the tree, end. Otherwise, go to step 4.
This, of course, assumes that the graph consisting only of the FIXED edges does not contain cycles. If it does, there is no solution (that is, a subgraph without edges, but containing all FIXED edges).
For directed graphs, it's a bit more complicated. In this case, any component of the graph of FIXED edges should be a tree. In the Dijkstra-like algorithm, only roots of these nodes should be candidates to be added to the tree.
the problem is understated because you haven't specified e.g. if the graph needs to remain connected, or if you want to remove a "small" number of non-FIXED edges to break all cycles, or if you really need the globally minimum number of non-FIXED edges to be removed.
If the graph does not need to remain connected, just traverse all the edges and remove all non-FIXED ones. That removes all cycles which you can remove without removing FIXED edges.
If you want a simple greedy algorithm to remove edges that is pure DFS, you can use something like this IF the graph remains connected also when you remove some of the non-FIXED edges:
proc recurse(vertex n, vertex_set ns)
if (n appers_in ns) // it is a cycle
return BREAK_CYCLE
else for (e in list_outgoing_edges_from(n))
np = e.destination
result = recurse(np, add_to_set(ns, np))
if (result == BREAK_CYCLE)
if (e.FIXED)
return BREAK_CYCLE
else
[remove e from the graph]
return RETRY
else if (result == RETRY)
return recurse(n, ns)
return FINISHED
if (recurse (your_initial_node, empty_vertex_set()))
[graph contains a cycle through only FIXED edges]
else
[the reachable component from initial_node has no longer cycles]
I used the following algorithm to solve my problem:
Start with a graph of all fixed edges marked as confirmed
From a start node, recurse through all confirmed edges as well as the not-yet-confirmed ones. But when you're about to recurse down a not-yet-confirmed edge first check if the node that the edge goes to has a path, by following confirmed edges, to a node in the current search tree (i.e. a node with a visiting flag set). This check must be done recursively by following all confirmed edges but this is too slow for me so I will settle here to just check if the node is visiting or if any of the nodes it is confirmed connected to are visiting. This will cover most of my cases but occationally leave cycles in the graph.
After the above step I use Tarjan's algorithm for finding the strongly connected components left in the graph (this can be done in O(V + E) time). The number of strongly connected components will be very small in my cases so I just go through each strongly connected component and remove one removable edge each. Then I do this step again until no more cycles remain in the graph.
This works fine and is fast enough.