Go语言数据结构和算法(三十七)回溯算法

23 阅读4分钟

回溯算法是一种递归解决问题的技术.它尝试逐步构建一个解决方案.一次一个.删除那

些在任何时间点都不能满足问题约束的解决方案.回溯算法通常用于解决各种计算问

题.特别是优化组合问题.

1.步骤:

如果当前节点是一个可行的解决方案.则返回true.

如果所有路径都已经穷尽(即当前节点为端点).则返回false.因为没有可行解.

如果当前点不是端点.则回溯探索其他点.然后重复上述步骤.

2.常见算法:

2.1N皇后问题:

将n个皇后放在nn的棋盘上.使得没有两个皇后之间相互存在威胁.*

2.2数独求解器:

给定一个不完整的数度问题.用数字填充单元格.使每一行 每一列和33的子网格包含*

1~9的数字.

2.3哈密顿循环:

给定一个图.找到一个每个顶点恰好访问一次的循环.

3.N皇后问题:

N皇后问题是指在nn的棋盘上放置n个皇后.使两个皇后之间不会相互攻击的问题.给*

定整数n.返回N皇后问题的所有不同解.

4.N皇后应用场景:

4.1基准算法:

N皇后问题经常被用作基准问题来评估各种搜索算法和启发算法的效率和有效性.研

究人员可以使用该问题来测试和比较不同算法在解决组合问题时的性能.

4.2游戏开发:

在游戏中用于开发智能游戏代理.通过解决N皇后问题.智能体可以学习和应用不同搜

索算法和启发式算法.以在国际象棋 西洋跳棋和围棋等游戏做出最佳动作.

4.3教育:

N皇后问题经常用于计算机科学和数学教育.以教授解决问题的技术和算法.用于介绍

回溯 搜索算法和启发式算法概念.

5.实战:

5.1方法:

package data

import (
	"fmt"
	"math"
)

type Point struct {
	x int
	y int
}

// 二维数组对象.
var results = make([][]Point, 0)

// 找到N皇后问题的所有解.
func NQueenSolve(n int) {
	//遍历每一列.
	for col := 0; col < n; col++ {
		//为当前列创建一个起点.
		start := Point{x: col, y: 0}
		//创建一个空切片来保存当前解决方案.
		current := make([]Point, 0)
		//从当前列开始递归解决问题.
		Recurse(start, current, n)
	}
	//打印结果.
	fmt.Println("结果:\n")
	for _, result := range results {
		fmt.Println(result)
	}
	//打印找到的解决方案.
	fmt.Printf("一共有%d 种解决方法\n", len(results))
}

// 从指定节点开始递归求解N皇后问题.
func Recurse(point Point, current []Point, n int) {
	//如果当前点是有效位置.则将其添加到当前解决方案.
	if CanPlace(point, current) {
		current = append(current, point)
		//如果当前解包含n个点.则将其添加到结果列表.
		if len(current) == n {
			c := make([]Point, n)
			for i, point := range current {
				c[i] = point
			}
			results = append(results, c)
		} else {
			//否则.从下一行的每个点开始递归求解.
			for col := 0; col < n; col++ {
				for row := point.y; row < n; row++ {
					nextStart := Point{x: col, y: row}
					Recurse(nextStart, current, n)
				}
			}
		}
	}
}

// 确定是否可以在不攻击任何其他棋子的情况下将目标点放在棋盘上.
func CanPlace(target Point, board []Point) bool {
	for _, point := range board {
		if CanAttack(point, target) {
			return false
		}
	}
	return true
}

// 判断棋盘上的两点是否可以相互攻击.
func CanAttack(a, b Point) bool {
	//如果两个点在同一行. 同一列或对角线上.则它们可以互相攻击.
	answer := a.x == b.x || a.y == b.y ||
		math.Abs(float64(a.y-b.y)) == math.Abs(float64(a.x-b.x))
	return answer
}

5.2main方法:

package main

import (
	"gomodule/data"
	_ "gomodule/pubsub"
)

func main() {
	data.NQueenSolve(4)
}

6.实战:

6.1方法:

func SolveNQueens(n int) [][]string {
	var result [][]string
	var path [][]byte

	//创建三个布尔数组已检查给定位置是否被占用.
	col := make([]bool, n)
	diag1 := make([]bool, n<<1-1)
	diag2 := make([]bool, n<<1-1)
	helperPoint(n, 0, col, diag1, diag2, path, &result)
	return result
}

// 辅助函数是一个递归函数.生成N皇后问题的所有可能解.
func helperPoint(n int, row int, col []bool, diag1 []bool, diag2 []bool, path [][]byte, result *[][]string) {
	//所有行都已填充.则将解决方案添加到结果切片.
	if row >= n {
		var elem []string
		for i := 0; i < n; i++ {
			elem = append(elem, string(path[i]))
		}
		*result = append(*result, elem)
		return
	}

	//对于每一行.检查所有可能的位置以放置皇后并递归调用辅助函数.
	for i := 0; i < n; i++ {
		//如果列或对角线被占用.跳到下一个位置.
		if col[i] || diag1[row+i] || diag2[row-i+n-1] {
			continue
		}
		//在第i列中创建一个带有Q和.的行.
		line := make([]byte, n)
		for j := 0; j < n; j++ {
			line[j] = '.'
		}
		line[i] = 'Q'
		//通过将行附加到路径来创建新的路径
		newPath := append(append([][]byte{}, path...), line)
		//将位置标记位已占用.
		col[i] = true
		diag1[row+i] = true
		diag2[row-i+n-1] = true
		//使用更新后的路径递归调用辅助函数.
		helperPoint(n, row+1, col, diag1, diag2, newPath, result)
		//通过将位置标记为占用回溯.
		col[i] = false
		diag1[row+i] = false
		diag2[row-i+n-1] = false
	}
}

6.2main方法:

package main

import (
	"fmt"
	"gomodule/data"
	_ "gomodule/pubsub"
)

func main() {
	queens := data.SolveNQueens(4)
	fmt.Println(queens)
}

揣着敬畏揣测.

如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路