1640 lines
55 KiB
Python
1640 lines
55 KiB
Python
import numpy as np
|
|
|
|
def greedy_coloring(adj_matrix):
|
|
"""简单贪心算法
|
|
按顺序为每个顶点分配可用的最小颜色编号
|
|
"""
|
|
n = adj_matrix.shape[0]
|
|
colors = [-1] * n
|
|
|
|
# 为第一个顶点分配颜色0
|
|
colors[0] = 0
|
|
|
|
# 为剩余顶点着色
|
|
for u in range(1, n):
|
|
# 标记已用于邻接点的颜色
|
|
used = [False] * n
|
|
for v in range(n):
|
|
if adj_matrix[u][v] and colors[v] != -1:
|
|
used[colors[v]] = True
|
|
|
|
# 找到第一个未使用的颜色
|
|
for c in range(n):
|
|
if not used[c]:
|
|
colors[u] = c
|
|
break
|
|
|
|
return colors
|
|
|
|
def welsh_powell_coloring(adj_matrix):
|
|
"""Welsh-Powell算法
|
|
按度数降序排列顶点,依次为不相邻的顶点分配相同颜色
|
|
"""
|
|
n = adj_matrix.shape[0]
|
|
colors = [-1] * n
|
|
|
|
# 计算每个顶点的度数
|
|
degrees = [(i, sum(adj_matrix[i])) for i in range(n)]
|
|
# 按度数降序排序
|
|
degrees.sort(key=lambda x: x[1], reverse=True)
|
|
|
|
color = 0
|
|
while -1 in colors:
|
|
# 找到未着色的第一个顶点
|
|
first = next(v for v,d in degrees if colors[v] == -1)
|
|
colors[first] = color
|
|
|
|
# 为所有可能的不相邻顶点分配相同颜色
|
|
for v,_ in degrees:
|
|
if colors[v] == -1:
|
|
# 检查是否与已分配该颜色的顶点相邻
|
|
can_color = True
|
|
for u in range(n):
|
|
if colors[u] == color and adj_matrix[v][u]:
|
|
can_color = False
|
|
break
|
|
if can_color:
|
|
colors[v] = color
|
|
color += 1
|
|
|
|
return colors
|
|
|
|
def brute_force_coloring(adj_matrix):
|
|
"""暴力搜索算法
|
|
尝试所有可能的颜色组合直到找到合法解
|
|
"""
|
|
n = adj_matrix.shape[0]
|
|
colors = [-1] * n
|
|
|
|
def is_safe(v, c):
|
|
"""检查顶点v是否可以着色为c"""
|
|
for i in range(n):
|
|
if adj_matrix[v][i] and colors[i] == c:
|
|
return False
|
|
return True
|
|
|
|
def color_graph(v):
|
|
"""递归为顶点v及之后的顶点着色"""
|
|
if v == n:
|
|
return True
|
|
|
|
for c in range(n):
|
|
if is_safe(v, c):
|
|
colors[v] = c
|
|
if color_graph(v + 1):
|
|
return True
|
|
colors[v] = -1
|
|
|
|
return False
|
|
|
|
if not color_graph(0):
|
|
raise Exception("无法找到合法着色方案")
|
|
return colors
|
|
|
|
def dsatur_coloring(adj_matrix):
|
|
"""DSATUR算法
|
|
基于顶点饱和度的动态着色算法
|
|
"""
|
|
n = adj_matrix.shape[0]
|
|
colors = [-1] * n
|
|
|
|
def get_saturation(v):
|
|
"""计算顶点v的饱和度(邻接顶点使用的不同颜色数)"""
|
|
adjacent_colors = set()
|
|
for i in range(n):
|
|
if adj_matrix[v][i] and colors[i] != -1:
|
|
adjacent_colors.add(colors[i])
|
|
return len(adjacent_colors)
|
|
|
|
def get_uncolored_vertex():
|
|
"""选择饱和度最大的未着色顶点"""
|
|
max_sat = -1
|
|
max_deg = -1
|
|
selected = -1
|
|
|
|
for v in range(n):
|
|
if colors[v] == -1:
|
|
sat = get_saturation(v)
|
|
deg = sum(adj_matrix[v])
|
|
|
|
if sat > max_sat or (sat == max_sat and deg > max_deg):
|
|
max_sat = sat
|
|
max_deg = deg
|
|
selected = v
|
|
|
|
return selected
|
|
|
|
# 为第一个最大度数的顶点着色
|
|
first = max(range(n), key=lambda i: sum(adj_matrix[i]))
|
|
colors[first] = 0
|
|
|
|
# 为剩余顶点着色
|
|
for _ in range(n-1):
|
|
v = get_uncolored_vertex()
|
|
|
|
# 找到最小可用颜色
|
|
used = [False] * n
|
|
for u in range(n):
|
|
if adj_matrix[v][u] and colors[u] != -1:
|
|
used[colors[u]] = True
|
|
|
|
for c in range(n):
|
|
if not used[c]:
|
|
colors[v] = c
|
|
break
|
|
|
|
return colors
|
|
|
|
def graph_coloring_v1(adj_matrix):
|
|
"""基于度数排序的贪心算法"""
|
|
n = adj_matrix.shape[0]
|
|
colors = [-1] * n
|
|
degree = np.sum(adj_matrix, axis=1)
|
|
nodes = np.argsort(-degree)
|
|
|
|
for node in nodes:
|
|
available_colors = [True] * n
|
|
|
|
for neighbor in range(n):
|
|
if adj_matrix[node][neighbor] == 1 and colors[neighbor] != -1:
|
|
available_colors[colors[neighbor]] = False
|
|
|
|
for color in range(n):
|
|
if available_colors[color]:
|
|
colors[node] = color
|
|
break
|
|
|
|
return colors
|
|
|
|
def graph_coloring_v2(adj_matrix):
|
|
"""基于饱和度的贪心算法"""
|
|
n = adj_matrix.shape[0]
|
|
colors = np.full(n, -1)
|
|
|
|
def get_saturation_degree(vertex):
|
|
return len(set(colors[v] for v in range(n) if adj_matrix[vertex][v] == 1 and colors[v] != -1))
|
|
|
|
def find_next_vertex():
|
|
max_saturation = -1
|
|
candidate_vertex = -1
|
|
for vertex in range(n):
|
|
if colors[vertex] == -1:
|
|
saturation_degree = get_saturation_degree(vertex)
|
|
degree = sum(adj_matrix[vertex])
|
|
if saturation_degree > max_saturation or (saturation_degree == max_saturation and degree > sum(adj_matrix[candidate_vertex]) if candidate_vertex != -1 else degree):
|
|
max_saturation = saturation_degree
|
|
candidate_vertex = vertex
|
|
return candidate_vertex
|
|
|
|
for _ in range(n):
|
|
u = find_next_vertex()
|
|
if u == -1:
|
|
break
|
|
|
|
available_colors = set(range(n))
|
|
for v in range(n):
|
|
if adj_matrix[u][v] == 1 and colors[v] != -1:
|
|
available_colors.discard(colors[v])
|
|
|
|
if available_colors:
|
|
color_usage = np.zeros(n, dtype=int)
|
|
for v in range(n):
|
|
if adj_matrix[u][v] == 1 and colors[v] != -1:
|
|
color_usage[colors[v]] += 1
|
|
|
|
min_color = min(available_colors, key=lambda c: (color_usage[c], c))
|
|
colors[u] = min_color
|
|
|
|
return colors
|
|
|
|
def graph_coloring_v3(adj_matrix):
|
|
"""带回溯的混合算法"""
|
|
n = adj_matrix.shape[0]
|
|
colors = np.full(n, -1)
|
|
degree_list = sorted(range(n), key=lambda x: sum(adj_matrix[x]), reverse=True)
|
|
|
|
def can_color(vertex, color):
|
|
return all(adj_matrix[vertex][neighbor] == 0 or colors[neighbor] != color for neighbor in range(n))
|
|
|
|
def backtrack(vertex):
|
|
if vertex == n:
|
|
return True
|
|
|
|
for color in range(n):
|
|
if can_color(vertex, color):
|
|
colors[vertex] = color
|
|
if backtrack(vertex + 1):
|
|
return True
|
|
colors[vertex] = -1
|
|
|
|
return False
|
|
|
|
for u in degree_list:
|
|
available_colors = set(range(n))
|
|
|
|
for v in range(n):
|
|
if adj_matrix[u][v] == 1 and colors[v] != -1:
|
|
available_colors.discard(colors[v])
|
|
|
|
if available_colors:
|
|
color_usage = np.zeros(n, dtype=int)
|
|
for v in range(n):
|
|
if adj_matrix[u][v] == 1 and colors[v] != -1:
|
|
color_usage[colors[v]] += 1
|
|
|
|
min_color = min(available_colors, key=lambda c: (color_usage[c], c))
|
|
colors[u] = min_color
|
|
else:
|
|
if not backtrack(u):
|
|
raise Exception("无法为图着色")
|
|
|
|
return colors
|
|
|
|
def graph_coloring_v4(adj_matrix):
|
|
"""混合进化算法"""
|
|
import numpy as np
|
|
import heapq
|
|
from dataclasses import dataclass
|
|
from typing import List, Set
|
|
import random
|
|
|
|
@dataclass
|
|
class Move:
|
|
vertex: int
|
|
color: int
|
|
|
|
class TabuSearch:
|
|
def __init__(self, n_vertices, n_colors):
|
|
self.n_vertices = n_vertices
|
|
self.n_colors = n_colors
|
|
self.adj_color_table = np.zeros((n_vertices, n_colors), dtype=int)
|
|
self.tabu_tenure = np.zeros((n_vertices, n_colors), dtype=int)
|
|
self.conflict = 0
|
|
self.best_conflict = 0
|
|
self.iteration = 0
|
|
|
|
def find_move(self, solution, adj_list):
|
|
min_delta = float('inf')
|
|
tabu_delta = float('inf')
|
|
equal_nontabu = []
|
|
equal_tabu = []
|
|
|
|
aspiration = self.best_conflict - self.conflict
|
|
|
|
for v in range(self.n_vertices):
|
|
curr_color = solution[v]
|
|
if self.adj_color_table[v][curr_color] > 0:
|
|
for c in range(self.n_colors):
|
|
if curr_color != c:
|
|
delta = self.adj_color_table[v][c] - self.adj_color_table[v][curr_color]
|
|
|
|
if self.tabu_tenure[v][c] <= self.iteration:
|
|
if delta < min_delta:
|
|
min_delta = delta
|
|
equal_nontabu = [(v, c)]
|
|
elif delta == min_delta:
|
|
equal_nontabu.append((v, c))
|
|
else:
|
|
if delta < tabu_delta:
|
|
tabu_delta = delta
|
|
equal_tabu = [(v, c)]
|
|
elif delta == tabu_delta:
|
|
equal_tabu.append((v, c))
|
|
|
|
if tabu_delta < aspiration and tabu_delta < min_delta:
|
|
v, c = random.choice(equal_tabu)
|
|
return Move(v, c), tabu_delta
|
|
else:
|
|
v, c = random.choice(equal_nontabu)
|
|
return Move(v, c), min_delta
|
|
|
|
def make_move(self, solution, move, adj_list):
|
|
self.conflict += move[1]
|
|
if self.conflict < self.best_conflict:
|
|
self.best_conflict = self.conflict
|
|
|
|
old_color = solution[move[0].vertex]
|
|
solution[move[0].vertex] = move[0].color
|
|
|
|
self.tabu_tenure[move[0].vertex][old_color] = (
|
|
self.iteration + self.conflict + random.randint(1,10)
|
|
)
|
|
|
|
for neighbor in adj_list[move[0].vertex]:
|
|
self.adj_color_table[neighbor][old_color] -= 1
|
|
self.adj_color_table[neighbor][move[0].color] += 1
|
|
|
|
def search(self, solution, adj_list, max_iter=20000):
|
|
# 计算初始冲突
|
|
self.conflict = 0
|
|
for v in range(self.n_vertices):
|
|
color = solution[v]
|
|
for u in adj_list[v]:
|
|
if solution[u] == color:
|
|
self.conflict += 1
|
|
self.adj_color_table[v][solution[u]] += 1
|
|
|
|
self.conflict //= 2
|
|
self.best_conflict = self.conflict
|
|
|
|
self.iteration = 0
|
|
while self.iteration < max_iter and self.conflict > 0:
|
|
self.iteration += 1
|
|
move = self.find_move(solution, adj_list)
|
|
self.make_move(solution, move, adj_list)
|
|
|
|
class Population:
|
|
def __init__(self, n_vertices, n_colors, pop_size):
|
|
self.solutions = []
|
|
self.conflicts = []
|
|
self.min_conflict = float('inf')
|
|
self.best_solution = None
|
|
|
|
for _ in range(pop_size):
|
|
sol = np.random.randint(0, n_colors, size=n_vertices)
|
|
self.solutions.append(sol)
|
|
|
|
def update(self, new_sol, new_conflict):
|
|
if new_conflict < self.min_conflict:
|
|
self.min_conflict = new_conflict
|
|
self.best_solution = new_sol.copy()
|
|
|
|
# 替换最差解
|
|
max_idx = np.argmax(self.conflicts)
|
|
self.solutions[max_idx] = new_sol
|
|
self.conflicts[max_idx] = new_conflict
|
|
|
|
# 主算法流程
|
|
n = adj_matrix.shape[0]
|
|
n_colors = max(np.sum(adj_matrix, axis=1))
|
|
pop_size = 10
|
|
|
|
# 构建邻接表
|
|
adj_list = [[] for _ in range(n)]
|
|
for i in range(n):
|
|
for j in range(n):
|
|
if adj_matrix[i][j]:
|
|
adj_list[i].append(j)
|
|
|
|
# 初始化种群
|
|
pop = Population(n, n_colors, pop_size)
|
|
tabu = TabuSearch(n, n_colors)
|
|
|
|
# 对初始种群进行禁忌搜索优化
|
|
for sol in pop.solutions:
|
|
tabu.search(sol, adj_list)
|
|
pop.conflicts.append(tabu.conflict)
|
|
if tabu.conflict < pop.min_conflict:
|
|
pop.min_conflict = tabu.conflict
|
|
pop.best_solution = sol.copy()
|
|
|
|
# 主循环
|
|
max_gen = 1000
|
|
gen = 0
|
|
while gen < max_gen and pop.min_conflict > 0:
|
|
# 选择父代
|
|
p1, p2 = random.sample(range(pop_size), 2)
|
|
sol1, sol2 = pop.solutions[p1], pop.solutions[p2]
|
|
|
|
# 交叉
|
|
child = np.zeros(n, dtype=int)
|
|
for i in range(n_colors):
|
|
if i % 2 == 0:
|
|
mask = (sol1 == i)
|
|
else:
|
|
mask = (sol2 == i)
|
|
child[mask] = i
|
|
|
|
# 对未着色顶点随机着色
|
|
uncolored = (child == 0)
|
|
child[uncolored] = np.random.randint(0, n_colors, size=np.sum(uncolored))
|
|
|
|
# 禁忌搜索优化新解
|
|
tabu.search(child, adj_list)
|
|
|
|
# 更新种群
|
|
pop.update(child, tabu.conflict)
|
|
gen += 1
|
|
|
|
return pop.best_solution
|
|
|
|
def graph_coloring_v5(adj_matrix):
|
|
"""优化的饱和度贪心算法
|
|
1. 使用堆维护顶点优先级
|
|
2. 缓存并动态更新饱和度
|
|
3. 优化邻接颜色集合计算
|
|
4. 改进颜色选择策略
|
|
"""
|
|
import heapq
|
|
|
|
n = adj_matrix.shape[0]
|
|
colors = np.full(n, -1)
|
|
|
|
# 预计算并缓存每个顶点的度数
|
|
degrees = np.sum(adj_matrix, axis=1)
|
|
|
|
# 缓存每个顶点的邻接顶点列表
|
|
adj_lists = [np.where(adj_matrix[i] == 1)[0] for i in range(n)]
|
|
|
|
# 维护每个顶点的邻接颜色集合
|
|
adj_colors = [set() for _ in range(n)]
|
|
|
|
# 初始化优先队列 (-饱和度, -度数, 顶点ID)
|
|
vertex_heap = [(0, -degrees[i], i) for i in range(n)]
|
|
heapq.heapify(vertex_heap)
|
|
|
|
# 标记顶点是否已着色
|
|
colored = np.zeros(n, dtype=bool)
|
|
|
|
def update_saturation(vertex):
|
|
"""更新顶点的饱和度并返回新的优先级"""
|
|
saturation = len(adj_colors[vertex])
|
|
return (-saturation, -degrees[vertex], vertex)
|
|
|
|
def update_neighbors(vertex, color):
|
|
"""更新邻居的饱和度"""
|
|
for neighbor in adj_lists[vertex]:
|
|
if not colored[neighbor]:
|
|
adj_colors[neighbor].add(color)
|
|
|
|
while vertex_heap:
|
|
# 获取优先级最高的未着色顶点
|
|
_, _, vertex = heapq.heappop(vertex_heap)
|
|
|
|
# 如果顶点已着色,跳过
|
|
if colored[vertex]:
|
|
continue
|
|
|
|
# 计算可用颜色集合
|
|
used_colors = adj_colors[vertex]
|
|
available_colors = set(range(n)) - used_colors
|
|
|
|
if available_colors:
|
|
# 统计邻接顶点中各颜色的使用频率
|
|
color_usage = np.zeros(n, dtype=int)
|
|
for neighbor in adj_lists[vertex]:
|
|
if colored[neighbor]:
|
|
color_usage[colors[neighbor]] += 1
|
|
|
|
# 选择使用频率最低的可用颜色
|
|
min_color = min(available_colors, key=lambda c: (color_usage[c], c))
|
|
colors[vertex] = min_color
|
|
colored[vertex] = True
|
|
|
|
# 更新邻居的饱和度并重新入堆
|
|
update_neighbors(vertex, min_color)
|
|
|
|
# 将受影响的未着色邻居重新入堆
|
|
for neighbor in adj_lists[vertex]:
|
|
if not colored[neighbor]:
|
|
heapq.heappush(vertex_heap, update_saturation(neighbor))
|
|
|
|
return colors
|
|
|
|
def graph_coloring_v6(adj_matrix):
|
|
"""高性能混合算法
|
|
1. 结合DSATUR和禁忌搜索
|
|
2. 使用局部搜索优化初始解
|
|
3. 采用自适应参数调整
|
|
4. 并行处理大规模数据
|
|
"""
|
|
import heapq
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
import threading
|
|
|
|
n = adj_matrix.shape[0]
|
|
best_colors = np.full(n, -1)
|
|
best_num_colors = n
|
|
|
|
# 预处理:计算关键数据
|
|
degrees = np.sum(adj_matrix, axis=1)
|
|
adj_lists = [np.where(adj_matrix[i] == 1)[0] for i in range(n)]
|
|
max_degree = np.max(degrees)
|
|
initial_colors = max(max_degree + 1, n // 2) # 估计所需颜色数
|
|
|
|
# 线程安全的更新机制
|
|
solution_lock = threading.Lock()
|
|
|
|
class EnhancedDSATUR:
|
|
def __init__(self):
|
|
self.colors = np.full(n, -1)
|
|
self.colored = np.zeros(n, dtype=bool)
|
|
self.saturation = np.zeros(n, dtype=int)
|
|
self.adj_colors = [set() for _ in range(n)]
|
|
|
|
def get_next_vertex(self):
|
|
max_sat = -1
|
|
max_deg = -1
|
|
selected = -1
|
|
|
|
for v in range(n):
|
|
if not self.colored[v]:
|
|
sat = self.saturation[v]
|
|
deg = degrees[v]
|
|
if sat > max_sat or (sat == max_sat and deg > max_deg):
|
|
max_sat = sat
|
|
max_deg = deg
|
|
selected = v
|
|
return selected
|
|
|
|
def color_vertex(self, vertex, max_color):
|
|
used = set()
|
|
for u in adj_lists[vertex]:
|
|
if self.colored[u]:
|
|
used.add(self.colors[u])
|
|
|
|
# 优化颜色选择策略
|
|
color_freq = np.zeros(max_color, dtype=int)
|
|
for u in adj_lists[vertex]:
|
|
if self.colored[u]:
|
|
color_freq[self.colors[u]] += 1
|
|
|
|
# 选择频率最低的可用颜色
|
|
available = set(range(max_color)) - used
|
|
if available:
|
|
color = min(available, key=lambda c: (color_freq[c], c))
|
|
self.colors[vertex] = color
|
|
self.colored[vertex] = True
|
|
|
|
# 更新邻居的饱和度
|
|
for u in adj_lists[vertex]:
|
|
if not self.colored[u]:
|
|
self.adj_colors[u].add(color)
|
|
self.saturation[u] = len(self.adj_colors[u])
|
|
return True
|
|
return False
|
|
|
|
def local_search(solution, num_colors, max_iterations=1000):
|
|
"""增强的局部搜索优化
|
|
1. 使用禁忌搜索避免循环
|
|
2. 采用Kempe链交换
|
|
3. 动态邻域大小
|
|
4. 自适应步长控制
|
|
"""
|
|
conflicts = np.zeros(n, dtype=int)
|
|
tabu_list = {} # 禁忌表
|
|
tabu_tenure = 10 # 禁忌期限
|
|
|
|
# 计算冲突
|
|
def calculate_conflicts():
|
|
conflicts.fill(0)
|
|
total = 0
|
|
for v in range(n):
|
|
color = solution[v]
|
|
for u in adj_lists[v]:
|
|
if solution[u] == color:
|
|
conflicts[v] += 1
|
|
total += 1
|
|
return total // 2
|
|
|
|
# Kempe链交换
|
|
def kempe_chain_interchange(vertex, color1, color2):
|
|
chain = {vertex}
|
|
queue = [vertex]
|
|
old_colors = solution.copy()
|
|
|
|
while queue:
|
|
v = queue.pop(0)
|
|
current_color = old_colors[v]
|
|
new_color = color2 if current_color == color1 else color1
|
|
solution[v] = new_color
|
|
|
|
for u in adj_lists[v]:
|
|
if u not in chain and old_colors[u] in (color1, color2):
|
|
chain.add(u)
|
|
queue.append(u)
|
|
|
|
# 评估交换效果
|
|
new_conflicts = calculate_conflicts()
|
|
if new_conflicts > total_conflicts:
|
|
# 如果没有改善则恢复
|
|
for v in chain:
|
|
solution[v] = old_colors[v]
|
|
return False
|
|
return True
|
|
|
|
# 寻找最佳移动
|
|
def find_best_move(iteration):
|
|
best_delta = float('inf')
|
|
best_moves = []
|
|
|
|
# 动态调整搜索范围
|
|
sample_size = min(100 + iteration // 10, n)
|
|
vertices = np.random.choice(n, size=sample_size, replace=False)
|
|
|
|
for v in vertices:
|
|
if conflicts[v] == 0:
|
|
continue
|
|
current_color = solution[v]
|
|
|
|
# 考虑颜色交换和Kempe链
|
|
for c in range(num_colors):
|
|
if c != current_color:
|
|
# 计算常规移动增量
|
|
delta = 0
|
|
for u in adj_lists[v]:
|
|
if solution[u] == c:
|
|
delta += 1
|
|
if solution[u] == current_color:
|
|
delta -= 1
|
|
|
|
# 检查禁忌状态
|
|
move_key = (v, c)
|
|
if move_key in tabu_list and iteration < tabu_list[move_key]:
|
|
# 除非这是最好的解
|
|
if delta >= best_delta:
|
|
continue
|
|
|
|
if delta <= best_delta:
|
|
if delta < best_delta:
|
|
best_moves = []
|
|
best_delta = delta
|
|
best_moves.append((v, c, "normal"))
|
|
|
|
# 尝试Kempe链交换
|
|
if np.random.random() < 0.3: # 控制Kempe链使用频率
|
|
temp_sol = solution.copy()
|
|
if kempe_chain_interchange(v, current_color, c):
|
|
k_delta = calculate_conflicts() - total_conflicts
|
|
if k_delta <= best_delta:
|
|
if k_delta < best_delta:
|
|
best_moves = []
|
|
best_delta = k_delta
|
|
best_moves.append((v, c, "kempe"))
|
|
solution[:] = temp_sol
|
|
|
|
if not best_moves:
|
|
return None, None, None, float('inf')
|
|
|
|
# 随机选择一个最佳移动
|
|
v, c, move_type = best_moves[np.random.randint(len(best_moves))]
|
|
return v, c, move_type, best_delta
|
|
|
|
total_conflicts = calculate_conflicts()
|
|
iterations = 0
|
|
no_improve = 0
|
|
|
|
while total_conflicts > 0 and iterations < max_iterations:
|
|
v, c, move_type, delta = find_best_move(iterations)
|
|
if v is None:
|
|
break
|
|
|
|
# 执行移动
|
|
if move_type == "normal":
|
|
old_color = solution[v]
|
|
solution[v] = c
|
|
|
|
# 更新冲突
|
|
for u in adj_lists[v]:
|
|
if solution[u] == old_color:
|
|
conflicts[u] -= 1
|
|
conflicts[v] -= 1
|
|
if solution[u] == c:
|
|
conflicts[u] += 1
|
|
conflicts[v] += 1
|
|
|
|
# 更新禁忌表
|
|
tabu_list[(v, old_color)] = iterations + tabu_tenure
|
|
|
|
total_conflicts += delta
|
|
iterations += 1
|
|
|
|
# 自适应参数调整
|
|
if delta >= 0:
|
|
no_improve += 1
|
|
if no_improve >= 20:
|
|
# 增加扰动
|
|
for _ in range(3):
|
|
v = np.random.randint(n)
|
|
c = np.random.randint(num_colors)
|
|
solution[v] = c
|
|
total_conflicts = calculate_conflicts()
|
|
no_improve = 0
|
|
else:
|
|
no_improve = 0
|
|
|
|
return total_conflicts == 0
|
|
|
|
def solve_with_colors(num_colors):
|
|
"""使用指定颜色数尝试求解"""
|
|
dsatur = EnhancedDSATUR()
|
|
success = True
|
|
|
|
# 按DSATUR顺序为顶点着色
|
|
for _ in range(n):
|
|
v = dsatur.get_next_vertex()
|
|
if v == -1:
|
|
break
|
|
if not dsatur.color_vertex(v, num_colors):
|
|
success = False
|
|
break
|
|
|
|
if success:
|
|
# 应用局部搜索优化
|
|
solution = dsatur.colors.copy()
|
|
if local_search(solution, num_colors):
|
|
nonlocal best_colors, best_num_colors
|
|
with solution_lock:
|
|
if num_colors < best_num_colors:
|
|
best_colors = solution.copy()
|
|
best_num_colors = num_colors
|
|
return True
|
|
return False
|
|
|
|
# 并行尝试不同的颜色数
|
|
color_range = range(max_degree, initial_colors + 1)
|
|
with ThreadPoolExecutor(max_workers=4) as executor:
|
|
executor.map(solve_with_colors, color_range)
|
|
|
|
return best_colors
|
|
|
|
|
|
def graph_coloring_v7(adj_matrix):
|
|
"""改进的Welsh-Powell算法
|
|
1. 动态度数排序
|
|
2. 颜色交换优化
|
|
3. 有限深度回溯
|
|
4. 颜色合并
|
|
5. 特殊结构识别
|
|
"""
|
|
n = adj_matrix.shape[0]
|
|
colors = np.full(n, -1)
|
|
|
|
# 预处理:识别特殊结构
|
|
def find_cliques():
|
|
"""识别图中的极大团"""
|
|
cliques = []
|
|
for i in range(n):
|
|
neighbors = set(np.where(adj_matrix[i] == 1)[0])
|
|
if not neighbors:
|
|
continue
|
|
|
|
# 检查邻居是否形成团
|
|
is_clique = True
|
|
for u in neighbors:
|
|
for v in neighbors:
|
|
if u != v and adj_matrix[u][v] == 0:
|
|
is_clique = False
|
|
break
|
|
if not is_clique:
|
|
break
|
|
|
|
if is_clique:
|
|
cliques.append(neighbors.union({i}))
|
|
return cliques
|
|
|
|
# 计算初始度数
|
|
degrees = [(i, np.sum(adj_matrix[i])) for i in range(n)]
|
|
|
|
# 构建邻接表以加速计算
|
|
adj_lists = [np.where(adj_matrix[i] == 1)[0] for i in range(n)]
|
|
|
|
# 计算有效度数(只考虑未着色的邻居)
|
|
def effective_degree(v):
|
|
return sum(1 for u in adj_lists[v] if colors[u] == -1)
|
|
|
|
# 颜色交换优化
|
|
def kempe_chain_exchange(v, c1, c2):
|
|
"""尝试Kempe链交换以减少颜色冲突"""
|
|
if c1 == c2:
|
|
return False
|
|
|
|
# 保存原始颜色
|
|
original_colors = colors.copy()
|
|
|
|
# 构建Kempe链
|
|
chain = {v}
|
|
queue = [v]
|
|
while queue:
|
|
current = queue.pop(0)
|
|
for u in adj_lists[current]:
|
|
if colors[u] in (c1, c2) and u not in chain:
|
|
chain.add(u)
|
|
queue.append(u)
|
|
|
|
# 交换颜色
|
|
for u in chain:
|
|
if colors[u] == c1:
|
|
colors[u] = c2
|
|
elif colors[u] == c2:
|
|
colors[u] = c1
|
|
|
|
# 检查交换后是否有冲突
|
|
for u in chain:
|
|
for v in adj_lists[u]:
|
|
if colors[u] == colors[v] and colors[u] != -1:
|
|
# 恢复原始颜色
|
|
colors[:] = original_colors
|
|
return False
|
|
|
|
return True
|
|
|
|
# 颜色合并
|
|
def try_merge_colors(c1, c2):
|
|
"""尝试将颜色c1合并到c2"""
|
|
vertices_with_c1 = np.where(colors == c1)[0]
|
|
|
|
# 检查每个颜色为c1的顶点是否可以改为c2
|
|
can_merge = True
|
|
for v in vertices_with_c1:
|
|
for u in adj_lists[v]:
|
|
if colors[u] == c2:
|
|
can_merge = False
|
|
break
|
|
if not can_merge:
|
|
break
|
|
|
|
if can_merge:
|
|
colors[vertices_with_c1] = c2
|
|
return True
|
|
return False
|
|
|
|
# 有限深度回溯
|
|
def backtrack_coloring(start_vertex, depth=3):
|
|
"""从指定顶点开始进行有限深度回溯着色"""
|
|
if depth == 0:
|
|
return True
|
|
|
|
# 获取可用颜色
|
|
used_colors = set()
|
|
for u in adj_lists[start_vertex]:
|
|
if colors[u] != -1:
|
|
used_colors.add(colors[u])
|
|
|
|
# 尝试每种可能的颜色
|
|
available_colors = [c for c in range(n) if c not in used_colors]
|
|
|
|
for c in available_colors:
|
|
colors[start_vertex] = c
|
|
|
|
# 找到下一个未着色顶点
|
|
next_vertex = -1
|
|
for v in range(n):
|
|
if colors[v] == -1:
|
|
next_vertex = v
|
|
break
|
|
|
|
if next_vertex == -1 or backtrack_coloring(next_vertex, depth-1):
|
|
return True
|
|
|
|
# 回溯
|
|
colors[start_vertex] = -1
|
|
return False
|
|
|
|
# 主着色算法
|
|
def enhanced_welsh_powell():
|
|
max_color = -1
|
|
uncolored = set(range(n))
|
|
|
|
while uncolored:
|
|
# 动态更新度数并排序
|
|
sorted_vertices = sorted(uncolored,
|
|
key=lambda v: (effective_degree(v), sum(adj_matrix[v])),
|
|
reverse=True)
|
|
|
|
# 选择当前度数最大的顶点
|
|
current_color = max_color + 1
|
|
max_color = current_color
|
|
|
|
# 为第一个顶点着色
|
|
first_vertex = sorted_vertices[0]
|
|
colors[first_vertex] = current_color
|
|
uncolored.remove(first_vertex)
|
|
|
|
# 为其他可能的顶点着色
|
|
for v in sorted_vertices[1:]:
|
|
if v not in uncolored:
|
|
continue
|
|
|
|
# 检查是否可以使用当前颜色
|
|
can_use_color = True
|
|
for u in adj_lists[v]:
|
|
if colors[u] == current_color:
|
|
can_use_color = False
|
|
break
|
|
|
|
if can_use_color:
|
|
colors[v] = current_color
|
|
uncolored.remove(v)
|
|
|
|
# 识别团并预先着色
|
|
cliques = find_cliques()
|
|
cliques.sort(key=len, reverse=True)
|
|
|
|
# 对最大团进行预着色
|
|
if cliques:
|
|
largest_clique = list(cliques[0])
|
|
for i, v in enumerate(largest_clique):
|
|
colors[v] = i
|
|
|
|
# 主着色过程
|
|
enhanced_welsh_powell()
|
|
|
|
# 后处理优化
|
|
|
|
# 1. 尝试颜色合并
|
|
used_colors = sorted(set(colors))
|
|
for i in range(len(used_colors)):
|
|
for j in range(i+1, len(used_colors)):
|
|
try_merge_colors(used_colors[j], used_colors[i])
|
|
|
|
# 2. 尝试Kempe链交换优化
|
|
for v in range(n):
|
|
neighbor_colors = set(colors[u] for u in adj_lists[v])
|
|
all_colors = set(colors)
|
|
for c in all_colors - neighbor_colors:
|
|
if c < colors[v]: # 尝试用更小的颜色替换
|
|
kempe_chain_exchange(v, colors[v], c)
|
|
|
|
# 3. 对关键顶点应用有限深度回溯
|
|
# 关键顶点:使用了最大颜色编号的顶点
|
|
max_color_used = max(colors)
|
|
critical_vertices = np.where(colors == max_color_used)[0]
|
|
|
|
for v in critical_vertices:
|
|
# 暂时取消着色
|
|
original_color = colors[v]
|
|
colors[v] = -1
|
|
|
|
# 尝试用更少的颜色重新着色
|
|
if not backtrack_coloring(v):
|
|
# 如果失败,恢复原始颜色
|
|
colors[v] = original_color
|
|
|
|
# 重新映射颜色以确保连续
|
|
color_map = {}
|
|
new_colors = np.zeros_like(colors)
|
|
next_color = 0
|
|
|
|
for i, c in enumerate(colors):
|
|
if c not in color_map:
|
|
color_map[c] = next_color
|
|
next_color += 1
|
|
new_colors[i] = color_map[c]
|
|
|
|
return new_colors
|
|
|
|
|
|
def graph_coloring_v9(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))
|
|
|
|
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 |