C++之练习题7

有一个3*3的棋盘,其中有0-8共9个数字,0表示空格,其他的数字可以和0交换位置。求由初始状态到达目标状态  1 2 3   的步数最少的解?
                                                                                                                                                                                                          4 5 6
                                                                                                                                                                                                          7 8 0

1.广度优先搜索的代码框架
  BFS(){
         初始化队列
         while(队列不为空且未找到目标节点){
                    取队首节点扩展,并将扩展出的节点放入队尾;
                    必要时要记住每个节点的父节点;
           }
}

2.深度优先搜索的非递归框架
  DFS(){
         初始化栈;
         while(栈不为空且未找到目标节点){
                    取栈顶节点扩展,扩展出的节点放回栈顶;
        }
        ……
}

3.判重

方案一:每个节点对应于一个九进制数,则4个字节就能表示一个节点。( 228< 99=387,420,489 <229)每判重需要一个标志位序列,个状态对应于标志位序列中的1位,标志位为0表示该状态尚未扩展,为1则说明已经扩展过了标志位序列可以用字符数组存放。数组的每个元素存放8个状态的标志位。位序列最多需要99位,因此存放位序列的数组需要99/8 + 1个字节 48,427,562字节如果某个状态对应于一个9进制数a,则其标志位就是标志位序列中的第a位(其所属的数组元素下标是a/8)

方案二:为节点编号把每个节点都看一个排列,以此排列在全部排列中的位置作为其编号排列总数:9!=362880只需要一个整数(4字节)即可存下一个节点判重用的标志数组只需要362 880字节即可此方案需要编写给定排列求序号和给定序号求排列的函数,这些函数的执行速度慢于字符串形式的9进制数到其整型值的互相转换函数。


采用方案1的非递归方案

数据结构用一个九进制字符串表示一个节点,其中有且仅有一个0(表示空格)---因此若九进制串中没有0,则0一定是在最高位设一个48427562位字符型标志位序列数组szFlag,标识每个状态是否已扩展设一个队列MyQueue存放搜索过程中的可能状态,根据问题定义,状态总数362880。考虑到搜索过程中可能存在的重复状态,其大小设为1000000。
为简化计算,设置与MyQueue同样大小的数组anFather存放父节点指针、数组szMoves存放从父节点到当前节点的移动步骤设置一个结果队列,为防止溢出,设置与MyQueue同样大小

采用方案1的非递归方案:关键处理逻辑
Main程序
1.将输入的原始字符串变为九进制字符串;
2.用BFS过程看是否可以达到目标状态;
3.若能达到,通过anFather数组找到成功的状态序列;
4.根据数组szMoves找到相应的移动步骤,并输出.
BFS过程—输入:初始状态
输出:成功/失败;若成功, anFather、szMoves
BFS中的关键问题:

1)如何进行状态扩展?
2)状态中0的位置与cMove动作间的关系?
3)如何计算求从nStatus经过cMove 移动后得到的新状态(定义为函数NewStatus)?
4)如何判断扩展标记已经存在?
5)对未扩展状态,如何置已扩展标记(定义为函数SetBit )?
6)字符串形式的9进制数到其整型值的互相转换函数(定义为NineToTen和TenToNine)

//本程序在ai上会超内存,在acm上能过
#include <iostream>
using namespace std;
int nGoalStatus; //目标状态
unsigned char szFlag[48427562]; //节点是否扩展的标记
char szResult[1000000]; //结果
char szMoves[1000000]; //移动步骤 :u/d/r/l
int anFather[1000000]; //父节点指针
int MyQueue[1000000]; //状态队列,状态总数362880
int nQHead;
int nQTail;
char sz4Moves[] = "udrl";//四种动作

int NineToTen( char * s ) {
//九进制字符串转十进制
int nResult = 0;
for( int i = 0; s[i]; i ++ ) {
nResult *= 9;
nResult += s[i] - '0';
}
return nResult;
}
int GetBit( unsigned char c, int n) {
return ( c >> n ) & 1;
}
void SetBit( unsigned char & c, int n, int v) {
if( v )
c |= (1 << n);
else
c &= ~(1 << n);
}

