骑士巡游问题,怎样快速出解

058.走遍全世界

在国际象棋中,“骑士”这颗棋子的走法很奇特,它只能往前后左右移动一格,然后再往斜向移动一格。
你能用“骑士”在8×8的国际象棋棋盘(见图2-2)上,将每一格都恰好走过,不许重复,也不遗漏,然后再回到出发点吗?该怎么走?
图2-2 国际象棋中“骑士”的走法在这里插入图片描述
其实就是“骑士巡游”问题,骑士走法就是马走日的步法。下面是一个解决方案,采用多线程+Warnsdorff启发式算法+位掩码,迅速枚举出解,每秒差不多出一万解。可在后台运行,想枚举出所有,是不可能的,没有那么大的存储,估摸着能枚举出上千万的解,顶天一亿多。默认起(0,0),可以在命令行输入起点坐标。

/*
 *  用法:gcc -O3 -pthread knigt_tour.c -o knight && ./knight [start_x start_y]
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>

#define N 8                     /* 棋盘尺寸,可改为 6,10,12… */
#define NN (N*N)

/* 8 个马步方向 */
static const int dx[8] = { 2, 1,-1,-2,-2,-1, 1, 2 };
static const int dy[8] = { 1, 2, 2, 1,-1,-2,-2,-1 };

/* ================= 全局进度相关 ================= */
static volatile uint64_t g_nodes      = 0;   /* 已访问节点数 */
static volatile uint64_t g_total_sols = 0;   /* 已得解数 */
static volatile bool     g_progress_stop = false;

/* 原子递增封装 */
static inline void bump_node(void) {
    __atomic_add_fetch(&g_nodes, 1, __ATOMIC_RELAXED);
}
static inline void bump_sol(void) {
    __atomic_add_fetch(&g_total_sols, 1, __ATOMIC_RELAXED);
}

/* 进度线程:每秒 stderr 刷新 */
static void *progress_thread(void *dummy) {
    uint64_t last_n = 0, last_s = 0;
    time_t start = time(NULL);
    while (!g_progress_stop) {
        sleep(1);
        uint64_t n = g_nodes;
        uint64_t s = g_total_sols;
        double spd = (double)(n - last_n);
        fprintf(stderr, "\r节点 %llu  解 %llu  速度 %.0f 节点/s  已用 %ld s",
                (unsigned long long)n, (unsigned long long)s, spd, time(NULL)-start);
        last_n = n; last_s = s;
    }
    return NULL;
}
/* ============================================ */

/* 线程私有数据 */
typedef struct {
    int  tid;               /* 线程编号 = 第一跳方向 */
    FILE *fp;               /* 输出文件句柄 */
    uint64_t cnt;           /* 本线程解数 */
    char buf[1024*1024];    /* 1 MB 缓冲,减少 fwrite 调用 */
    int  buf_used;
    int  start_sq;          /* 用户指定的起点格子编号 */
} worker_t;

static worker_t g_workers[8];

/* 缓冲刷盘 */
static void flush_buf(worker_t *w) {
    if (w->buf_used) {
        fwrite(w->buf, 1, w->buf_used, w->fp);
        w->buf_used = 0;
    }
}

/* 把一条完整回路写入缓冲 */
static void push_solution(worker_t *w, const int path[NN]) {
    bump_sol();                      /* 全局解数++ */
    char tmp[NN*8 + 2];
    int  len = 0;
    for (int i = 0; i < NN; ++i)
        len += sprintf(tmp + len, "(%d,%d)", path[i] / N, path[i] % N);
    tmp[len++] = '\n';
    if (w->buf_used + len > sizeof(w->buf)) flush_buf(w);
    memcpy(w->buf + w->buf_used, tmp, len);
    w->buf_used += len;
    w->cnt++;
}

