📌  相关文章
📜  要从无向图上删除的最小标记节点,这样就不会出现循环

📅  最后修改于: 2021-04-21 20:43:16             🧑  作者: Mango

给定从1到N标记的N个节点的无向图,任务是找到应从图中删除的最小标记的节点,以使生成的图不具有周期。

注意:如果初始图没有循环,即不需要删除任何节点,请打印-1。

例子:

幼稚的方法:针对此问题的幼稚的方法是分别删除每个顶点,并检查结果图是否具有循环。这种方法的时间复杂度是二次的。

高效方法:想法是在给定图上应用深度优先搜索,并观察形成的dfs树。

  • 后边缘称为不属于所构造DFS树的一部分的边缘,并且是某些节点v与v的祖先之一之间的边缘。
  • 显然,图的所有那些不属于DFS树的边缘都是后边缘。
  • 如果图中没有后沿,则图中没有循环。因此,在这种情况下,答案将为-1

如果图中有后边缘,那么我们需要找到最小边缘。为此,我们需要检查在从图形中删除特定边时是否删除了循环。因此,令v为我们当前正在检查的顶点。因此,顶点v必须遵循以下条件,以便在移除时不会导致循环:

  • v必须位于连接图中每个后边缘端点的树路径上。
    证明:假设存在一些后沿xy,使得v不在树路径上。如果删除v,我们仍然可以从x遍历到y,并通过后边缘返回x,这表明该循环未被删除。
  • v的子树必须具有至少一个v祖先的后边缘。
    证明:让子树S必须后退wx和yz的边缘,使得w和y在S中,而x和z是v的祖先。如果删除v,显然仍然存在一个循环,该循环由w到y之间的路径组成,从x到z的路径以及两个后边缘wx和yz(即循环)未删除。

因此,其思想是跟踪后边缘,并为节点的任何祖先节点的子树中的后边缘数量提供指示符。为了跟踪后边缘,我们将使用改良的DFS图形着色算法。
为了检查子树v是否具有至v的任何祖先的至少一个后边缘,我们实现dfs,使其返回v的子树的两个最高边缘的深度。我们维护一个数组,其中每个索引’如果节点“ i”满足上述条件2,则存储在数组中的“ i”。类似地,实现了两个数组,一个用于子级,另一个用于父级,以查看节点v是否位于连接端点的树路径上。

下面是上述方法的实现:

C++
// C++ implementation to find the
// minimum labelled node to be
// removed such that there is no
// cycle in the undirected graph
 
#include 
 
using namespace std;
 
const int MAX = 100005;
 
int totBackEdges;
int countAdj[MAX], small[MAX];
 
// Variables to store if a node V has
// at-most one back edge and store the
// depth of the node for the edge
int isPossible[MAX], depth[MAX];
 
vector adj[MAX];
int vis[MAX];
 
// Function to swap the pairs of the graph
void change(pair& p, int x)
{
    // If the second value is
    // greater than x
    if (p.second > x)
        p.second = x;
 
    // Put the pair in the ascending
    // order internally
    if (p.first > p.second)
        swap(p.first, p.second);
}
 
// Function to perform the DFS
pair dfs(int v, int p = -1, int de = 0)
{
    // Initialise with the large value
    pair answer(100000000, 100000000);
 
    // Storing the depth of this vertex
    depth[v] = de;
 
    // Mark the vertex as visited
    vis[v] = 1;
    isPossible[v] = 1;
 
    // Iterating through the graph
    for (int u : adj[v]) {
 
        // If the node is a child node
        if (u ^ p) {
 
            // If the child node is unvisited
            if (!vis[u]) {
 
                // Move to the child and increase
                // the depth
                auto x = dfs(u, v, de + 1);
 
                // increase according to algorithm
                small[v] += small[u];
 
                change(answer, x.second);
                change(answer, x.first);
 
                // If the node is not having
                // exactly K backedges
                if (x.second < de)
                    isPossible[v] = 0;
            }
 
            // If the child is already visited
            // and in current dfs
            // (because colour is 1)
            // then this is a back edge
            else if (vis[u] == 1) {
                totBackEdges++;
 
                // Increase the countAdj values
                countAdj[v]++;
                countAdj[u]++;
                small[p]++;
                small[u]--;
                change(answer, depth[u]);
            }
        }
    }
 
    // Colour this vertex 2 as
    // we are exiting out of
    // dfs for this node
    vis[v] = 2;
    return answer;
}
 
