从无向图中移除二度顶点(Skiena TADM 5-22)

时间:2017-02-19 05:29:00

标签: algorithm graph-algorithm graph-theory

从算法设计手册第2版,问题5-22:

  

设计线性时间算法,通过用边(u,w)替换边(u,v)和(v,w),从图中消除2阶的每个顶点v。我们还试图通过用单个边缘替换它们来消除多个边缘副本。请注意,删除边的多个副本可能会创建一个2级的新顶点,必须将其删除,并且删除2级顶点可能会创建多个边,也必须将其删除。

因为问题出现在无向图的部分中, 假设我们的图表是无向的。

这是一种根据需要删除二阶顶点的算法,类似于给定here的顶点。实现依赖于Skiena的图形,队列和edgenode结构。 g-> edges [v]是指向v的邻接列表的头部的指针。 g-> M [u] [v]返回g的邻接矩阵的行u和列v中的布尔值。

问题:根据我的分析,无论我们是否使用邻接列表或邻接矩阵来表示我们的图形,它在线性时间内都不起作用。

process_vertex(graph *g, int v, queue Q) {
    int u,w;
    if (degree[v] != 2) {return;}

    u = pop_first_edge(g,v); // O(n) for AL, O(n) for AM
    w = pop_first_edge(g,v); // O(n) for AL, O(n) for AM
    if (!edge_exists(g,u,w)) { // O(n) for AL, O(1) for AM
        insert_edge(g,u,w);
    }

    if (degree[u] == 2) {enqueue(Q,u);}
    if (degree[w] == 2) {enqueue(Q,w);}
}

remove_degree_twos(graph *g) {
    queue Q;
    for (int v = 1; v <= g->nvertices; ++v) {
        if (degree[v] == 2) {enqueue(Q,v);}
    }
    while (!Q.empty()) {
        process_vertex(g,dequeue(Q),Q);
    }
}

有两个尚未实现的必需功能:

// removes the first edge in v's adjacency list
// and updates degrees appropriately
// returns the vertex to which that edge points
int pop_first_edge(g,v);

// determines whether edge (u,v) already exists
// in graph g
bool edge_exists(g,u,v);

如果用邻接列表表示g,则可以按如下方式实现所需的函数:

// O(n)
int pop_first_edge(g,v) {
    int u = -1; // the vertex to which the first outedge from v points
    edgenode *p = g->edges[v];

    if (p != NULL) {
        u = p->y;
        g->edges[v] = p->next;
        --(g->degree[v]);

        // delete v from u's adjacency list
        p1 = &g->edges[u];
        p2 = g->edges[u];
        while (p2 != NULL) {
            if (p2->y == v) {
                *p1 = p2->next;
                --(g->degree[u]);
                break;
            }
            p1 = p2;
            p2 = p2->next;
        }
    }
}

// O(n)
edge_exists(g,u,w) {
    edgenode *p = g->edges[u];

    while (p != NULL) {
        if (p->y == w) {
            return true;
        }
        p = p->next;
    }

    return false;
}

如果g用邻接矩阵表示,那么我们有:

// O(n)
int pop_first_edge(v) {
    int u = -1;

    for (int j = 1; j <= g->nvertices; ++j) {
        if (M[v][j]) {
            u = j;
            break;
        }
    }

    if (u > 0) {
        M[v][u] = false;
        M[u][v] = false;
        --(g->degree[v]);
        --(g->degree[u]);
        return u;
    } else {
        return -1;
    }
}

// O(1)
edge_exists(g,u,w) {
    return g->M[u][w];
}

无论我们使用邻接列表还是邻接矩阵,process_vertex的运行时都是O(n),其中n是图中顶点的数量。因为可以处理O(n)个顶点,所以总运行时间是O(n ^ 2)。

如何在线性时间内完成?

1 个答案:

答案 0 :(得分:0)

假设我们有图G =(V,E),其中

V={1,...,n} 

是顶点集,E是边集,因此是集合

的子集
{(x,y) : x,y in V}

通常,图表由边列表给出。我们假设我们以这种方式接收它。现在:

  1. 使边集不同(O(m),m = | E |),考虑边(x,y)和(y,x)相等
  2. 创建一个数组,表示G中每个顶点的程度(再次为O(m))
  3. 对于2度的每个顶点 v ,制作这2条边的列表(这是O(m),边缘上的一次迭代就足够了)
  4. 最后迭代2度的顶点并用一条边取代相关的边缘去掉2度的顶点(这是O(n)操作)
  5. 重复步骤1.并返回边缘
  6. 这是用python编写的代码:

    def remove_2_degree_vertices(n, edges):
    
        adj_matrix = [[0]*n for i in xrange(n)]
    
        #1 O(m)
        edges = get_distinct(adj_matrix, edges)
    
        #2 O(m)
        degrees = calculate_degrees(n, edges)
    
        #3 O(m)
        adj_lists = get_neighbours(degrees, edges)
    
        #4 O(n + m)
        to_remove, to_add_candidates = process(n, adj_lists)    
        edges.extend(to_add_candidates)
        result = [(e0,e1) for e0, e1 in edges if to_remove[e0][e1] == 0]
    
        #5 O(m)
        adj_matrix = [[0]*n for i in xrange(n)]
        result = get_distinct(adj_matrix, result)
    
        return result
    
    def get_distinct(adj_matrix, edges):
        result = []
        for e0, e1 in edges:
            if adj_matrix[e0][e1] == 0:
                result.append((e0,e1))
                adj_matrix[e0][e1] = adj_matrix[e1][e0] = 1
        return result
    
    
    def calculate_degrees(n, edges):
        result = [0]*n
        for e0, e1 in edges:
            result[e0] += 1
            result[e1] += 1
        return result
    
    
    def get_neighbours(degrees, edges):
        result = {}
        for e0, e1 in edges:
            if degrees[e0] == 2:
                add_neighbour(result, e0, e1)
            if degrees[e1] == 2:
                add_neighbour(result, e1, e0)
        return result
    
    
    def add_neighbour(neighbours, key, value):
        if not neighbours.has_key(key):
            neighbours[key] = set()
            neighbours[key].add(value)
        else:
            neighbours[key].add(value)
    
    
    def process(n, adj_lists):
        to_remove = [[0 for i in xrange(n)] for j in xrange(n)]
        to_add_candidates = []
    
        if len(adj_lists) == 0:
            return to_remove, to_add_candidates
    
        for key in adj_lists:
            neighbours = list(adj_lists[key])
            if len(neighbours) == 1:
                to_remove[key][neighbours[0]] = to_remove[neighbours[0]][key] = 1
            else: # len(neighbours) == 2
                remove_edge(adj_lists, to_remove, key, neighbours[0], neighbours[1])
                remove_edge(adj_lists, to_remove, key, neighbours[1], neighbours[0])
                to_add_candidates.append((neighbours[0], neighbours[1]))
    
        return to_remove, to_add_candidates
    
    
    def remove_edge(adj_lists, to_remove, key, n0, n1):
        to_remove[key][n0] = to_remove[n0][key] = 1
        if n0 in adj_lists:
            adj_lists[n0].remove(key)
            adj_lists[n0].add(n1)