Files
algo2025/horse_travel/main.go
2025-12-18 16:00:22 +08:00

150 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 声明包为 main表示这是一个可执行程序
package main
import (
"fmt" // 导入 fmt 包,用于格式化输入输出
"time" // 导入 time 包,用于计算程序运行时间
)
// 定义常量 N代表棋盘的大小为 8x8
const N = 8
var (
// dx 和 dy 数组定义了马在棋盘上可以移动的8个方向的 x 和 y 坐标变化
// 例如,(dx[0], dy[0]) = (2, 1) 表示马可以从 (x, y) 移动到 (x+2, y+1)
dx = []int{2, 1, -1, -2, -2, -1, 1, 2}
dy = []int{1, 2, 2, 1, -1, -2, -2, -1}
// stepCount 用于记录算法关键步骤的执行次数,主要用于性能分析
stepCount int64
)
// isValid 函数检查给定坐标 (x, y) 是否在棋盘内,并且该位置尚未被访问过
// board[x][y] == -1 表示该位置未被访问
func isValid(x, y int, board [][]int) bool {
stepCount++ // 每次检查都增加关键步骤计数
return x >= 0 && x < N && y >= 0 && y < N && board[x][y] == -1
}
// getDegree 函数计算从 (x, y) 位置出发,有多少个可行的下一步(即“出度”
// 这是 Warnsdorff 规则的核心,用于优化路径选择
func getDegree(x, y int, board [][]int) int {
count := 0
// 遍历8个可能的移动方向
for i := 0; i < 8; i++ {
// 如果移动后的位置是有效的,则计数器加一
if isValid(x+dx[i], y+dy[i], board) {
count++
}
}
return count
}
// solveKnightTour 是解决马踏棋盘问题的主要递归函数
// x, y: 当前马的位置
// moveCount: 当前是第几步
// board: 棋盘状态
func solveKnightTour(x, y, moveCount int, board [][]int) bool {
// 将当前位置标记为第 moveCount 步
board[x][y] = moveCount
// 如果已经走满了 N*N-1 步步数从0开始说明已经找到了一个完整的路径
if moveCount == N*N-1 {
return true // 返回 true 表示成功
}
// 定义一个结构体 Move用于存储下一步的位置和该位置的“出度”
type Move struct {
x, y, degree int
}
// 创建一个切片,用于存储所有可能的下一步
moves := make([]Move, 0, 8)
// 遍历8个方向找出所有有效的下一步
for i := 0; i < 8; i++ {
nx, ny := x+dx[i], y+dy[i]
if isValid(nx, ny, board) {
// 如果是有效的一步,计算该点的“出度”并存入 moves 切片
degree := getDegree(nx, ny, board)
moves = append(moves, Move{nx, ny, degree})
}
}
// 使用选择排序对所有可能的下一步进行排序,按照“出度”从小到大排序
// 这是 Warnsdorff 规则的应用:优先选择“出度”最少的点,这样可以减少后面出现“死路”的可能性
for i := 0; i < len(moves); i++ {
for j := i + 1; j < len(moves); j++ {
if moves[j].degree < moves[i].degree {
moves[i], moves[j] = moves[j], moves[i]
}
}
}
// 按照排序后的顺序,依次尝试每一个可能的下一步
for _, move := range moves {
// 递归调用 solveKnightTour进入下一步
if solveKnightTour(move.x, move.y, moveCount+1, board) {
return true // 如果递归返回 true说明找到了解直接返回 true
}
}
// 如果所有可能的下一步都无法导致一个成功的解,则进行“回溯”
// 将当前位置重置为未访问状态 (-1),并返回 false
board[x][y] = -1
return false
}
// printBoard 函数用于打印最终的棋盘路径
func printBoard(board [][]int) {
fmt.Println("\n马踏棋盘路径:")
for i := range N {
for j := range N {
// 使用 %3d 格式化输出,使棋盘对齐
fmt.Printf("%3d", board[i][j])
}
fmt.Println() // 每行结束后换行
}
}
// main 函数是程序的入口
func main() {
var startX, startY int
// 提示用户输入起始位置
fmt.Print("请输入起始位置 (x y, 范围0-7): ")
// 读取用户输入的坐标
fmt.Scanf("%d %d", &startX, &startY)
// 检查输入的坐标是否在有效范围内
if startX < 0 || startX >= N || startY < 0 || startY >= N {
fmt.Println("输入位置无效!")
return // 如果无效,程序退出
}
// 初始化棋盘,创建一个 N*N 的二维切片
board := make([][]int, N)
for i := range board {
board[i] = make([]int, N)
// 将棋盘所有位置初始化为 -1表示都未被访问
for j := range board[i] {
board[i][j] = -1
}
}
// 重置关键步骤计数器
stepCount = 0
// 记录算法开始时间
start := time.Now()
// 调用 solveKnightTour 函数开始求解
if solveKnightTour(startX, startY, 0, board) {
// 如果求解成功
elapsed := time.Since(start) // 计算总耗时
printBoard(board) // 打印棋盘
fmt.Printf("\n求解成功!\n")
fmt.Printf("运行时间: %v\n", elapsed)
fmt.Printf("关键步骤执行次数: %d\n", stepCount)
} else {
// 如果求解失败
fmt.Println("无解!")
}
}