从算法设计手册第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)。
如何在线性时间内完成?
答案 0 :(得分:0)
假设我们有图G =(V,E),其中
V={1,...,n}
是顶点集,E是边集,因此是集合
的子集{(x,y) : x,y in V}
通常,图表由边列表给出。我们假设我们以这种方式接收它。现在:
这是用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)