int TenToNine( int n, char * s) {
//十进制数转九进制字符串。可能有前导0,返回0的位置
int nZeroPos;
int nBase = 1;
int j = 0;
while( nBase <= n) /*从高位开始的进制转换*/
nBase *= 9;
nBase /= 9;
do {
s[j] = n/nBase + '0';
if( s[j] == '0' )
nZeroPos = j;
j ++;
n %= nBase;
nBase /= 9;
}while( nBase >= 1 );
s[j] = 0;//串结束符
//判断是否要加前导0,此时第0位即为0
if( j < 9 ) {
for( int i = j + 1; i > 0; i --)
s[i] = s[i-1];
s[0] = '0';
return 0;
}
return nZeroPos;
}

int NewStatus( int nStatus, char cMove) {
//求从nStatus经过cMove 移动后得到的新状态。若移动不可行则返回-1
char szTmp[20];
int nZeroPos = TenToNine(nStatus,szTmp);//返回空格的位置
switch( cMove) {
case 'u': if( nZeroPos - 3 < 0 ) return -1; //空格不在第一行
else { szTmp[nZeroPos] = szTmp[nZeroPos - 3];
szTmp[nZeroPos - 3] = '0'; }
break;
case 'd': if( nZeroPos + 3 > 8 ) return -1; //空格不在第三行
else { szTmp[nZeroPos] = szTmp[nZeroPos + 3];
szTmp[nZeroPos + 3] = '0'; }
break;
case 'l': if( nZeroPos % 3 == 0) return -1; //空格不在第一列
else { szTmp[nZeroPos] = szTmp[nZeroPos -1];
szTmp[nZeroPos -1 ] = '0'; }
break;
case 'r': if( nZeroPos % 3 == 2) return -1; //空格不在第三列
else { szTmp[nZeroPos] = szTmp[nZeroPos + 1];
szTmp[nZeroPos + 1 ] = '0'; }
break;
}
return NineToTen(szTmp);
}

bool Bfs(int nStatus){
int nNewStatus;
nQHead = 0; nQTail = 1;
MyQueue[nQHead] = nStatus;
while ( nQHead != nQTail) { //队列不为空
nStatus = MyQueue[nQHead];
if( nStatus == nGoalStatus ) //找到目标状态
return true;
for( int i = 0;i < 4;i ++ ) { //尝试4种移动
nNewStatus = NewStatus(nStatus,sz4Moves[i]);
if( nNewStatus == -1 ) continue; //不可移,试下一种
int nByteNo = nNewStatus / 8;
int nBitNo = nNewStatus % 8;
if( GetBit( szFlag[nByteNo],nBitNo))
continue; //如果扩展标记已经存在,则不能入队
SetBit( szFlag[nByteNo],nBitNo,1); //设上已扩展标记
MyQueue[nQTail] = nNewStatus; //新节点入队列
anFather[nQTail] = nQHead; //记录父节点
//记录本节点是由父节点经什么动作而来
szMoves[nQTail] = sz4Moves[i];
nQTail ++;
}
nQHead ++;
}
return false;
}

main(){
nGoalStatus = NineToTen("123456780");
memset(szFlag,0,sizeof(szFlag));
char szLine[50]; char szLine2[20];
cin.getline(szLine,48);
int i,j;
//将输入的原始字符串变为九进制字符串
for( i = 0, j = 0; szLine[i]; i ++ ) {
if( szLine[i] != ' ' ) {
if( szLine[i] == 'x' ) szLine2[j++] = '0';
else szLine2[j++] = szLine[i];
}
}
szLine2[j] = 0;
if( Bfs(NineToTen(szLine2))) {
int nMoves = 0;
int nPos = nQHead;
do { //通过anFather数组找到成功的状态序列,输出相应步骤
szResult[nMoves++] = szMoves[nPos];
nPos = anFather[nPos];
} while( nPos);
for( int i = nMoves -1; i >= 0; i -- )
cout << szResult[i];
}
else
cout << "unsolvable" << endl;
}


