问题描述
给定一个连通的图,确定一个删除节点的顺序,使得每次删除节点之后图的剩余部分依然是连通的。要求算法的时间复杂度是O(V + E)。
分析
这个问题粗看起来比较复杂。因为对于一个图来说,假设我们要删除一个节点,那么它所对应的边都要被删掉。光去遍历所有的节点找到所有包含某个要删除节点的边都要费很大的劲。所以不能单纯的用一个个查找然后删除的方法。
我们来看这个问题的要求,既然是希望删除了某个节点之后保证图还是连通的,我们在当前要删除的节点就可能是图中间某个独立的边上的节点,或者是一个环上面的节点。因为删除了这个节点之后,并不会破坏图的连通性。但是这个节点不能在图的bridge上。因为图的桥边表示一旦它被删除就将使得图被划分成两个独立的部分。只是说,在图里面去专门查找和判断这些边或者点的过程还是很复杂。似乎也没有什么具体可行的办法。
看来上述的两个思路都不可行,尤其是在要求时间复杂度为O(V+E)的情况下。这里,可能需要一点联想的应用了。在一些问题的场景里,我们经常看到一些问题的依赖关系图。比如说,我们要做事情a,那么就需要首先完成事情b,而同时要完成事情b,又需要若干其他的事情完成...这样就构成了一个依赖关系图。一个典型的依赖图如下:
在上述图中,我们可以看到,虽然这种依赖关系构成了一个有向图,但是有这么一个有趣的特性。每个元素和它所依赖的元素构成了一个边。而对于最终那个不依赖于任何元素的节点,它就是那个我们可以删除的节点。为什么呢?因为它之前的所有边都是由一个节点扩展来的,那么它把它删除之后,它之前的所有节点依然是连通的。嗯,在这一步我们似乎找到了一个突破口。
可是问题的要求是找到一个删除所有元素的顺序。那么顺着前面的推导来看,当我们删除了这个节点之后。和它直接相连的那些依赖节点里也可能出现一个和最终节点一样的状态,它没有别的依赖了。也就是说,它也可以符合我们之前可以删除的节点的条件了。只是这次符合这个条件的节点还不止一个。想上图中,如果我们删除了节点f之后,节点g1, g2都可以删除。这样类推,我们就可以得到一个符合问题描述里条件的元素删除序列了。
在这里,我们得到的办法是基于一个依赖关系图得到的。对于一个普通的连通无向图来说,有没有可以同样运用的地方呢?对于依赖关系图来说,它可以说是一个有向图,节点和节点之间有依赖关系才构成这个图。而对于无向图来说,节点和一些节点之间有一个连通关系。如果我们把这些连通关系也看成是一个依赖关系的话,这样不也就构成了一个类似的关系了吗?对于某个给定的节点来说,它有若干个和它直接连通的节点。如果我们让它按照某种方式去遍历去访问,它就相当于一个类似的依赖关系构造。而对图的常用遍历方式有广度优先遍历和深度优先遍历。在任何一个遍历的过程中,最后被访问到的节点可以说是可以最先删除的。因为它之前的所有的节点都是连通的。
讨论到这里,我们就发现了原来这个问题的本质就是找到图的任何一种遍历的方式,但是将它访问的元素按照遍历访问的顺序相反的过程输出。而我们常用的两种遍历方式分别是BFS和DFS,因此我们就有两种问题的解决方法。
DFS
我们先看深度优先遍历的过程该怎么取得这个序列。我们首先取一个节点进行遍历,在每次访问一个节点的时候,将它标记为已访问,这样剩下的节点里继续递归的去访问直到某个节点它周边所有的节点都已经被访问过。那么从这个节点这里就要返回了。这个节点就是一个我们可以优先删除的对象。
public class DepthFirstPaths { private boolean[] marked; private int s; private LinkedList<Integer> accessPath; public DepthFirstPaths(Graph g, int s) { marked = new boolean[g.vertices()]; this.s = s; this.accessPath = new LinkedList<>(); dfs(g, s); } private void dfs(Graph g, int v) { marked[v] = true; accessPath.add(v); for(int w : g.adj()) if(!marked[w]) { edgeTo[w] = v; dfs(g, w); } } private void dfs(Graph g, int v) { LinkedList<Integer> stack = new LinkedList<>(); stack.push(v); marked[v] = true; accessPath.add(v); while(!stack.isEmpty()) { int u = stack.pop(); for(int w : g.adj(u)) { if(!marked[w]) { marked[w] = true; accessPath.add(w); stack.push(w); } } } } public List<Integer> accessSequence() { Collections.reverse(accessPath); return accessPath; } }
在上述代码实现里,我们采用了递归和非递归两种实现。基本的思路是每次我们访问某个节点的时候,需要将该节点对应的位置marked[v] 设置为true。这个时候我们将该元素加入到一个列表中。在访问结束后,将列表倒置一下就得到期望的结果。
BFS
广度优先遍历的思路类似,我们每次访问的时候需要注意设置元素访问标记,并将元素加入到列表中。详细实现代码如下:
public class BreadthFirstPaths { private boolean[] marked; private LinkedList<Integer> accessPath; private int s; public BreadthFirstPaths(Graph g, int s) { this.marked = new boolean[g.vertices()]; this.accessPath = new LinkedList<>(); this.s = s; bfs(g, s); } private void bfs(Graph g, int s) { Queue<Integer> queue = new LinkedList<>(); marked[s] = true; queue.add(s); accessPath.add(s); while(!queue.isEmpty()) { int v = queue.remove(); for(int w : g.adj(v)) if(!marked[w]) { accessPath.add(w); marked[w] = true; queue.add(w); } } } public List<Integer> accessSequence() { Collections.reverse(accessPath); return accessPath; } }
总结
深度优先或者广度优先遍历图的过程相对来说对于大家都很常见。它们其实就是一种对图遍历访问的一种策略。而不管用哪种策略,它从某个节点延伸出来所覆盖的节点是和之前的子图连通的。因此,如果我们按照遍历访问的顺序从后往前的保存元素,得到的就是一个符合问题描述中的序列了。这个序列和依赖关系图也有很密切的关系。
相关推荐
Tool for fast (compare to usage over interface) workorder deletion. Useful for fast database cleanup,
* ME1P:Purchase Order Price History - 采购订单价格历史记录 * ME1W:Info Records Per Material Group - 每物料组的信息记录 * ME1X:Buyer's Negotiation Sheet for Vendor - 用于供应商的买方采购谈判记录 * ...
- **Balanced Tree Deletion**: Strategies for preserving balance during deletion. **Optimal Search Trees**: Trees optimized for search operations, minimizing the average number of comparisons required...
- `order_id`:订单ID,varchar50,关联到用户的购买行为。 - `item_id`:商品ID,varchar50,用于标识被评价的具体商品。 - `star`:星级评价,tinyint类型,通常1-5星,表示用户对商品的满意度。 - `type`:...
* IW57 - Assign deletion Flag to Completed Service Notifications:将删除标志分配给已完成的服务通知 * IW58 - Change Service Notifications: Selection of Notification:修改服务通知:通知选择 * IW59 - ...
- BAPI_PRODORD_SET_DEL_INDICATOR / BAPI_PRODORD_SET_DELETION_FLAG:设置订单的删除标志。 - BAPI_PRODORD_SCHEDULE:调度或调整生产订单。 - BAPI_PRODORD_REVOKEUSERSTATUS:撤销生产订单的用户状态。 这些...
删除(Deletion)** 删除BST中的节点相对复杂,需要考虑多种情况。递归删除如下: ```python def delete_bst(node, target): if node is None: return node if target node.left = delete_bst(node.left, ...
- **删除(Deletion)**:移除树中的某个节点。 - **前序遍历(Pre-Order Traversal)**:根-左-右。 - **中序遍历(In-Order Traversal)**:左-根-右。 - **后序遍历(Post-Order Traversal)**:左-右-根。 - **...
Lazy deletion 479 Linear probing 479 Quadratic probing 480 Double hashing 484 Cuckoo hashing 486 Coalesced hashing 491 Perfect hash function 494 Universal hashing 496 Linear hashing 501 Extendible ...
Providing net global tool in order to manage acumatica website creation and deletion 在此仓库中,我正在构建一个全局工具,该工具可以创建或删除网站。 该工具基于多个子命令: 使用子命令CreateSite创建...
// code for deletion logic } ``` 4. **遍历二叉搜索树**:主要有三种遍历方式:前序遍历、中序遍历和后序遍历。 - **前序遍历**:先访问根节点,再遍历左子树,最后遍历右子树。 ```java void preOrder...
II Sorting and Order Statistics Introduction 123 6 Heapsort 127 6.l Heaps I27 6.2 Maintaining the heap property 130 6.3 Building a heap 132 6.4 The heapsort algorithm 135 6.5 Priority queues 138 7 ...
Getway Raid Recovery Software can help you recover data due to the following possible data loss situations: File Deletion; Accidental Array Format; MBR damage/ loss/ excursion; DBR damage; One or two ...
它的二叉搜索树的性质保证了按照中序遍历(inorder sequence)搜索键是有序的,而节点的优先级遵循堆的性质,即节点的优先级小于其子节点的优先级,且树中优先级最高的节点总是位于树根。优先级的值是随机生成的,这...
- **删除异常 (Deletion Anomaly)** - 删除异常发生在当删除一个仅出现过一次的员工的订单时,这会导致员工的信息也随之丢失。 #### 四、范式分析 **4. 范式分析** - **2NF (第二范式)** - 给定的关系处于第二...
1.3 **锁定/解锁供应商** (Block/Unblock vendor) 和 **标记为删除** (Flag for Deletion Vendor) 如果供应商不再使用或者出现问题,可以将其锁定,防止进一步的业务处理。如果决定永久移除供应商,可标记为删除,...
- **Van Emde Boas Trees:** A specialized data structure that efficiently supports certain types of queries, such as minimum and maximum, and updates such as insertion and deletion. - **Multithreaded ...
This makes arrays ideal for scenarios where the order of elements matters and efficient indexing is required. ##### Array Fundamentals Arrays can be mutable or immutable. Immutable arrays do not ...