// Function to find the minimum labelled
// node to be removed such that
// there is no cycle in the undirected graph
int minNodetoRemove(
    int n,
    vector > edges)
{
 
    // Construct the graph
    for (int i = 0; i < edges.size(); i++) {
        adj[edges[i].first]
            .push_back(edges[i].second);
        adj[edges[i].second]
            .push_back(edges[i].first);
    }
 
    // Mark visited as false for each node
    memset(vis, 0, sizeof(vis));
 
    totBackEdges = 0;
 
    // Apply dfs on all unmarked nodes
    for (int v = 1; v <= n; v++) {
        if (!vis[v])
            dfs(v);
    }
 
    // If no backedges in the initial graph
    // this means that there is no cycle
    // So, return -1
    if (totBackEdges == 0)
        return -1;
 
    int node = -1;
 
    // Iterate through the vertices and
    // return the first node that
    // satisfies the condition
    for (int v = 1; v <= n; v++) {
 
        // Check whether the count sum of
        // small[v] and count is the same as
        // the total back edges and
        // if the vertex v can be removed
        if (countAdj[v] + small[v]
                == totBackEdges
            && isPossible[v]) {
            node = v;
        }
        if (node != -1)
            break;
    }
 
    return node;
}
 
// Driver code
int main()
{
    int N = 5;
    vector > edges;
    edges.push_back(make_pair(5, 1));
    edges.push_back(make_pair(5, 2));
    edges.push_back(make_pair(1, 2));
    edges.push_back(make_pair(2, 3));
    edges.push_back(make_pair(2, 4));
 
    cout << minNodetoRemove(N, edges);
}


Java
// Java implementation to find the
// minimum labelled node to be
// removed such that there is no
// cycle in the undirected graph
import java.util.ArrayList;
import java.util.Arrays;
 
class Pair
{
    int first, second;
 
    public Pair(int first, int second)
    {
        this.first = first;
        this.second = second;
    }
}
 
class GFG{
 
static final int MAX = 100005;
 
static int totBackEdges;
static int[] countAdj = new int[MAX];
static int[] small = new int[MAX];
 
// Variables to store if a node V has
// at-most one back edge and store the
// depth of the node for the edge
static int[] isPossible = new int[MAX];
static int[] depth = new int[MAX];
 
@SuppressWarnings("unchecked")
static ArrayList[] adj = new ArrayList[MAX];
 
static int[] vis = new int[MAX];
 
// Function to swap the pairs of the graph
static void change(Pair p, int x)
{
     
    // If the second value is
    // greater than x
    if (p.second > x)
        p.second = x;
 
    // Put the Pair in the ascending
    // order internally
    if (p.first > p.second)
    {
        int tmp = p.first;
        p.first = p.second;
        p.second = tmp;
    }
}
 
// Function to perform the DFS
static Pair dfs(int v, int p, int de)
{
     
    // Initialise with the large value
    Pair answer = new Pair(100000000, 100000000);
 
    // Storing the depth of this vertex
    depth[v] = de;
 
    // Mark the vertex as visited
    vis[v] = 1;
    isPossible[v] = 1;
 
    // Iterating through the graph
    for(int u : adj[v])
    {
         
        // If the node is a child node
        if ((u ^ p) != 0)
        {
             
            // If the child node is unvisited
            if (vis[u] == 0)
            {
                 
                // Move to the child and increase
                // the depth
                Pair x = dfs(u, v, de + 1);
 
                // increase according to algorithm
                small[v] += small[u];
 
                change(answer, x.second);
                change(answer, x.first);
 
                // If the node is not having
                // exactly K backedges
                if (x.second < de)
                    isPossible[v] = 0;
            }
 
            // If the child is already visited
            // and in current dfs
            // (because colour is 1)
            // then this is a back edge
            else if (vis[u] == 1)
            {
                totBackEdges++;
 
                // Increase the countAdj values
                countAdj[v]++;
                countAdj[u]++;
                small[p]++;
                small[u]--;
                change(answer, depth[u]);
            }
        }
    }
 
    // Colour this vertex 2 as
    // we are exiting out of
    // dfs for this node
    vis[v] = 2;
    return answer;
}
 
// Function to find the minimum labelled
// node to be removed such that
// there is no cycle in the undirected graph
static int minNodetoRemove(int n, ArrayList edges)
{
     
    // Construct the graph
    for(int i = 0; i < edges.size(); i++)
    {
        adj[edges.get(i).first].add(
            edges.get(i).second);
        adj[edges.get(i).second].add(
            edges.get(i).first);
    }
 
    // Mark visited as false for each node
    Arrays.fill(vis, 0);
 
    totBackEdges = 0;
 
    // Apply dfs on all unmarked nodes
    for(int v = 1; v <= n; v++)
    {
        if (vis[v] == 0)
            dfs(v, -1, 0);
    }
 
    // If no backedges in the initial graph
    // this means that there is no cycle
    // So, return -1
    if (totBackEdges == 0)
        return -1;
 
    int node = -1;
 
    // Iterate through the vertices and
    // return the first node that
    // satisfies the condition
    for(int v = 1; v <= n; v++)
    {
         
        // Check whether the count sum of
        // small[v] and count is the same as
        // the total back edges and
        // if the vertex v can be removed
        if ((countAdj[v] + small[v] == totBackEdges) &&
           isPossible[v] != 0)
        {
            node = v;
        }
         
        if (node != -1)
            break;
    }
    return node;
}
 
// Driver code
public static void main(String[] args)
{
    int N = 5;
     
    ArrayList edges = new ArrayList<>();
    for(int i = 0; i < MAX; i++)
    {
        adj[i] = new ArrayList<>();
    }
     
    edges.add(new Pair(5, 1));
    edges.add(new Pair(5, 2));
    edges.add(new Pair(1, 2));
    edges.add(new Pair(2, 3));
    edges.add(new Pair(2, 4));
 
    System.out.println(minNodetoRemove(N, edges));
}
}
 