2.乔治拿来一组等长的棍子,将它们随机地裁断(截断后的小段称为木棒),使得每一节木棒的长度都不超过50个长度单位。然后他又想把这些木棒恢复到为裁截前的状态,但忘记了棍子的初始长度。请你设计一个程序,帮助乔治计算棍子的可能最小长度。每一节木棒的长度都用大于零的整数表示
 输入:由多个案例组成,每个案例包括两行。第一行是一个不超过64的整数,表示裁截之后共有多少节木棒。第二行是经过裁截后,所得到的各节木棒的长度。在最后一个案例之后,是零。
 输出:为每个案例,分别输出木棒的可能最小长度。每个案例占一行。

枚举所有有可能的棍子长度。从最长的那根木棒的长度一直枚举到木棒长度总和的一半,对每个假设的棍子长度试试看能否拼齐所有棍子

在拼接过程中,要给用过的木棒做上标记,以免重复使用

回溯:拼好前i根棍子,结果发现第i+1根拼不成了,那么就要推翻第i根的拼法,重拼第i根…..直至有可能推翻第1根棍子的拼法

构造一条木棒长度为L的“路径”:拼接木棒在未被拼接的木棒中,找出一节最长的,开始拼接从未拼接的木棒中,选择一个长度合适的木棒,使得拼接后的木棒长度≤L找到了若在前面的拼接过程中曾试图用过相同长度的一节其他木棒,但发现这样拼接不成功,继续寻找能够进行拼接的木棒//剪枝3把找到的那节木棒拼接上去。继续进行拼接继续拼接成功,找到了“路径”继续拼接不成功,把刚拼接的那节木棒拿下来,继续找下一个合适的未拼接木棒//剪枝2、1没有找到:拼接失败

#include <iostream.h>
#include <memory.h>
#include <stdlib.h>
int T, S;
int L;
int anLength[65];
int anUsed[65];
int i,j,k;
int Dfs(int nUnusedSticks, int nLeft);
int MyCompare( const void * e1, const void * e2) { //用于木棒排序
int * p1, * p2;
p1 = (int * ) e1;
p2 = (int * ) e2;
return * p2 - * p1;
}

int main(){
while(1) { //输入
cin >> S;
if( S == 0 )
break;
int nTotalLen = 0;
for( int i = 0; i < S; i ++ ) {
cin >> anLength[i];
nTotalLen += anLength[i];
}
qsort(anLength, S, sizeof(int), MyCompare); //排序
//枚举
for( L = anLength[0]; L <= nTotalLen / 2; L ++ ) {
if( nTotalLen % L)
continue;
memset( anUsed, 0, sizeof(anUsed));
if( Dfs( S,L)) {
cout << L << endl;
break;
}
}
if( L > nTotalLen / 2 )
cout << nTotalLen << endl;
} // while
return 0;
}

int Dfs( int nUnusedSticks, int nLeft) {
// nLeft表示当前正在拼的棍子和L 比还缺的长度
if( nUnusedSticks == 0 && nLeft == 0 )
return true;
if( nLeft == 0 ) //一根刚刚拼完
nLeft = L; //开始拼新的一根
for( int i = 0; i < S; i ++) {
if( !anUsed[i] && anLength[i] <= nLeft) {
if( i > 0 ) {
if( anUsed[i-1] == false
&& anLength[i] == anLength[i-1])
continue; //剪枝3
}
anUsed[i] = 1;
if ( Dfs( nUnusedSticks -1, nLeft - anLength[i]))
return true;
else {
anUsed[i] = 0;//说明本次不能用第i根
//第i根以后还有用
if( anLength[i] == nLeft || nLeft == L)
return false;//剪枝2、1
}
}
}
return false;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值