一个图中有10个顶点,建立一个图,使得图中任意4个点离开网络该图仍然是连通图
问题描述
问题描述:一个图中有10个顶点,建立一个图,使得图中任意4个点离开网络该图仍然是连通图
解决思路
不妨假设图中存在某个结点 X 仅有4个结点与之相连,那么只要这四个点离开,点X就会成为孤立点,图的连通性必然被破坏。因此,我们得出结论,每个点与其他5个点直接相连是这一问题的必要条件,但是未必充分。事实上,读者可以尝试列举反例,以发现这一条件是不充分的。
为此,决定以“每个点与其他5个点相连”为起点进行求解这样的图结构,这样可以保证图中边的数量尽可能少。我们使用邻接矩阵对该图进行描述。
初始化
初始情况下,我们们的邻接矩阵是这样的,保证图中有且仅有25条边。当然,其实不一定非得是下图中的样子,只要保证矩阵是包含50个1的对称矩阵即可。
[0 1 1 1 1 1 1 1 0 0]
[1 0 1 1 1 1 1 1 0 0]
[1 1 0 1 1 1 1 1 0 0]
[1 1 1 0 1 1 1 1 0 0]
[1 1 1 1 0 1 1 0 0 0]
[1 1 1 1 1 0 1 0 0 0]
[1 1 1 1 1 1 0 0 0 0]
[1 1 1 1 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0]
矩阵处理
对矩阵进行处理的目的是使得矩阵中每行每列均包含5个1,即图中每个结点有且仅有5个其他点与之直接相连。这一变化的详细思路可以参见下面代码中的 move_edge 方法。
如何保证去掉任意4个结点后图是连通的
在经过上一个过程后,每个结点都满足了与另外5个结点相连的条件。
我们用 [A] 表示与结点A直接相连的结点。
我们可以考虑这样的情形:
- 如果存在结点 A 和 B, 如果 [A] ∩ [B] 包括4个结点,且[A]∪[B] - [A] ∩ [B] = {A, B}, 那么这样一个图一定是不满足条件的。
- 如果存在结点 A B C, 如果 [A] ∩ [B] ∩ [C] 包括3个或4个结点,且[A]∪[B]∪[C] - [A] ∩ [B] ∩ [C] = {A, B, C}, 那么这个图一定是不满足条件的。
事实上,这是10个点去掉4个点的情况下存在的情形。如果点的总数增加,问题会变得更复杂。
下面是完整的 python 算法。
其运行结果:
源程序
#!/usr/bin/python
# -*- coding: utf-8 -*-
import copy, random
import numpy as np
from colorama import init, Fore, Back, Style
'''
Problem:
一个图中有10个顶点,建立一个图,使得图中任意4个点离开网络该图仍然是连通图
'''
class AdjacencyMatrix(object):
'''Node number is 10 and the matrix is 10 * 10'''
node_num = 10
matrix = np.uint8(np.zeros((node_num, node_num)))
def __init__(self):
'''initial the matrix with 0 and 1, while 1 means connection and 0 means no connection'''
init(autoreset=True)
edge_num = self.node_num * 5 / 2
for i in range(0, self.node_num):
for j in range(0, i):
if i*(i-1)/2+j+1 <= edge_num:
self.matrix[i][j] = self.matrix[j][i] = 1
print self.matrix
def node_connected(self, node):
'''get all nodes collected to the given node'''
node_set = set([])
try:
for i in range(0, self.node_num):
if self.is_node_connect(node, i):
node_set.add(i)
except:
self.printError('fail in node_connected')
return node_set
def over_connected(self):
'''if a node has more than 5 edge, we think it is over connected'''
node_set = set([])
for i in range(0, self.node_num):
if sum(self.matrix[i]) > 5:
node_set.add(i)
return node_set
def under_connected(self):
'''if a node has less than 5 edge, we think it is under connected'''
node_set = set([])
for i in range(0, self.node_num):
if sum(self.matrix[i]) < 5:
node_set.add(i)
return node_set
def is_node_connect(self, i, j):
'''decide if node i and j are connected'''
return bool(self.matrix[i][j]) or bool(self.matrix[j][i])
def move_edge(self):
'''try to move the edge so that every node has five edges connected to it, which means every row and collunm has five 1'''
nodes_over_connect = sorted(list(self.over_connected()))
nodes_under_connect = sorted(list(self.under_connected()))
for node in nodes_under_connect:
for j in range(0, self.node_num):
if self.matrix[node][j] == 0:
for i in range(0, node):
if i in nodes_over_connect and self.matrix[i][j] == 1:
self.matrix[node][j] = self.matrix[j][node] = 1
self.matrix[i][j] = self.matrix[j][i] = 0
nodes_over_connect = sorted(list(self.over_connected()))
nodes_under_connect = sorted(list(self.under_connected()))
self.self_test()
break
if sum(self.matrix[node]) == 5:
break
self.is_all_five()
def is_subset_fail(self, set_size):
'''test if the graph contains the failing model'''
for i in range(0, self.node_num):
conn_A = self.node_connected(i)
for j in range(i+1, self.node_num):
conn_B = self.node_connected(j)
if conn_A ^ conn_B == set([i, j]):
self.printWarn('Need to be adjusted')
self.printPass('All two nodes OK')
for i in range(0, self.node_num):
conn_A = self.node_connected(i)
for j in range(i+1, self.node_num):
conn_B = self.node_connected(j)
for k in range(j+1, self.node_num):
conn_C = self.node_connected(k)
if conn_A ^ conn_B ^ conn_C == set([i, j, k]):
self.printWarn('Need to be adjusted')
self.printPass('All three nodes OK')
def is_all_five(self):
'''test if every node is collected by 5 other nodes'''
for i in range(0, self.node_num):
if sum(self.matrix[i]) != 5:
self.printError('Not all five')
return False
self.printPass('Adjust to all five success')
return True
def self_test(self):
'''test if there is something wrong with the matrix when processing'''
for i in range(0, self.node_num):
for j in range(0, i+1):
if self.matrix[i][j] != self.matrix[j][i]:
self.printError('Self-Test: Fail')
exit()
if sum(sum(self.matrix)) != 50:
self.printError('Self-Test: Fail')
exit()
self.printPass('Self-Test: Pass')
def random_test(self, test_times):
'''black-box test for the graph'''
for i in range(0, test_times):
remove_nodes = random.sample(range(0, self.node_num), 4)
matrix_copy = copy.deepcopy(self.matrix)
for node in remove_nodes:
self.matrix[node] = 0
self.matrix[:][node] = 0
if self.is_connected_graph():
self.printPass('The test ' + str(i) + ' success')
else:
self.printError('The test ' + str(i) + ' fail')
self.matrix = matrix_copy
def is_connected_graph(self):
'''decide the graph is connected or not by the depth first searching algorithm'''
travelled = []
def dfs(node):
travelled.append(node)
neighbors = self.node_connected(node)
for item in neighbors:
if item not in travelled:
dfs(item)
dfs(0)
return set(travelled) == set(range(10))
def printError(self, text):
'''print error information'''
print(Back.RED + text)
# print(Fore.RED + 'some red text')
# print(Back.GREEN + 'and with a green background')
# print(Style.DIM + 'and in dim text')
# print(Fore.RESET + Back.RESET + Style.RESET_ALL) autoreset=True
# print('back to normal now')
def printWarn(self, text):
'''print waring information'''
print(Back.YELLOW + text)
def printPass(self, text):
'''print success information'''
print(Back.GREEN + text)
if __name__ == '__main__':
m = AdjacencyMatrix()
m.self_test()
m.move_edge()
m.is_subset_fail(2)
print m.matrix
m.random_test(10)