// This code is contributed by sanjeev2552


Python3
# Python3 implementation to find the
# minimum labelled node to be
# removed such that there is no
# cycle in the undirected graph 
MAX = 100005; 
totBackEdges = 0
countAdj = [0 for i in range(MAX)]
small = [0 for i in range(MAX)]
   
# Variables to store if a node V has
# at-most one back edge and store the
# depth of the node for the edge
isPossible = [0 for i in range(MAX)]
depth = [0 for i in range(MAX)]
adj = [[] for i in range(MAX)]
vis = [0 for i in range(MAX)]
 
# Function to swap the pairs of the graph
def change(p, x):
 
    # If the second value is
    # greater than x
    if (p[1] > x):
        p[1] = x;
   
    # Put the pair in the ascending
    # order internally
    if (p[0] > p[1]):
     
       tmp = p[0];
       p[0] = p[1];
       p[1] = tmp;
      
# Function to perform the DFS
def dfs(v, p = -1, de = 0):
    global vis, totBackEdges
     
    # Initialise with the large value
    answer = [100000000, 100000000]
   
    # Storing the depth of this vertex
    depth[v] = de;
   
    # Mark the vertex as visited
    vis[v] = 1;
    isPossible[v] = 1;
   
    # Iterating through the graph
    for u in adj[v]:
   
        # If the node is a child node
        if ((u ^ p) != 0):
   
            # If the child node is unvisited
            if (vis[u] == 0):
   
                # Move to the child and increase
                # the depth
                x = dfs(u, v, de + 1);
   
                # increase according to algorithm
                small[v] += small[u];
   
                change(answer, x[1]);
                change(answer, x[0]);
   
                # If the node is not having
                # exactly K backedges
                if (x[1] < de):
                    isPossible[v] = 0;
   
            # If the child is already visited
            # and in current dfs
            # (because colour is 1)
            # then this is a back edge
            elif (vis[u] == 1):
                totBackEdges += 1
   
                # Increase the countAdj values
                countAdj[v] += 1
                countAdj[u] += 1
                small[p] += 1
                small[u] -= 1
                change(answer, depth[u]);
               
    # Colour this vertex 2 as
    # we are exiting out of
    # dfs for this node
    vis[v] = 2;
    return answer;
   
# Function to find the minimum labelled
# node to be removed such that
# there is no cycle in the undirected graph
def minNodetoRemove( n, edges):
   
    # Construct the graph
    for i in range(len(edges)):
     
        adj[edges[i][0]].append(edges[i][1]);
        adj[edges[i][1]].append(edges[i][0]);
         
    global vis, totBackEdges
     
    # Mark visited as false for each node
    vis = [0 for i in range(len(vis))] 
    totBackEdges = 0;
   
    # Apply dfs on all unmarked nodes
    for v in range(1, n + 1):  
        if (vis[v] == 0):
            dfs(v);
      
    # If no backedges in the initial graph
    # this means that there is no cycle
    # So, return -1
    if (totBackEdges == 0):
        return -1; 
    node = -1;
   
    # Iterate through the vertices and
    # return the first node that
    # satisfies the condition
    for v in range(1, n + 1):
   
        # Check whether the count sum of
        # small[v] and count is the same as
        # the total back edges and
        # if the vertex v can be removed
        if ((countAdj[v] + small[v] == totBackEdges) and isPossible[v] != 0):
            node = v;      
        if (node != -1):
            break; 
    return node;
   