/* DFS + 位掩码 + Warnsdorff */
static void dfs(int sq, int step, uint64_t mask, int path[NN], worker_t *w) {
    bump_node();                     /* 每进入一次 = 一个节点 */
    path[step] = sq;

    /* 到达最后一步,检查能否跳回起点形成闭链 */
    if (step == NN - 1) {
        int start_sq = w->start_sq;
        int x0 = start_sq / N, y0 = start_sq % N;
        int x1 = sq / N, y1 = sq % N;
        int dx0 = abs(x0 - x1), dy0 = abs(y0 - y1);
        if ((dx0 == 1 && dy0 == 2) || (dx0 == 2 && dy0 == 1))
            push_solution(w, path);
        return;
    }

    uint64_t next_mask = mask | (1ULL << sq);
    int x = sq / N, y = sq % N;
    int cand[8], nc = 0;

    /* 生成所有可行下一步 */
    for (int d = 0; d < 8; ++d) {
        int nx = x + dx[d], ny = y + dy[d];
        if (nx < 0 || nx >= N || ny < 0 || ny >= N) continue;
        int ns = nx * N + ny;
        if (mask & (1ULL << ns)) continue;
        cand[nc++] = ns;
    }

    /* Warnsdorff:优先扩展“后续选择最少”的节点 */
    for (int i = 0; i < nc; ++i) {
        int min_deg = 99, best = -1;
        for (int j = i; j < nc; ++j) {
            int s = cand[j], dg = 0;
            int sx = s / N, sy = s % N;
            for (int d = 0; d < 8; ++d) {
                int nx = sx + dx[d], ny = sy + dy[d];
                if (nx < 0 || nx >= N || ny < 0 || ny >= N) continue;
                int ns2 = nx * N + ny;
                if (next_mask & (1ULL << ns2)) continue;
                dg++;
            }
            if (dg < min_deg) { min_deg = dg; best = j; }
        }
        /* 交换到当前位置 */
        int t = cand[i]; cand[i] = cand[best]; cand[best] = t;
        dfs(cand[i], step + 1, next_mask, path, w);
    }
}

/* 每个 DFS 线程的入口 */
static void *thread_entry(void *arg) {
    worker_t *w = (worker_t *)arg;
    int path[NN];
    int start_sq = w->start_sq;
    uint64_t mask = 1ULL << start_sq;
    path[0] = start_sq;

    /* 本线程只负责“第一跳”方向 = tid */
    int x = start_sq / N, y = start_sq % N;
    int d = w->tid;
    int nx = x + dx[d], ny = y + dy[d];
    if (nx < 0 || nx >= N || ny < 0 || ny >= N) return NULL;
    int ns = nx * N + ny;
    dfs(ns, 1, mask | (1ULL << ns), path, w);

    flush_buf(w);          /* 线程结束把剩余缓冲写盘 */
    return NULL;
}

int main(int argc, char *argv[]) {
    /* 解析起点 */
    int start_x = 0, start_y = 0;
    if (argc >= 3) { start_x = atoi(argv[1]); start_y = atoi(argv[2]); }
    if (start_x < 0 || start_x >= N || start_y < 0 || start_y >= N) {
        fprintf(stderr, "Invalid start position (%d,%d)\n", start_x, start_y);
        return 1;
    }
    int start_sq = start_x * N + start_y;
    printf("Searching closed tours from (%d,%d) ...\n", start_x, start_y);

    FILE *fp = fopen("knight_tour_closed.txt", "w");
    if (!fp) { perror("fopen"); return 1; }

    /* 启动进度线程 */
    pthread_t prog_tid;
    pthread_create(&prog_tid, NULL, progress_thread, NULL);

    /* 启动 8 个 DFS 线程 */
    pthread_t tids[8];
    for (int i = 0; i < 8; ++i) {
        worker_t *w = &g_workers[i];
        w->tid = i; w->fp = fp; w->cnt = 0; w->buf_used = 0; w->start_sq = start_sq;
        pthread_create(&tids[i], NULL, thread_entry, w);
    }
    for (int i = 0; i < 8; ++i) pthread_join(tids[i], NULL);

    g_progress_stop = true;                 /* 通知进度线程退出 */
    pthread_join(prog_tid, NULL);
    putchar('\n');                          /* 换行覆盖进度条 */

    uint64_t total = 0;
    for (int i = 0; i < 8; ++i) total += g_workers[i].cnt;
    printf("Total closed tours from (%d,%d): %llu\n", start_x, start_y, (unsigned long long)total);
    fclose(fp);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值