GDPU 竞赛技能实践 天码行空4-二、实验内容:

时间:2024-03-28 11:40:28

1.八数码问题

在一个3×3的棋盘上,放置编号为1~8的8个方块,每个占一格,另外还有一个空格。与空格相邻的数字方块可以移动到空格里。

任务1:指定初始棋局和目标棋局,计算出最少的移动步数;

任务2:输出数码的移动序列。
在这里插入图片描述

输入案例
二维棋盘压缩成一维,输入的 x 表示空格
第一行输入初始棋盘,第二行输入目标前

23415x768
12345678x

输出案例

空格移动的步骤:上左左下下右上右下左左上右右下左上右下
空格移动的步数为:19

???? BFS

Cpp
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <unordered_map>

using namespace std;

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
string li[4] = {"上", "右", "下", "左"};

int bfs(string s,string end)
{
    queue <string> q;
    unordered_map<string, int> dist;
    unordered_map<string, string> list;
    dist[s] = 0;
    list[s] = "";
    q.push(s);
    while (q.size())
    {
        string t = q.front();
        q.pop();
        int dis = dist[t];
        string lis = list[t];
        if (t == end) 
        {
            puts(lis.c_str());
            return dis;
        }
        int k = t.find('x');
        int x = k / 3, y = k % 3;
        for (int i = 0; i < 4; i ++ )
        {
            int a = x + dx[i], b = y + dy[i];
            if (0 <= a && a < 3 && 0 <= b && b < 3) 
            {
                swap(t[k], t[a * 3 + b]);
                if (!dist.count(t)) 
                {
                    dist[t] = dis + 1, q.push(t);
                    list[t] = lis + li[i];
                }
                swap(t[k], t[a * 3 + b]);
            }
        }
    }
    puts("无解!!");
    return -1;
}

int main()
{
    string start,end;
    cin >> start >> end;
    // for (int i = 1; i <= 9; i ++ )
    // {
    //     string s;
    //     cin >> s;
    //     strat += s;
    // }
    int steps = bfs(start,end);
    if(steps != -1)
        cout << "总步数为:" << steps;
    return 0;
}


Java
import java.util.*;

public class 八数码
{
//	位移数组
	static int[] dx = { -1, 0, 1, 0 };
	static int[] dy = { 0, 1, 0, -1 };
	static String[] move = { "上", "右", "下", "左" };
	static String end;// 终点

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		String start = sc.next();
		end = sc.next();
		int steps = bfs(start);
		if (steps != -1)
			System.out.println("空格移动的步数为:" + steps);
	}

	private static int bfs(String start)
	{
		LinkedList<String> q = new LinkedList<>();// BFS当前层的队列
		HashMap<String, Integer> dist = new HashMap<>();// 步骤数
		HashMap<String, String> list = new HashMap<>();// 存移动的历史记录
		dist.put(start, 0);
		list.put(start, "");
		q.add(start);
		while (!q.isEmpty())
		{
			String t = q.poll();
			int steps = dist.get(t);// 当前的步骤数
			String path = list.get(t);// 移动的历史记录
			if (end.equals(t))// 说明到达终点
			{
				System.out.println("空格移动的步骤:" + path);
				return steps;
			}
			int k = t.indexOf('x');// 找到当前的空格位置
			int x = k / 3;
			int y = k % 3;
			for (int i = 0; i < 4; i++)
			{
				int xx = x + dx[i];// 要移动到的横坐标
				int yy = y + dy[i];// 要移动到的纵坐标
				if (xx >= 0 && xx < 3 && yy >= 0 && yy < 3)
				{
					String newStr = swap(t, k, xx * 3 + yy);// 移动
					if (!dist.containsKey(newStr))// 去重
					{
						dist.put(newStr, steps + 1);// 步数 + 1
//						q.push(newStr); //错误示例:将元素加入队首
						q.add(newStr);// 将元素加入队尾
						list.put(newStr, path + move[i]);// 记录操作步骤
					}
				}
			}
		}
		System.out.println("无解!");
		return -1;
	}

	/**
	 * 交换字符串 s 中 a,b下标的字符,并返回交换后的字符串
	 */
	private static String swap(String s, int a, int b)
	{
		char[] ss = s.toCharArray();
		char t = ss[a];
		ss[a] = ss[b];
		ss[b] = t;
		return String.valueOf(ss);
	}
}

???? 双向BFS

import java.util.*;

public class 八数码plus
{
	static class Pair
	{
		int d;// 当前点到终点估计的距离
		String sta;// 存当前的状态

		public Pair(int d, String sta)
		{
			super();
			this.d = d;
			this.sta = sta;
		}
	}

	static class Pair2
	{
		String s;
		String c;

		public Pair2(String s, String c)
		{
			super();
			this.s = s;
			this.c = c;
		}
	}

//	返回 状态m 中 1-8 中每个数 到 终点的 位置 的哈夫曼距离 之和
	static int f(String m)
	{
		int dt = 0;
		for (int i = 0; i < 9; i++)// 枚举当前状态的每个坑位对应的数
			if (m.charAt(i) != 'x')//
			{
				int t = m.charAt(i) - '1';// t 当前坑位上的数
//				t 的 终点位置 (t/3,t%3), t 的当前位置(i/3,i%3),求哈夫曼距离
				dt = dt + Math.abs(i / 3 - t / 3) + Math.abs(i % 3 - t % 3);
			}
		return dt;
	}

//	交换 x 中 a,b两个位置上的字符
	static String swap(String s, int a, int b)
	{
		char[] cc = s.toCharArray();
		cc[a] = s.charAt(b);
		cc[b] = s.charAt(a);
		String res = String.valueOf(cc);
		return res;
	}

	static String bfs(String start, String end)
	{
		HashMap<String, Integer> d = new HashMap<>();// 存距离<状态,距离>
//		小根堆实现 A星 BFS
		PriorityQueue<Pair> heap = new PriorityQueue<>((o1, o2) -> o1.d - o2.d);
		// 存路径(当前状态的上一状态 + 操作类型)
		HashMap<String, Pair2> pre = new HashMap<>();
//		char[] op = { 'u', 'd', 'l', 'r' };// 四种交换操作
		String[] op = { "上", "下", "左", "右" };// 四种交换操作
//		四个方向,必须与四种交换操作一一对应
		int[] dx = { -1, 1, 0, 0 };
		int[] dy = { 0, 0, -1, 1 };

		heap.add(new Pair(f(start), start));// 起点进堆
		d.put(start, 0);// 距离初始化为 0
		while (!heap.isEmpty())
		{
			// 取出当前距离最近的点,出堆的点距离已经确定为最优解
			Pair t = heap.poll();
			String sta = t.sta;// 当前状态
			if (sta.equals(end))// 终点出队就退出
				break;
			int x = 0, y = 0;// 记录 x 的坐标
			for (int i = 0; i < 9; i++)
				if (sta.charAt(i) == 'x')
				{
					x = i / 3;
					y = i % 3;
					break;
				}
//			宽搜四种交换方案
			for (int i = 0; i < 4; i++)
			{
//				xx,yy求新的 x 的坐标
				int xx = x + dx[i];
				int yy = y + dy[i];
				if (xx < 0 || xx > 2 || yy < 0 || yy > 2)// 非法越界
					continue;
				String newsta = swap(sta, xx * 3 + yy, x * 3 + y);// 交换后的状态
//					新状态以前没有搜到过     ||  之前的距离大于当前状态 sta 交换到 newsta 的距离       
				if (!d.containsKey(newsta) || d.get(newsta) > d.get(sta) + 1)
				{
					d.put(newsta, d.get(sta) + 1);// 更新当前距离
//					把 起点到 newsta的距离 + 预测newsta到终点的距离 和当前状态放入堆中
					heap.add(new Pair(f(newsta) + d.get(newsta), newsta));
//					记录 newsta 的上一个 转移状态 和 对应的交换操作
					pre.put(newsta, new Pair2(sta, op[i]));
				}
			}
		}

		String ans = "";
		while (end != start)
		{
			ans += pre.get(end).c;
			end = pre.get(end).s;
		}
		ans = new StringBuffer(ans).reverse().toString();
		return ans;
	}

	public static void main(String[] args)
	{
		Scanner sc = new Scanner(System.in);
		String start = sc.next();
		String end = sc.next();
//		String end = "12345678x";// 表示终点

		String step = bfs(start, end);
		System.out.println("需要移动的步数为:" + step.length() + "\n空格移动序列为:" + step);
	}
}

2.八皇后问题

在棋盘上放置8个皇后,使它们不同行、不同列、不同对角线。问有多少种合法的情况。

答:92种

???? Java版

public class 八皇后
{
	// 版本2(从左上到右下,按格子放皇后)
	static int N = 20; // 由于斜线的原因,至少开 2n-1 的空间
	static int n; // n皇后问题
	static char[][] g = new char[N][N]; // 存储皇后的信息
//		布尔类型默认值为 flase
	static boolean[] col = new boolean[N]; // 列
	static boolean[] row = new boolean[N]; // 列
	// 正对角线 u = -i + b --> b = u + i
	static boolean[] dg = new boolean[N];
	// 反对角线 u = i + b --> b = u - i 但是 b 不能小于0,所以b = n - (u - i) = n - u + i
	static boolean[] udg = new boolean[N];
	static int cnt = 0;// 记录方案数

	/**
	 * @param x 行
	 * @param y 列
	 * @param s 皇后个数
	 */
	static void dfs(int x, int y, int s)
	{
		if (y == n)
		{// 列标 溢出
			y = 0;
			x++;// 转到下一行
		}

		if (x == n)
		{
			if (s == n)
			{ // 放满8个皇后
				print();
			}
			return;
		}

//			不放皇后
		dfs(x, y + 1, s);

//			放皇后
		if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n])
		{
			g[x][y] = 'Q';
			row[x] = col[y] = dg[x + y] = udg[x - y + n] = true;
			dfs(x, y + 1, s + 1);
			row[x] = col[y] = dg[x + y] = udg[x - y + n] = false;
			g[x][y] = '.';
		}
	}

//	输出当前棋盘
	private static void print()
	{
		System.out.println("方案" + ++cnt);
		for (int i = 0; i < n; i++)
		{
			for (int j = 0; j < n; j++)
			{
				System.out.print(g[i][j]);
			}
			System.out.println();
		}
		System.out.println();

	}

	public static void main(String[] args)
	{
		n = 8;// 八皇后
//			初始化棋盘
		for (int i = 0; i < n; i++)
			for (int j = 0; j < n; j++)
				g[i][j] = '.';
		dfs(0, 0, 0);
		System.out.println("总方案数为:" + cnt);
	}
}

???? C语言版

#include <stdio.h> // 包含标准输入输出库的头文件
#define N 20 // 定义一个常量N,代表棋盘大小

int n; // 定义一个全局变量n,表示棋盘的行数和列数
char g[N][N]; // 定义一个二维字符数组g,用于表示棋盘
int col[N], row[N], dg[N], udg[N]; // 定义四个数组,用于标记列、行和两个对角线是否已经放置了皇后
int cnt = 0; // 定义一个全局变量cnt,用于记录方案的数量

// 打印当前棋盘状态的函数
void print() {
	printf("方案%d\n", ++cnt); // 打印方案编号
	for (int i = 0; i < n; i++) { // 遍历每一行
		for (int j = 0; j < n; j++) { // 遍历每一列
			printf("%c", g[i][j]); // 打印当前格子的状态
		}
		printf("\n"); // 每打印完一行后换行
	}
	printf("\n"); // 每打印完一个方案后换行
}

// 深度优先搜索函数,用于搜索所有可能的皇后放置方案
void dfs(int x, int y, int s) {
	if (y == n) { // 如果y坐标达到n,说明一行已经放置完毕
		y = 0; // 重置y坐标,开始新的一行
		x++; // 移动到下一行
	}
	if (x == n) { // 如果x坐标达到n,说明已经放置了n个皇后,找到一个方案
		if (s == n) { // 检查是否所有皇后都已放置
			print(); // 如果是,打印当前方案
		}
		return; // 结束递归
	}
	dfs(x, y + 1, s); // 递归调用,尝试在当前行下一个位置放置皇后
	
	// 检查当前位置是否可以放置皇后
	if (!row[x] && !col[y] && !dg[x + y] && !udg[x - y + n]) {
		g[x][y] = 'Q'; // 放置皇后
		row[x] = col[y] = dg[x + y] = udg[x - y + n] = 1; // 标记当前位置已放置皇后
		dfs(x, y + 1, s + 1); // 递归调用,尝试放置下一行的皇后
		row[x] = col[y] = dg[x + y] = udg[x - y + n] = 0; // 回溯,撤销当前位置的皇后
		g[x][y] = '.'; // 恢复棋盘状态
	}
}

int main() {
	n = 8; // 设置棋盘大小为8
	for (int i = 0; i < n; i++) // 初始化棋盘,所有格子状态为'.'
		for (int j = 0; j < n; j++)
			g[i][j] = '.';
	dfs(0, 0, 0); // 从棋盘左上角开始深度优先搜索
	printf("总方案数为:%d\n", cnt); // 打印所有可能的皇后放置方案总数
	return 0; // 程序结束
}