用deepseek解决圆周相邻和平方问题的过程 - l1t1/note GitHub Wiki
for (int v = neighbors._Find_first(); v < MAX_N; v = neighbors._Find_next(v)) 请把这个循环改为:先取出所有neighbors, 然后按这些neighbors按尚未使用的邻接点数量排序存入数组,再针对数组循环
为了提高代码的效率,我们可以对 neighbors
的处理方式进行优化。具体来说,我们可以先将所有未访问的邻接顶点提取出来,然后根据这些顶点的未使用邻接点数量进行排序,最后再遍历排序后的顶点数组。这样可以优先处理邻接点较少的顶点,从而减少回溯的次数。
以下是修改后的代码:
#include <iostream>
#include <vector>
#include <cmath>
#include <ctime>
#include <bitset>
#include <algorithm>
using namespace std;
const int MAX_N = 100; // 假设最大顶点数为 100
class Graph {
int V; // 顶点数
vector<bitset<MAX_N>> adj; // 邻接表(使用 bitset 表示)
public:
Graph(int V) : V(V), adj(V) {}
long long cnt = 0;
void add_edge(int u, int v) {
adj[u].set(v); // 设置 u 和 v 之间的边
adj[v].set(u); // 无向图,双向设置
}
bool hamiltonian_cycle_util(vector<int>& path, bitset<MAX_N>& visited, int pos) {
if (pos == V) {
// 检查是否形成环
if (adj[path[pos - 1]].test(path[0])) {
return true;
}
return false;
}
// 获取当前顶点的邻接顶点 bitset
bitset<MAX_N> neighbors = adj[path[pos - 1]] & ~visited;
// 提取所有未访问的邻接顶点
vector<int> neighbor_list;
for (int v = neighbors._Find_first(); v < MAX_N; v = neighbors._Find_next(v)) {
neighbor_list.push_back(v);
}
// 按未使用的邻接点数量排序
sort(neighbor_list.begin(), neighbor_list.end(), [this, &visited](int a, int b) {
return (adj[a] & ~visited).count() < (adj[b] & ~visited).count();
});
// 遍历排序后的邻接顶点
for (int v : neighbor_list) {
cnt++;
path[pos] = v;
visited.set(v); // 标记为已访问
if (hamiltonian_cycle_util(path, visited, pos + 1)) {
return true;
}
visited.reset(v); // 回溯
}
return false;
}
vector<int> hamiltonian_cycle(int s) {
vector<int> path(V, -1);
bitset<MAX_N> visited; // 使用 bitset 存储访问状态
// 从顶点 s 开始
path[0] = s;
visited.set(path[0]);
if (hamiltonian_cycle_util(path, visited, 1)) {
return path;
}
return {};
}
};
int main() {
int n = 50;
Graph G(n);
// 构建图
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
int sum = i + j;
int sqrt_sum = sqrt(sum);
if (sqrt_sum * sqrt_sum == sum) {
G.add_edge(i - 1, j - 1); // 顶点编号从 0 开始
}
}
}
for (int s = 0; s < 3; s++) {
// 基于当前系统的当前日期/时间
time_t now = time(0);
G.cnt = 0;
// 查找哈密顿环
vector<int> cycle = G.hamiltonian_cycle(s);
if (!cycle.empty()) {
cout << "Hamiltonian Cycle: ";
for (int v : cycle) {
cout << v + 1 << " "; // 恢复顶点编号
}
cout << time(0) - now << " " << G.cnt << endl;
} else {
cout << "No Hamiltonian Cycle found." << endl;
}
}
return 0;
}
-
提取邻接顶点:使用
neighbor_list
存储所有未访问的邻接顶点。 - 排序:根据每个顶点的未使用邻接点数量进行排序,优先处理邻接点较少的顶点。
-
遍历排序后的顶点:在
hamiltonian_cycle_util
中,遍历排序后的neighbor_list
,而不是直接遍历neighbors
。
- 减少回溯次数:通过优先处理邻接点较少的顶点,可以减少递归深度和回溯次数,从而提高算法效率。
- 代码清晰:将邻接顶点的提取和排序逻辑分离,使代码更易读和维护。
- 排序操作会增加一定的计算开销,但在大多数情况下,这种开销会被减少的回溯次数所抵消。
- 如果图的规模非常大(如
n > 100
),可能需要进一步优化或使用更高效的算法(如动态规划或启发式算法)。
因为最后一步时首个顶点要用它的邻接点回环,加一个判断首节点所有邻接点都已用就回溯,或者禁止在未到最后一步时使用首个顶点的最后一个邻接点,采取实现更方便的方法
为了优化哈密顿环的查找过程,特别是针对最后一步需要回到起点的情况,可以在递归过程中增加一些判断条件,避免在未到最后一步时使用起点的最后一个邻接点,从而减少不必要的回溯。以下是修改后的代码实现:
#include <iostream>
#include <vector>
#include <cmath>
#include <ctime>
#include <bitset>
#include <algorithm>
using namespace std;
const int MAX_N = 100; // 假设最大顶点数为 100
class Graph {
int V; // 顶点数
vector<bitset<MAX_N>> adj; // 邻接表(使用 bitset 表示)
public:
Graph(int V) : V(V), adj(V) {}
long long cnt = 0;
void add_edge(int u, int v) {
adj[u].set(v); // 设置 u 和 v 之间的边
adj[v].set(u); // 无向图,双向设置
}
bool hamiltonian_cycle_util(vector<int>& path, bitset<MAX_N>& visited, int pos, int start) {
if (pos == V) {
// 检查是否形成环
if (adj[path[pos - 1]].test(start)) {
return true;
}
return false;
}
// 获取当前顶点的邻接顶点 bitset
bitset<MAX_N> neighbors = adj[path[pos - 1]] & ~visited;
// 提取所有未访问的邻接顶点
vector<int> neighbor_list;
for (int v = neighbors._Find_first(); v < MAX_N; v = neighbors._Find_next(v)) {
neighbor_list.push_back(v);
}
// 按未使用的邻接点数量排序
sort(neighbor_list.begin(), neighbor_list.end(), [this, &visited](int a, int b) {
return (adj[a] & ~visited).count() < (adj[b] & ~visited).count();
});
// 遍历排序后的邻接顶点
for (int v : neighbor_list) {
// 如果未到最后一步,且 v 是起点的最后一个邻接点,则跳过
if (pos != V - 1 && v == start && adj[path[pos - 1]].count() == 1) {
continue;
}
cnt++;
path[pos] = v;
visited.set(v); // 标记为已访问
if (hamiltonian_cycle_util(path, visited, pos + 1, start)) {
return true;
}
visited.reset(v); // 回溯
}
return false;
}
vector<int> hamiltonian_cycle(int s) {
vector<int> path(V, -1);
bitset<MAX_N> visited; // 使用 bitset 存储访问状态
// 从顶点 s 开始
path[0] = s;
visited.set(path[0]);
if (hamiltonian_cycle_util(path, visited, 1, s)) {
return path;
}
return {};
}
};
int main() {
int n = 50;
Graph G(n);
// 构建图
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
int sum = i + j;
int sqrt_sum = sqrt(sum);
if (sqrt_sum * sqrt_sum == sum) {
G.add_edge(i - 1, j - 1); // 顶点编号从 0 开始
}
}
}
for (int s = 0; s < 3; s++) {
// 基于当前系统的当前日期/时间
time_t now = time(0);
G.cnt = 0;
// 查找哈密顿环
vector<int> cycle = G.hamiltonian_cycle(s);
if (!cycle.empty()) {
cout << "Hamiltonian Cycle: ";
for (int v : cycle) {
cout << v + 1 << " "; // 恢复顶点编号
}
cout << time(0) - now << " " << G.cnt << endl;
} else {
cout << "No Hamiltonian Cycle found." << endl;
}
}
return 0;
}
-
增加
start
参数:- 在
hamiltonian_cycle_util
函数中增加start
参数,用于记录起点。 - 在最后一步(
pos == V
)时,检查当前顶点是否可以回到起点。
- 在
-
跳过起点的最后一个邻接点:
- 在遍历邻接点时,如果未到最后一步(
pos != V - 1
),且当前邻接点是起点(v == start
),并且当前顶点的邻接点数量为 1(即起点的最后一个邻接点),则跳过该邻接点。 - 这样可以避免在未到最后一步时使用起点的最后一个邻接点,从而减少不必要的回溯。
- 在遍历邻接点时,如果未到最后一步(
-
排序优化:
- 仍然保留对邻接点按未使用邻接点数量排序的逻辑,以进一步优化搜索顺序。
- 减少回溯:通过跳过起点的最后一个邻接点,避免在未到最后一步时使用它,从而减少不必要的递归调用和回溯。
- 保持高效:排序逻辑仍然保留,确保优先处理邻接点较少的顶点,进一步优化搜索效率。
- 代码清晰:逻辑分离明确,易于理解和维护。
假设图中有哈密顿环,输出可能如下:
Hamiltonian Cycle: 1 3 6 10 15 21 28 36 45 5 14 23 34 46 9 20 32 44 8 19 31 43 7 18 30 42 12 24 37 50 13 25 38 49 11 22 35 47 16 27 39 48 17 29 40 2 4 26 41 0 1
Time: 2 seconds, Recursive calls: 123456
如果没有找到哈密顿环,输出如下:
No Hamiltonian Cycle found.
通过增加对起点最后一个邻接点的判断,可以有效减少不必要的回溯,同时保留排序优化逻辑,进一步提升算法效率。这种方法实现简单,且适用于大多数中小规模图的哈密顿环查找问题。
测试发现,不同的起点找到回环所用的递归调用次数cnt差异很大,n<90时,大多数都有一个起点cnt小于10万,请把hamiltonian_cycle()函数实现为内部带起点从0开始尝试的循环,一旦cnt大于10万就放弃这个起点,cnt归零测试下一个起点,一旦找到回环就跳出循环返回主程序,同时把主程序改为测试n从80到100这种思路的循环
为了实现您描述的功能,我们需要对 hamiltonian_cycle()
函数进行修改,使其内部带有一个循环,从起点 0
开始尝试,如果递归调用次数 cnt
超过 10 万,则放弃当前起点并尝试下一个起点。一旦找到哈密顿环,立即返回结果。同时,主程序将改为测试 n
从 80 到 100 的循环。
以下是修改后的代码:
#include <iostream>
#include <vector>
#include <cmath>
#include <ctime>
#include <bitset>
#include <algorithm>
using namespace std;
const int MAX_N = 100; // 假设最大顶点数为 100
class Graph {
int V; // 顶点数
vector<bitset<MAX_N>> adj; // 邻接表(使用 bitset 表示)
public:
Graph(int V) : V(V), adj(V) {}
long long cnt = 0;
void add_edge(int u, int v) {
adj[u].set(v); // 设置 u 和 v 之间的边
adj[v].set(u); // 无向图,双向设置
}
bool hamiltonian_cycle_util(vector<int>& path, bitset<MAX_N>& visited, int pos, int start) {
if (pos == V) {
// 检查是否形成环
if (adj[path[pos - 1]].test(start)) {
return true;
}
return false;
}
// 获取当前顶点的邻接顶点 bitset
bitset<MAX_N> neighbors = adj[path[pos - 1]] & ~visited;
// 提取所有未访问的邻接顶点
vector<int> neighbor_list;
for (int v = neighbors._Find_first(); v < MAX_N; v = neighbors._Find_next(v)) {
neighbor_list.push_back(v);
}
// 按未使用的邻接点数量排序
sort(neighbor_list.begin(), neighbor_list.end(), [this, &visited](int a, int b) {
return (adj[a] & ~visited).count() < (adj[b] & ~visited).count();
});
// 遍历排序后的邻接顶点
for (int v : neighbor_list) {
// 如果未到最后一步,且 v 是起点的最后一个邻接点,则跳过
if (pos != V - 1 && v == start && adj[path[pos - 1]].count() == 1) {
continue;
}
cnt++;
if (cnt > 100000) { // 如果递归调用次数超过 10 万,直接返回 false
return false;
}
path[pos] = v;
visited.set(v); // 标记为已访问
if (hamiltonian_cycle_util(path, visited, pos + 1, start)) {
return true;
}
visited.reset(v); // 回溯
}
return false;
}
vector<int> hamiltonian_cycle() {
vector<int> path(V, -1);
bitset<MAX_N> visited; // 使用 bitset 存储访问状态
// 尝试从每个起点开始
for (int s = 0; s < V; s++) {
cnt = 0; // 重置递归调用次数
path.assign(V, -1); // 重置路径
visited.reset(); // 重置访问状态
// 从顶点 s 开始
path[0] = s;
visited.set(s);
if (hamiltonian_cycle_util(path, visited, 1, s)) {
return path;
}
}
return {};
}
};
int main() {
// 测试 n 从 80 到 100
for (int n = 80; n <= 100; n++) {
Graph G(n);
// 构建图
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
int sum = i + j;
int sqrt_sum = sqrt(sum);
if (sqrt_sum * sqrt_sum == sum) {
G.add_edge(i - 1, j - 1); // 顶点编号从 0 开始
}
}
}
// 基于当前系统的当前日期/时间
time_t now = time(0);
// 查找哈密顿环
vector<int> cycle = G.hamiltonian_cycle();
if (!cycle.empty()) {
cout << "n = " << n << ": Hamiltonian Cycle found. Recursive calls: " << G.cnt << ", Time: " << time(0) - now << " seconds." << endl;
cout << "Cycle: ";
for (int v : cycle) {
cout << v + 1 << " "; // 恢复顶点编号
}
cout << endl;
} else {
cout << "n = " << n << ": No Hamiltonian Cycle found. Recursive calls: " << G.cnt << ", Time: " << time(0) - now << " seconds." << endl;
}
}
return 0;
}
-
hamiltonian_cycle()
函数:- 内部增加一个循环,从起点
0
开始尝试。 - 如果递归调用次数
cnt
超过 10 万,则放弃当前起点并尝试下一个起点。 - 一旦找到哈密顿环,立即返回结果。
- 内部增加一个循环,从起点
-
主程序:
- 改为测试
n
从 80 到 100 的循环。 - 对于每个
n
,构建图并调用hamiltonian_cycle()
查找哈密顿环。 - 输出结果,包括递归调用次数和运行时间。
- 改为测试
-
递归调用次数限制:
- 在
hamiltonian_cycle_util()
中,每次递归调用时检查cnt
是否超过 10 万,如果超过则直接返回false
。
- 在
假设对于某个 n
,程序找到哈密顿环,输出可能如下:
n = 80: Hamiltonian Cycle found. Recursive calls: 87654, Time: 3 seconds.
Cycle: 1 3 6 10 15 21 28 36 45 5 14 23 34 46 9 20 32 44 8 19 31 43 7 18 30 42 12 24 37 50 13 25 38 49 11 22 35 47 16 27 39 48 17 29 40 2 4 26 41 0 1
如果没有找到哈密顿环,输出如下:
n = 85: No Hamiltonian Cycle found. Recursive calls: 100001, Time: 5 seconds.
通过修改 hamiltonian_cycle()
函数,使其内部尝试多个起点,并在递归调用次数超过 10 万时放弃当前起点,可以显著提高算法的效率。同时,主程序改为测试 n
从 80 到 100 的循环,方便批量测试和分析结果。
非常好,n到500都能很快算出。谢谢!
不客气!很高兴听到这个优化对您的问题有效,并且能够处理更大的规模(如 n = 500
)。这说明我们的优化策略(如限制递归调用次数、动态调整起点、排序邻接点等)确实显著提升了算法的效率。
如果您有其他问题或需要进一步的优化建议,随时可以联系我!以下是一些可能的进一步优化方向,供您参考:
-
并行化:
- 如果硬件支持,可以将不同起点的搜索任务并行化(如使用多线程或 GPU 加速)。
- 每个线程负责一个起点的搜索,独立计算,最终汇总结果。
-
启发式剪枝:
- 在递归过程中,可以引入更多的启发式规则(如基于度数、连通性等)来提前剪枝,减少不必要的递归调用。
-
动态调整递归深度限制:
- 根据当前递归深度动态调整
cnt
的限制。例如,在较浅的递归层允许更多的调用次数,而在较深的递归层减少调用次数。
- 根据当前递归深度动态调整
-
混合算法:
- 结合其他算法(如动态规划、随机化算法或启发式搜索)来处理更大规模的图。
-
图的性质分析:
- 在实际应用中,如果图具有某些特殊性质(如稀疏性、对称性等),可以利用这些性质进一步优化算法。
如果您有兴趣尝试并行化,以下是一个简单的伪代码示例:
#include <thread>
#include <vector>
vector<int> parallel_hamiltonian_cycle(Graph& G) {
vector<int> result;
vector<thread> threads;
bool found = false;
for (int s = 0; s < G.V; s++) {
threads.emplace_back([&G, s, &result, &found]() {
if (found) return; // 如果其他线程已经找到结果,直接返回
auto cycle = G.hamiltonian_cycle(s);
if (!cycle.empty()) {
result = cycle;
found = true; // 设置标志位,通知其他线程
}
});
}
for (auto& t : threads) {
t.join();
}
return result;
}
您的反馈非常有价值!如果您在未来遇到类似的问题或需要进一步的帮助,请随时联系我。祝您在算法优化和应用中取得更多成功! 🚀