666 lines
23 KiB
Python
666 lines
23 KiB
Python
def graph_coloring_v8(adj_matrix):
|
||
"""高级混合图着色算法
|
||
结合多种算法优点,使用数学优化和多阶段策略,尽可能减少所需颜色数
|
||
1. 图分解与重组
|
||
2. 多阶段混合策略
|
||
3. 高级预处理与结构识别
|
||
4. 迭代颜色减少
|
||
5. 自适应参数调整
|
||
"""
|
||
import numpy as np
|
||
import random
|
||
import heapq
|
||
import time
|
||
from collections import defaultdict, deque
|
||
from itertools import combinations
|
||
|
||
start_time = time.time()
|
||
max_time = 60 # 最大运行时间(秒)
|
||
|
||
n = adj_matrix.shape[0]
|
||
best_coloring = np.full(n, -1)
|
||
best_color_count = n
|
||
|
||
# 构建邻接表以加速计算
|
||
adj_lists = [np.where(adj_matrix[i] == 1)[0] for i in range(n)]
|
||
|
||
# 计算顶点度数
|
||
degrees = np.sum(adj_matrix, axis=1)
|
||
max_degree = np.max(degrees)
|
||
|
||
# 1. 高级预处理与结构识别
|
||
|
||
def find_max_clique():
|
||
"""使用Bron-Kerbosch算法寻找最大团"""
|
||
def bron_kerbosch(r, p, x, max_clique):
|
||
if len(p) == 0 and len(x) == 0:
|
||
if len(r) > len(max_clique[0]):
|
||
max_clique[0] = r.copy()
|
||
return
|
||
|
||
# 选择枢轴点以优化分支
|
||
if p:
|
||
pivot = max(p, key=lambda v: len(set(adj_lists[v]) & p))
|
||
p_minus_n_pivot = p - set(adj_lists[pivot])
|
||
else:
|
||
p_minus_n_pivot = p
|
||
|
||
for v in list(p_minus_n_pivot):
|
||
neighbors_v = set(adj_lists[v])
|
||
bron_kerbosch(r | {v}, p & neighbors_v, x & neighbors_v, max_clique)
|
||
p.remove(v)
|
||
x.add(v)
|
||
|
||
# 提前终止条件
|
||
if time.time() - start_time > max_time / 10:
|
||
return
|
||
|
||
max_clique = [set()]
|
||
vertices = set(range(n))
|
||
|
||
# 使用度数排序启发式加速
|
||
sorted_vertices = sorted(range(n), key=lambda v: degrees[v], reverse=True)
|
||
for v in sorted_vertices[:min(100, n)]: # 限制初始顶点数量
|
||
if time.time() - start_time > max_time / 10:
|
||
break
|
||
r = {v}
|
||
p = set(adj_lists[v]) & vertices
|
||
x = vertices - p - r
|
||
bron_kerbosch(r, p, x, max_clique)
|
||
|
||
return list(max_clique[0])
|
||
|
||
def identify_independent_sets():
|
||
"""识别大的独立集"""
|
||
independent_sets = []
|
||
remaining = set(range(n))
|
||
|
||
while remaining and time.time() - start_time < max_time / 10:
|
||
# 选择度数最小的顶点开始
|
||
start_vertex = min(remaining, key=lambda v: degrees[v])
|
||
ind_set = {start_vertex}
|
||
candidates = remaining - {start_vertex} - set(adj_lists[start_vertex])
|
||
|
||
# 贪心扩展独立集
|
||
while candidates:
|
||
# 选择度数最小的顶点加入
|
||
v = min(candidates, key=lambda v: degrees[v])
|
||
ind_set.add(v)
|
||
candidates -= {v} | set(adj_lists[v])
|
||
|
||
independent_sets.append(ind_set)
|
||
remaining -= ind_set
|
||
|
||
return independent_sets
|
||
|
||
def find_bipartite_subgraphs():
|
||
"""识别二分图子结构"""
|
||
bipartite_subgraphs = []
|
||
visited = np.zeros(n, dtype=bool)
|
||
|
||
def bfs_bipartite(start):
|
||
queue = deque([(start, 0)]) # (vertex, color)
|
||
coloring = {start: 0}
|
||
component = {start}
|
||
|
||
while queue and time.time() - start_time < max_time / 10:
|
||
v, color = queue.popleft()
|
||
next_color = 1 - color
|
||
|
||
for u in adj_lists[v]:
|
||
if u not in coloring:
|
||
coloring[u] = next_color
|
||
component.add(u)
|
||
queue.append((u, next_color))
|
||
elif coloring[u] == color: # 冲突,不是二分图
|
||
return None
|
||
|
||
# 分离两个部分
|
||
part0 = {v for v, c in coloring.items() if c == 0}
|
||
part1 = {v for v, c in coloring.items() if c == 1}
|
||
return (part0, part1)
|
||
|
||
# 寻找所有连通的二分子图
|
||
for i in range(n):
|
||
if not visited[i] and time.time() - start_time < max_time / 10:
|
||
result = bfs_bipartite(i)
|
||
if result:
|
||
part0, part1 = result
|
||
bipartite_subgraphs.append((part0, part1))
|
||
for v in part0 | part1:
|
||
visited[v] = True
|
||
|
||
return bipartite_subgraphs
|
||
|
||
# 2. 图分解技术
|
||
|
||
def decompose_graph():
|
||
"""将图分解为更易处理的组件"""
|
||
# 寻找割点和桥
|
||
articulation_points = find_articulation_points()
|
||
|
||
# 移除割点后的连通分量
|
||
components = []
|
||
visited = np.zeros(n, dtype=bool)
|
||
|
||
def dfs(v, component):
|
||
visited[v] = True
|
||
component.add(v)
|
||
for u in adj_lists[v]:
|
||
if not visited[u] and u not in articulation_points:
|
||
dfs(u, component)
|
||
|
||
# 找出所有连通分量
|
||
for i in range(n):
|
||
if not visited[i] and i not in articulation_points:
|
||
component = set()
|
||
dfs(i, component)
|
||
if component:
|
||
components.append(component)
|
||
|
||
# 如果没有找到有效分解,返回整个图
|
||
if not components:
|
||
return [set(range(n))]
|
||
|
||
return components
|
||
|
||
def find_articulation_points():
|
||
"""使用Tarjan算法寻找割点"""
|
||
disc = [-1] * n
|
||
low = [-1] * n
|
||
visited = [False] * n
|
||
ap = set()
|
||
timer = [0]
|
||
|
||
def dfs(u, parent):
|
||
children = 0
|
||
visited[u] = True
|
||
disc[u] = low[u] = timer[0]
|
||
timer[0] += 1
|
||
|
||
for v in adj_lists[u]:
|
||
if not visited[v]:
|
||
children += 1
|
||
dfs(v, u)
|
||
low[u] = min(low[u], low[v])
|
||
|
||
# 检查u是否为割点
|
||
if parent != -1 and low[v] >= disc[u]:
|
||
ap.add(u)
|
||
elif v != parent:
|
||
low[u] = min(low[u], disc[v])
|
||
|
||
# 如果u是根节点且有多个子节点
|
||
if parent == -1 and children > 1:
|
||
ap.add(u)
|
||
|
||
for i in range(n):
|
||
if not visited[i]:
|
||
dfs(i, -1)
|
||
|
||
return ap
|
||
|
||
# 3. 核心着色算法
|
||
|
||
def dsatur_coloring(vertices=None, max_colors=None):
|
||
"""增强版DSATUR算法"""
|
||
if vertices is None:
|
||
vertices = set(range(n))
|
||
if max_colors is None:
|
||
max_colors = n
|
||
|
||
n_sub = len(vertices)
|
||
vertices_list = list(vertices)
|
||
vertex_map = {v: i for i, v in enumerate(vertices_list)}
|
||
reverse_map = {i: v for v, i in vertex_map.items()}
|
||
|
||
# 创建子图的邻接表
|
||
sub_adj_lists = [[] for _ in range(n_sub)]
|
||
for i, v in enumerate(vertices_list):
|
||
for u in adj_lists[v]:
|
||
if u in vertex_map:
|
||
sub_adj_lists[i].append(vertex_map[u])
|
||
|
||
colors = np.full(n_sub, -1)
|
||
saturation = np.zeros(n_sub, dtype=int)
|
||
adj_colors = [set() for _ in range(n_sub)]
|
||
|
||
# 计算子图中的度数
|
||
sub_degrees = np.zeros(n_sub, dtype=int)
|
||
for i in range(n_sub):
|
||
sub_degrees[i] = len(sub_adj_lists[i])
|
||
|
||
# 初始化优先队列 (-饱和度, -度数, 顶点ID)
|
||
vertex_heap = [(0, -sub_degrees[i], i) for i in range(n_sub)]
|
||
heapq.heapify(vertex_heap)
|
||
|
||
colored_count = 0
|
||
colored_vertices = np.zeros(n_sub, dtype=bool)
|
||
|
||
while colored_count < n_sub and vertex_heap:
|
||
# 获取优先级最高的未着色顶点
|
||
_, _, vertex = heapq.heappop(vertex_heap)
|
||
|
||
# 如果顶点已着色,跳过
|
||
if colored_vertices[vertex]:
|
||
continue
|
||
|
||
# 计算可用颜色
|
||
used = [False] * max_colors
|
||
for u in sub_adj_lists[vertex]:
|
||
if colors[u] != -1:
|
||
used[colors[u]] = True
|
||
|
||
# 找到最小可用颜色
|
||
color = -1
|
||
for c in range(max_colors):
|
||
if not used[c]:
|
||
color = c
|
||
break
|
||
|
||
if color == -1:
|
||
# 没有可用颜色
|
||
return None
|
||
|
||
colors[vertex] = color
|
||
colored_vertices[vertex] = True
|
||
colored_count += 1
|
||
|
||
# 更新邻居的饱和度
|
||
for u in sub_adj_lists[vertex]:
|
||
if not colored_vertices[u]:
|
||
if color not in adj_colors[u]:
|
||
adj_colors[u].add(color)
|
||
saturation[u] += 1
|
||
# 重新入堆以更新优先级
|
||
heapq.heappush(vertex_heap, (-saturation[u], -sub_degrees[u], u))
|
||
|
||
# 将子图着色映射回原图
|
||
result = np.full(n, -1)
|
||
for i in range(n_sub):
|
||
result[reverse_map[i]] = colors[i]
|
||
|
||
return result
|
||
|
||
def kempe_chain_interchange(coloring, v, c1, c2):
|
||
"""Kempe链交换"""
|
||
if coloring[v] != c1 or c1 == c2:
|
||
return False
|
||
|
||
# 保存原始着色
|
||
original = coloring.copy()
|
||
|
||
# 构建Kempe链
|
||
chain = {v}
|
||
queue = deque([v])
|
||
|
||
while queue:
|
||
current = queue.popleft()
|
||
for u in adj_lists[current]:
|
||
if u not in chain and coloring[u] in (c1, c2):
|
||
chain.add(u)
|
||
queue.append(u)
|
||
|
||
# 交换颜色
|
||
for u in chain:
|
||
if coloring[u] == c1:
|
||
coloring[u] = c2
|
||
elif coloring[u] == c2:
|
||
coloring[u] = c1
|
||
|
||
# 验证交换后的合法性
|
||
for u in range(n):
|
||
for v in adj_lists[u]:
|
||
if coloring[u] == coloring[v] and coloring[u] != -1:
|
||
# 恢复原始着色
|
||
coloring[:] = original
|
||
return False
|
||
|
||
return True
|
||
|
||
def iterated_greedy(initial_coloring=None, iterations=100):
|
||
"""迭代贪心算法"""
|
||
if initial_coloring is None:
|
||
# 使用DSATUR生成初始解
|
||
coloring = dsatur_coloring()
|
||
else:
|
||
coloring = initial_coloring.copy()
|
||
|
||
best = coloring.copy()
|
||
best_colors_used = len(set(coloring))
|
||
|
||
for _ in range(iterations):
|
||
if time.time() - start_time > max_time:
|
||
break
|
||
|
||
# 随机选择顶点顺序
|
||
vertices = list(range(n))
|
||
random.shuffle(vertices)
|
||
|
||
# 临时移除顶点着色
|
||
temp_coloring = coloring.copy()
|
||
for v in vertices:
|
||
temp_coloring[v] = -1
|
||
|
||
# 重新着色
|
||
for v in vertices:
|
||
used = [False] * n
|
||
for u in adj_lists[v]:
|
||
if temp_coloring[u] != -1:
|
||
used[temp_coloring[u]] = True
|
||
|
||
# 找到最小可用颜色
|
||
for c in range(n):
|
||
if not used[c]:
|
||
temp_coloring[v] = c
|
||
break
|
||
|
||
# 更新最佳解
|
||
colors_used = len(set(temp_coloring))
|
||
if colors_used < best_colors_used:
|
||
best = temp_coloring.copy()
|
||
best_colors_used = colors_used
|
||
coloring = temp_coloring.copy()
|
||
elif random.random() < 0.3: # 有时接受较差解以跳出局部最优
|
||
coloring = temp_coloring.copy()
|
||
|
||
return best
|
||
|
||
def tabu_search(initial_coloring, max_iterations=1000):
|
||
"""禁忌搜索优化"""
|
||
coloring = initial_coloring.copy()
|
||
best_coloring = coloring.copy()
|
||
best_colors_used = len(set(coloring))
|
||
|
||
# 初始化禁忌表
|
||
tabu_list = {}
|
||
tabu_tenure = 10
|
||
iteration = 0
|
||
|
||
# 计算冲突
|
||
def count_conflicts():
|
||
conflicts = 0
|
||
for v in range(n):
|
||
for u in adj_lists[v]:
|
||
if u > v and coloring[u] == coloring[v]:
|
||
conflicts += 1
|
||
return conflicts
|
||
|
||
# 找到最佳移动
|
||
def find_best_move():
|
||
best_delta = float('inf')
|
||
best_moves = []
|
||
|
||
for v in range(n):
|
||
current_color = coloring[v]
|
||
|
||
# 计算当前冲突
|
||
current_conflicts = sum(1 for u in adj_lists[v] if coloring[u] == current_color)
|
||
|
||
# 尝试所有可能的颜色
|
||
for c in range(best_colors_used + 1):
|
||
if c != current_color:
|
||
# 计算新冲突
|
||
new_conflicts = sum(1 for u in adj_lists[v] if coloring[u] == c)
|
||
delta = new_conflicts - current_conflicts
|
||
|
||
# 检查禁忌状态
|
||
move_key = (v, c)
|
||
is_tabu = move_key in tabu_list and iteration < tabu_list[move_key]
|
||
|
||
# 特赦准则:如果移动导致新的最佳解
|
||
if is_tabu and not (new_conflicts == 0 and c < best_colors_used):
|
||
continue
|
||
|
||
if delta <= best_delta:
|
||
if delta < best_delta:
|
||
best_moves = []
|
||
best_delta = delta
|
||
best_moves.append((v, c))
|
||
|
||
if not best_moves:
|
||
return None, None
|
||
|
||
# 随机选择一个最佳移动
|
||
v, c = random.choice(best_moves)
|
||
return v, c
|
||
|
||
# 主循环
|
||
while iteration < max_iterations and time.time() - start_time < max_time:
|
||
v, c = find_best_move()
|
||
if v is None:
|
||
break
|
||
|
||
# 执行移动
|
||
old_color = coloring[v]
|
||
coloring[v] = c
|
||
|
||
# 更新禁忌表
|
||
tabu_list[(v, old_color)] = iteration + tabu_tenure
|
||
|
||
# 更新最佳解
|
||
colors_used = len(set(coloring))
|
||
if colors_used < best_colors_used:
|
||
best_coloring = coloring.copy()
|
||
best_colors_used = colors_used
|
||
|
||
iteration += 1
|
||
|
||
return best_coloring
|
||
|
||
# 4. 多阶段混合策略
|
||
|
||
def hybrid_coloring():
|
||
"""多阶段混合着色策略"""
|
||
# 阶段1: 预处理和结构识别
|
||
max_clique = find_max_clique()
|
||
clique_size = len(max_clique)
|
||
|
||
# 阶段2: 初始解生成
|
||
# 使用DSATUR生成初始解
|
||
initial_coloring = dsatur_coloring()
|
||
if initial_coloring is None:
|
||
initial_coloring = np.zeros(n, dtype=int)
|
||
for i in range(n):
|
||
used = set()
|
||
for j in adj_lists[i]:
|
||
if j < i:
|
||
used.add(initial_coloring[j])
|
||
for c in range(n):
|
||
if c not in used:
|
||
initial_coloring[i] = c
|
||
break
|
||
|
||
# 阶段3: 迭代优化
|
||
# 应用迭代贪心
|
||
improved_coloring = iterated_greedy(initial_coloring, iterations=50)
|
||
colors_used = len(set(improved_coloring))
|
||
|
||
# 修复:使用nonlocal关键字声明best_color_count是外部变量
|
||
nonlocal best_color_count
|
||
|
||
if colors_used < best_color_count:
|
||
best_coloring[:] = improved_coloring
|
||
best_color_count = colors_used
|
||
|
||
# 阶段4: 禁忌搜索优化
|
||
if time.time() - start_time < max_time * 0.7:
|
||
tabu_coloring = tabu_search(improved_coloring, max_iterations=500)
|
||
tabu_colors_used = len(set(tabu_coloring))
|
||
|
||
if tabu_colors_used < best_color_count:
|
||
best_coloring[:] = tabu_coloring
|
||
best_color_count = tabu_colors_used
|
||
|
||
# 阶段5: 颜色合并
|
||
if time.time() - start_time < max_time * 0.9:
|
||
merged_coloring = color_merging(best_coloring.copy())
|
||
merged_colors_used = len(set(merged_coloring))
|
||
|
||
if merged_colors_used < best_color_count:
|
||
best_coloring[:] = merged_coloring
|
||
best_color_count = merged_colors_used
|
||
|
||
def color_merging(coloring):
|
||
"""尝试合并颜色类"""
|
||
colors_used = sorted(set(coloring))
|
||
|
||
# 尝试合并每对颜色
|
||
for c1, c2 in combinations(colors_used, 2):
|
||
# 检查是否可以合并
|
||
can_merge = True
|
||
vertices_with_c1 = np.where(coloring == c1)[0]
|
||
|
||
for v in vertices_with_c1:
|
||
for u in adj_lists[v]:
|
||
if coloring[u] == c2:
|
||
can_merge = False
|
||
break
|
||
if not can_merge:
|
||
break
|
||
|
||
if can_merge:
|
||
# 合并颜色
|
||
coloring[vertices_with_c1] = c2
|
||
|
||
# 递归尝试更多合并
|
||
return color_merging(coloring)
|
||
|
||
# 重新映射颜色以确保连续
|
||
color_map = {}
|
||
new_coloring = np.zeros_like(coloring)
|
||
next_color = 0
|
||
|
||
for i, c in enumerate(coloring):
|
||
if c not in color_map:
|
||
color_map[c] = next_color
|
||
next_color += 1
|
||
new_coloring[i] = color_map[c]
|
||
|
||
return new_coloring
|
||
|
||
# 5. 图分解与重组
|
||
|
||
def solve_by_decomposition():
|
||
"""通过图分解求解"""
|
||
# 分解图
|
||
components = decompose_graph()
|
||
|
||
if len(components) > 1:
|
||
# 分别处理每个组件
|
||
component_colorings = []
|
||
max_colors = 0
|
||
|
||
for component in components:
|
||
# 为组件着色
|
||
comp_coloring = dsatur_coloring(component)
|
||
|
||
# 重新映射颜色
|
||
color_map = {}
|
||
for v in component:
|
||
if comp_coloring[v] not in color_map:
|
||
color_map[comp_coloring[v]] = len(color_map)
|
||
comp_coloring[v] = color_map[comp_coloring[v]]
|
||
|
||
component_colorings.append((component, comp_coloring))
|
||
max_colors = max(max_colors, max(comp_coloring[list(component)]) + 1)
|
||
|
||
# 重组解
|
||
combined_coloring = np.full(n, -1)
|
||
for component, comp_coloring in component_colorings:
|
||
for v in component:
|
||
combined_coloring[v] = comp_coloring[v]
|
||
|
||
return combined_coloring
|
||
else:
|
||
return None
|
||
|
||
# 6. 迭代颜色减少
|
||
|
||
def iterative_color_reduction(coloring):
|
||
"""迭代尝试减少颜色数"""
|
||
colors_used = len(set(coloring))
|
||
|
||
# 如果颜色数已经等于最大团大小,无法进一步减少
|
||
max_clique = find_max_clique()
|
||
if colors_used <= len(max_clique):
|
||
return coloring
|
||
|
||
# 尝试减少一种颜色
|
||
target_colors = colors_used - 1
|
||
|
||
# 重新映射颜色以确保连续
|
||
color_map = {}
|
||
for i, c in enumerate(coloring):
|
||
if c not in color_map:
|
||
color_map[c] = len(color_map)
|
||
coloring[i] = color_map[c]
|
||
|
||
# 尝试移除最高颜色
|
||
highest_color = target_colors
|
||
vertices_with_highest = np.where(coloring == highest_color)[0]
|
||
|
||
# 临时移除这些顶点的颜色
|
||
temp_coloring = coloring.copy()
|
||
temp_coloring[vertices_with_highest] = -1
|
||
|
||
# 尝试用更少的颜色重新着色
|
||
for v in vertices_with_highest:
|
||
used = [False] * target_colors
|
||
for u in adj_lists[v]:
|
||
if temp_coloring[u] != -1:
|
||
used[temp_coloring[u]] = True
|
||
|
||
# 寻找可用颜色
|
||
available_color = -1
|
||
for c in range(target_colors):
|
||
if not used[c]:
|
||
available_color = c
|
||
break
|
||
|
||
if available_color != -1:
|
||
temp_coloring[v] = available_color
|
||
else:
|
||
# 无法减少颜色
|
||
return coloring
|
||
|
||
# 递归尝试进一步减少
|
||
if time.time() - start_time < max_time * 0.95:
|
||
return iterative_color_reduction(temp_coloring)
|
||
else:
|
||
return temp_coloring
|
||
|
||
# 主算法流程
|
||
|
||
# 1. 尝试通过图分解求解
|
||
decomp_coloring = solve_by_decomposition()
|
||
if decomp_coloring is not None:
|
||
decomp_colors_used = len(set(decomp_coloring))
|
||
if decomp_colors_used < best_color_count:
|
||
best_coloring = decomp_coloring
|
||
best_color_count = decomp_colors_used
|
||
|
||
# 2. 应用多阶段混合策略
|
||
hybrid_coloring()
|
||
|
||
# 3. 迭代颜色减少
|
||
if time.time() - start_time < max_time * 0.95:
|
||
reduced_coloring = iterative_color_reduction(best_coloring.copy())
|
||
reduced_colors_used = len(set(reduced_coloring))
|
||
|
||
if reduced_colors_used < best_color_count:
|
||
best_coloring = reduced_coloring
|
||
best_color_count = reduced_colors_used
|
||
|
||
# 确保颜色编号连续且从0开始
|
||
color_map = {}
|
||
final_coloring = np.zeros_like(best_coloring)
|
||
next_color = 0
|
||
|
||
for i, c in enumerate(best_coloring):
|
||
if c not in color_map:
|
||
color_map[c] = next_color
|
||
next_color += 1
|
||
final_coloring[i] = color_map[c]
|
||
|
||
return final_coloring |