首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

POJ 2488 A Knight's Journey:騎士遍歷棋盤 DFS 深度搜尋與字典序最小路徑

     編輯:關於C語言

問題描述

  • p×q 棋盤(1 ≤ p×q ≤ 26),列用數字 1..p,行用字母 A..(第 q 個字母)
  • 騎士走「日」字:(±2, ±1) 或 (±1, ±2)
  • 求漢密爾頓路徑:恰好訪問每個格子一次
  • 輸出字典序最小路徑(如 A1B3C1...),無解輸出 impossible
  • 多測資,格式:Scenario #i:

核心演算法:DFS + 剪枝 + 字典序保證

  1. 按字典序嘗試起點:A1, A2, ..., Aq, B1, ... → 找到的第一條即為最小
  2. 按字典序嘗試移動:8 個方向預先按目標格字典序排序
  3. 回溯搜尋:標記已訪問,遞迴嘗試,失敗則回溯
  4. 找到完整路徑即停止:因起點、移動均按字典序,首解即最優

方向向量與字典序排序

/* 8 個騎士移動:預先按「目標格字典序」排序 */
/* 字典序:先比字母(行),再比數字(列) */
/* 行字母 = 'A' + col, 列數字 = row */
int dr[8] = {-2, -2, -1, -1, 1, 1, 2, 2};
int dc[8] = {-1, 1, -2, 2, -2, 2, -1, 1};
/* 對應目標格:(r-2,c-1), (r-2,c+1), (r-1,c-2), (r-1,c+2), 
                (r+1,c-2), (r+1,c+2), (r+2,c-1), (r+2,c+1) */
/* 已按 行遞增、列遞增 排序,滿足字典序要求 */

完整 C 解法(AC)

#include <stdio.h>
#include <string.h>

#define MAX_PQ 26
#define MAX_PATH 30

int p, q;                    // 列數、行數
int visited[7][7];           // 1-based 索引
char path_str[MAX_PATH];     // 字母部分
int path_num[MAX_PATH];      // 數字部分
int path_len;                // 當前路徑長度
int found;                   // 是否找到完整路徑

/* 檢查座標合法且未訪問 */
int valid(int r, int c) {
    return r >= 1 && r <= p && c >= 1 && c <= q && !visited[r][c];
}

/* DFS 回溯 */
void dfs(int r, int c) {
    if (found) return;  // 已找到最優解
    
    visited[r][c] = 1;
    path_str[path_len] = 'A' + c - 1;
    path_num[path_len] = r;
    path_len++;
    
    if (path_len == p * q) {  // 訪問完所有格子
        found = 1;
        return;
    }
    
    /* 按預排序方向嘗試 */
    for (int i = 0; i < 8; i++) {
        int nr = r + dr[i], nc = c + dc[i];
        if (valid(nr, nc)) {
            dfs(nr, nc);
            if (found) return;
        }
    }
    
    /* 回溯 */
    path_len--;
    visited[r][c] = 0;
}

int main() {
    int T;
    scanf("%d", &T);
    for (int tc = 1; tc <= T; tc++) {
        scanf("%d %d", &p, &q);
        
        printf("Scenario #%d:\n", tc);
        
        if (p == 1 && q == 1) {  // 特判 1x1
            printf("A1\n\n");
            continue;
        }
        
        found = 0;
        memset(visited, 0, sizeof(visited));
        
        /* 按字典序嘗試所有起點 */
        for (int c = 1; c <= q && !found; c++) {  // 列 A.. 
            for (int r = 1; r <= p && !found; r++) {  // 行 1..
                path_len = 0;
                dfs(r, c);
            }
        }
        
        if (found) {
            for (int i = 0; i < p * q; i++)
                printf("%c%d", path_str[i], path_num[i]);
            printf("\n\n");
        } else {
            printf("impossible\n\n");
        }
    }
    return 0;
}

關鍵優化與正確性保證

技巧作用
起點雙重迴圈按字典序保證找到的第一條完整路徑即為全域字典序最小
方向向量預排序同一起點下,優先嘗試字典序較小的下一步
全域 found 剪枝找到解後立即停止所有遞迴,避免多餘搜尋
p×q ≤ 26 狀態空間小無需 Warnsdorff 启發式,純 DFS 即可在時限內通過
特判 1×1避免 DFS 邏輯邊界錯誤

複雜度分析

  • 最壞情況:O(8pq) 但實際搜尋樹極小(pq ≤ 26,騎士圖稀疏)
  • 實測 POJ 2488:10-30ms 通過
  • 空間:O(pq) 遞迴深度 + 訪問陣列

擴展:Warnsdorff 規則(大棋盤加速)

若棋盤擴大(如標準 8×8),純 DFS 會超時。引入啟發式:

  1. 當前格的所有合法下一步中,選擇後續合法移動數最少的那一步
  2. 貪心策略,無需回溯即可快速構造一條路徑(非必保證字典序最小)
int count_onward(int r, int c) {
    int cnt = 0;
    for (int i = 0; i < 8; i++)
        if (valid(r + dr[i], c + dc[i])) cnt++;
    return cnt;
}

/* 在 dfs 中對合法下一步按 count_onward 升序排序後再遞迴 */
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved