_15-骑士精神(IDA*)

本文介绍了使用A*算法解决经典八数码问题的思路和代码实现。通过DFS结合估价函数进行剪枝,逐步找到将棋盘状态调整为目标状态的最小步数。代码中详细展示了A_star函数的迭代加深过程和评估函数的设计,以求达到最佳搜索效果。

一道经典的IDA*入门题,本质上就是dfs+估价函数

思路:以唯一的空位为起点,进行dfs,利用估价函数进行剪枝。

 for(int dep=1;maxdep<=15;dep++){  //dep:本次需要移动的次数,因为从1开始,一但满足条件,就是题目所要求的最小步数
     A_star(0,stx,sty,dep);  //dfs代码,stx sty:空位所在位子
     if(success){  //如果满足条件,输出步数
        printf("%d\n",dep);
     }
 }

评估函数:

        评估函数是理想的情况,而实际情况一定会大于等于评估函数,但如果评估函数越接近实际情况,那么剪枝效果就越好。

int evaluate(){
    int cnt=0;
    for(int i=0;i<5;i++)
        for(int j=0;j<5;j++)
            if(mp[i][j]!=goal[i][j])cnt++;  //如果现棋盘与目标棋盘不符,则cnt(至少需要的步数)加1
    return cnt;
}

dfs:

void a_star(int dep,int x,int y,int maxdep) {
	if (dep==maxdep){  //返回条件是当前搜索数达到了最大搜索数(也就是一开始传进来的dep)
		if(evaluate() == 0)   //如果评估函数为0,说明现棋盘与目标棋盘一致
			success = 1;
		return;
	}
    int mx[8] = { -2,-2,-1,1,-1,1,2,2 };  //棋子走的八个方向
    int my[8] = { -1,1,2, 2,-2,-2,-1,1 };
	for (int i = 0; i < 8; i++) {
		int xx = x + mx[i];
		int yy = y + my[i];
		if (!check(xx, yy))  //check()判断移动后的棋子是否越界
			continue;
		swap(a[x][y], a[xx][yy]);  //交换两棋子
		if (evaluate() + dep <= maxdep)    //如果评估函数的步数加当前步数未达到限制步数,进行迭代加深,这一步达到剪枝效果
			a_star(dep + 1, xx, yy, maxdep);  //迭代加深
		swap( a[x][y],a[xx][yy]);  //记得要回溯回来
	}
}

总代码:

#include<iostream>
using namespace std;
int goal[5][5] = {
	{1,1,1,1,1},
	{0,1,1,1,1},
	{0,0,2,1,1},
	{0,0,0,0,1},
	{0,0,0,0,0} };//1黑,0白
int  x, y,m,n,success,a[5][5],flag;
int mx[8] = { -2,-2,-1,1,-1,1,2,2 };
int my[8] = { -1,1,2, 2,-2,-2,-1,1 };
int evaluate() {  //价值评估
	int cnt = 0;
	for (int i = 0; i < 5; i++) {
		for (int j = 0; j < 5; j++) {
			if (a[i][j] != goal[i][j])
				cnt++;
		}
	}
	return cnt;
}
bool check(int x, int y) {  //判断棋子有无越界
	if(x<0||x>4||y<0||y>4)
		return false;
	return true;
}
void a_star(int dep,int x,int y,int maxdep) {
	if (dep==maxdep){
		if(evaluate() == 0) 
			success = 1;
		return;
	}
	for (int i = 0; i < 8; i++) {
		int xx = x + mx[i];
		int yy = y + my[i];
		if (!check(xx, yy))
			continue;
		swap(a[x][y], a[xx][yy]);
		if (evaluate() + dep <= maxdep) 
			a_star(dep + 1, xx, yy, maxdep);
		swap( a[x][y],a[xx][yy]);
	}
}
int main() {
	char c;
	int t;
	cin >> t;
	while (t) {
		success = 0;
		flag = 0;
		for (int i = 0; i < 5; i++) {
			for (int j = 0; j < 5; j++) {
				cin >> c;
				if (c != '*')
					a[i][j] = c - '0';
				else {
					a[i][j] = 2;
					n = i;
					m = j;
				}
			}
		}
		if (evaluate() == 0) {
			cout << 0 << endl;
			continue;
		}
		for (int i = 1; i <= 10; i++) {
			a_star(0, n, m, i);
			if (success == 1) {
				cout << i << endl;
				flag = 1;
				break;
			}
		}
		if (flag == 0)
			cout << -1 << endl;
		t--;
	}
	return 0;
}

ps:今天看这题,才认识了A*,尽管这是一道入门题,但自己还是花了一晚上搞定,要多练习。

<think> 嗯,用户遇到了一个典型的Windows桌面程序开发中的内存访问冲突问题,具体是pThread->m_pMainWnd指针异常。从错误值0xFFFFFFFFFFFFFF2F来看,这明显是个无效地址,接近-209的补码表示,说明程序试图访问非法内存。 用户提供的引用片段很关键,展示了MFC框架的启动流程。这里需要特别注意三个关键点:一是AfxWinInit初始化失败可能导致后续资源未创建;二是InitInstance虚函数实现中主窗口创建是否成功;三是消息循环启动前窗口是否已被销毁。 用户应该是位Windows C++开发者,熟悉MFC框架,但可能忽略了异常处理。ta现在最需要的是具体排查步骤而非理论解释。从错误现象看,崩溃发生在销毁窗口时,但根本原因可能在更早阶段——比如主窗口创建失败却未中止流程,或是线程对象提前释放。 比较棘手的是,这个错误值很特殊。常规空指针是0,而0xFFFFFFFFFFFFFF2F更像是某个对象被部分覆盖后的残留值。可能需要检查内存越界问题,特别是与CWinThread或CWinApp相关的全局数据。 解决方案设计上,应该先给用户可快速验证的基础排查(比如检查InitInstance返回值),再逐步深入。特别要强调调试技巧,因为这类问题仅靠代码审查很难定位。ida64调试器的内存断点功能会是关键突破口。 考虑到用户正在遭遇崩溃,回复需要包含即时应对方案(try-catch块防止崩溃)和根治方案(内存检测)。最后提醒文档资源也很重要,微软关于类似错误的KB文章可能有隐藏线索。 </think> 这个错误是典型的**野指针访问异常**,根本原因在于程序试图访问已被销毁或未正确初始化的 `CWinThread` 对象或其成员 `m_pMainWnd`。值 `0xFFFFFFFFFFFFFF2F` 是无效内存地址的常见表现形式(接近 `0x00000000` 的补码负数偏移)。 以下是可能原因及解决方案: --- ### **主要原因分析** 1. **主窗口未创建或提前销毁**- **`InitInstance()` 未创建主窗口**:`CWinApp` 派生类的 `InitInstance()` 虚函数中**未创建主窗口**或创建失败(如返回 `FALSE`),导致 `pThread->m_pMainWnd` 为空。 - **窗口提前销毁**:主窗口在 `Run()` 消息循环启动前被手动销毁(如调用了 `DestroyWindow()`)。 2. **线程对象生命周期问题**- **`CWinThread` 被提前释放**:线程对象在 `AfxWinMain` 执行结束前被意外销毁(如栈对象提前退出作用域)。 - **多线程竞争**:其他线程修改或释放了 `pThread` 指针(MFC主线程通常不涉及此问题)。 3. **框架初始化失败**- **`AfxWinInit()` 失败**:全局初始化未完成,导致后续资源无效。 - **内存损坏**:堆溢出、越界写入等破坏了 `CWinThread` 对象内存。 --- ### **解决方案步骤** #### 1. **检查 `InitInstance()` 实现** - 确保在 `CYourApp::InitInstance()` 中**显式创建主窗口**并赋值给 `m_pMainWnd`: ```cpp BOOL CYourApp::InitInstance() { CWinApp::InitInstance(); // 创建主窗口 CMainFrame* pFrame = new CMainFrame; if (!pFrame->LoadFrame(IDR_MAINFRAME)) return FALSE; // 创建失败 m_pMainWnd = pFrame; // 关键:赋值给 m_pMainWnd pFrame->ShowWindow(SW_SHOW); return TRUE; // 必须返回 TRUE } ``` - **验证返回值**:若窗口创建失败,需返回 `FALSE` 以确保框架正确终止。 #### 2. **排查窗口销毁时机** - 在 `InitInstance()` 和 `Run()` 之间**禁止手动销毁主窗口**- 检查是否在 `OnCreate()` 等初始化函数中误调用了 `DestroyWindow()`。 #### 3. **验证线程对象生命周期** - 确认 `CWinThread` 对象(通常是 `CWinApp` 实例)为**全局对象**,例如: ```cpp CYourApp theApp; // 全局唯一实例 ``` - 避免在栈上创建 `CWinApp` 派生类对象(会导致提前析构)。 #### 4. **启用调试工具** - **设置内存断点**: 1. 在调试器(如VS)的内存监视窗口输入 `&pThread->m_pMainWnd`。 2. 当该地址被修改为非法值时中断,回溯调用栈。 - **检查堆完整性**- 使用 `_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF)` 检测内存泄漏。 - 启用 GFlags 工具验证堆破坏([微软文档](https://docs.microsoft.com/zh-cn/windows-hardware/drivers/debugger/gflags))。 #### 5. **添加防御性代码** - 在访问 `m_pMainWnd` 前检查指针有效性: ```cpp if (pThread && ::IsWindow(pThread->m_pMainWnd->GetSafeHwnd())) { pThread->m_pMainWnd->DestroyWindow(); } ``` --- ### **关键排查流程图** ```mermaid graph TD A[异常触发] --> B{检查 InitInstance 实现} B -->|主窗口未创建| C[修正窗口创建逻辑] B -->|主窗口已创建| D{检查 m_pMainWnd 赋值} D -->|未正确赋值| E[确保 m_pMainWnd=新窗口指针] D -->|已赋值| F{检查窗口是否提前销毁} F -->|是| G[禁止提前销毁] F -->|否| H{检查线程对象生命周期} H -->|对象被释放| I[确保 CWinApp 为全局对象] H -->|内存损坏| J[使用调试工具检测堆破坏] ``` > **经验提示**:此类问题常见于动态创建窗口后未及时赋值 `m_pMainWnd`,或窗口在消息循环启动前被销毁。建议优先审查 `InitInstance` 和窗口销毁逻辑[^1]。 --- ### 相关问题 1. MFC 程序中如何正确管理主窗口的生命周期? 2. 调试内存访问冲突 (`ACCESS_VIOLATION`) 有哪些高级技巧? 3. MFC 框架启动流程中 `AfxWinMain` 和 `CWinApp` 的关系是怎样的? 4. 如何检测 C++ 程序中的堆内存损坏问题?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别搜了,自己做

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值