# Driver code
if __name__=='__main__':   
    N = 5;
    edges = []
    edges.append([5, 1]);
    edges.append([5, 2]);
    edges.append([1, 2]);
    edges.append([2, 3]);
    edges.append([2, 4]); 
    print(minNodetoRemove(N, edges));
 
    # This code is contributed by Pratham76


C#
// C# implementation to find the
// minimum labelled node to be
// removed such that there is no
// cycle in the undirected graph
  
using System;
using System.Collections;
using System.Collections.Generic;
 
class GFG
{
  
static int MAX = 100005;
  
static int totBackEdges;
static int []countAdj = new int[MAX];
static int []small = new int[MAX];
  
// Variables to store if a node V has
// at-most one back edge and store the
// depth of the node for the edge
static int []isPossible = new int[MAX];
static int []depth = new int[MAX];
 
static ArrayList adj = new ArrayList();
 
static int []vis = new int[MAX];
 
class pair
{
    public int first, second;
    public pair(int first, int second)
    {
        this.first = first;
        this.second = second;
    }
}
  
// Function to swap the pairs of the graph
static void change(ref pair p, int x)
{
    // If the second value is
    // greater than x
    if (p.second > x)
        p.second = x;
  
    // Put the pair in the ascending
    // order internally
    if (p.first > p.second)
    { 
       int tmp = p.first;
       p.first = p.second;
       p.second = tmp;
    }
}
  
// Function to perform the DFS
static pair dfs(int v, int p = -1, int de = 0)
{
    // Initialise with the large value
    pair answer = new pair(100000000, 100000000);
  
    // Storing the depth of this vertex
    depth[v] = de;
  
    // Mark the vertex as visited
    vis[v] = 1;
    isPossible[v] = 1;
  
    // Iterating through the graph
    foreach (int u in (ArrayList)adj[v]) {
  
        // If the node is a child node
        if ((u ^ p) != 0) {
  
            // If the child node is unvisited
            if (vis[u] == 0) {
  
                // Move to the child and increase
                // the depth
                pair x = dfs(u, v, de + 1);
  
                // increase according to algorithm
                small[v] += small[u];
  
                change(ref answer, x.second);
                change(ref answer, x.first);
  
                // If the node is not having
                // exactly K backedges
                if (x.second < de)
                    isPossible[v] = 0;
            }
  
            // If the child is already visited
            // and in current dfs
            // (because colour is 1)
            // then this is a back edge
            else if (vis[u] == 1) {
                totBackEdges++;
  
                // Increase the countAdj values
                countAdj[v]++;
                countAdj[u]++;
                small[p]++;
                small[u]--;
                change(ref answer, depth[u]);
            }
        }
    }
  
    // Colour this vertex 2 as
    // we are exiting out of
    // dfs for this node
    vis[v] = 2;
    return answer;
}
  
// Function to find the minimum labelled
// node to be removed such that
// there is no cycle in the undirected graph
static int minNodetoRemove(
    int n,
    ArrayList edges)
{
  
    // Construct the graph
    for (int i = 0; i < edges.Count; i++) {
        ((ArrayList)adj[((pair)edges[i]).first])
            .Add(((pair)edges[i]).second);
        ((ArrayList)adj[((pair)edges[i]).second])
            .Add(((pair)edges[i]).first);
    }
  
    // Mark visited as false for each node
    Array.Fill(vis, 0);
  
    totBackEdges = 0;
  
    // Apply dfs on all unmarked nodes
    for (int v = 1; v <= n; v++) {
        if (vis[v] == 0)
            dfs(v);
    }
  
    // If no backedges in the initial graph
    // this means that there is no cycle
    // So, return -1
    if (totBackEdges == 0)
        return -1;
  
    int node = -1;
  
    // Iterate through the vertices and
    // return the first node that
    // satisfies the condition
    for (int v = 1; v <= n; v++) {
  
        // Check whether the count sum of
        // small[v] and count is the same as
        // the total back edges and
        // if the vertex v can be removed
        if ((countAdj[v] + small[v] == totBackEdges) && isPossible[v] != 0) {
            node = v;
        }
        if (node != -1)
            break;
    }
  
    return node;
}
  
// Driver code
static void Main()
{
    int N = 5;
    ArrayList edges = new ArrayList();
    for(int i = 0; i < MAX; i++)
    {
        adj.Add(new ArrayList());
    }
    edges.Add(new pair(5, 1));
    edges.Add(new pair(5, 2));
    edges.Add(new pair(1, 2));
    edges.Add(new pair(2, 3));
    edges.Add(new pair(2, 4));
  
    Console.Write(minNodetoRemove(N, edges));
  }
}
 
// This code is contributed by rutvik_56


输出:
1

时间复杂度: O(N + M) ,其中N是节点数,M是边数。