C语言魔板问题递归解法
题目:
在成功地发明了魔方之后,鲁比克先生发明了它的二维版本,称作魔板。这是一张有8个大小相同的格子的魔板:
1 2 3 4
8 7 6 5
我们知道魔板的每一个方格都有一种颜色。这8种颜色用前8个正整数来表示。可以用颜色的序列来表示一种魔板状态,规定从魔板的左上角开始,沿顺时针方向依次取出整数,构成一个颜色序列。对于上图的魔板状态,我们用序列(1,2,3,4,5,6,7,8)来表示。这是基本状态。
这里提供三种基本操作,分别用大写字母“A”,“B”,“C”来表示(可以通过这些操作改变魔板的状态):
“A”:交换上下两行;
“B”:将最右边的一列插入最左边;
“C”:魔板中央四格作顺时针旋转。
下面是对基本状态进行操作的示范:
A:
8 7 6 5
1 2 3 4
B:
4 1 2 3
5 8 7 6
C:
1 7 2 4
8 6 3 5
对于每种可能的状态,这三种基本操作都可以使用。 你要编程计算用最少的基本操作完成基本状态到目标状态的转换,输出基本操作序列。
输入格式
只有一行,包括8个整数,用空格分开(这些整数在范围 1——8 之间)不换行,表示目标状态。
输出格式
第一行: 包括一个整数,表示最短操作序列的长度。 第二行: 在字典序中最早出现的操作序列,用字符串表示,除最后一行外,每行输出60个字符。
例如:
输入:
1 2 3 4 5 6 7 8
2 6 8 4 5 7 3 1
输出:
BCABCCB
解题:
第一:找出ABC三种操作,进行三次操作的可能会出现的可能性
A | B | C |
---|---|---|
AAA | BBB | CCC |
AAB | BBA | CCA |
AAC | BBC | CCB |
ABA | BAB | CAC |
ACA | BCB | CBC |
ACC | BAA | CAA |
ABB | BCC | CBB |
ABC | BAC | CAB |
ACB | BCA | CBA |
【注意事项】
观察上面的ABC操作可能性,不难发现: 当A两次连续操作或B四次连续操作或C四次连续操作就会回到原来的数组, 以上的情况都可能会导致死循环。为了防止会产生死循环的操作,需要在操作时添加相关的参数来记录,A、B、C连续操作的次数,并根据其判断下一步操作可进行哪个。
(2)由于原始魔板数组通过若干A、B、C操作最终都可以得到目标魔板数组,故需要限制最大的操作次数,以免出现死循环。
(3)使用struct结构体存储模板数组时注意指针问题。
第二:解题准备
【思路】:
- 在operateChange方法中分别判断A、B、C三种操作连续次数并决定下一个可以执行的操作。
- A、B、C操作中除了变换操作,还有对操作次数进行记录,string来记录每次操作以及判断操作后数组是否与目标魔板数组一致。
- 若操作后数组与目标魔板数组一致,使用stack来记录操作序列结果,并在最后通过比较得出最优解。
(1)顺序表堆栈SequenceStack
#include <stdlib.h>
#include <stdio.h>
#include<iostream>
#include<string>
using namespace std;
#define MAXSIZE 300
//注意堆栈大小,如果结果数量太多,会导致栈满
typedef struct {
string op;//操作序列结果
}StackData;
typedef struct{
StackData list[MAXSIZE];
int top;
}SequenceStack;
class MySequenceStack{
public:
void StackInitialize(SequenceStack *S);
int StackNotEmpty(SequenceStack S);
int StackPush(SequenceStack *S,StackData x);
int StackPop(SequenceStack *S,StackData *x);
int StackTop(SequenceStack S,StackData *x);
};
//堆栈初始化
void MySequenceStack::StackInitialize(SequenceStack *S){
S->top=0;
}
//判断栈是否为空
int MySequenceStack::StackNotEmpty(SequenceStack S){
if(S.top<=0) return 0;
else
return 1;
}
//入栈
int MySequenceStack::StackPush(SequenceStack *S,StackData x){
if(S->top>=MAXSIZE){
printf("堆栈已满,无法插入!\n");
return 0;
}else{
S->list[S->top]=x;
S->top++;
return 1;
}
}
//出栈
int MySequenceStack::StackPop(SequenceStack *S,StackData *x){
if(S->top<=0){
printf("堆栈已空,数据元素出栈!\n");
return 0;
}else{
S->top--;
*x=S->list[S->top];
return 1;
}
}
//取栈顶元素
int MySequenceStack::StackTop(SequenceStack S,StackData *x){
if(S.top<=0){
printf("堆栈已空,数据元素出栈!\n");
return 0;
}else{
*x=S.list[S.top-1];
return 1;
}
}
(2)进行解题操作
#include"SequenceStack.h"
#define MAX 10
//魔板数据
typedef struct {
int d[2][4];//魔板数组
bool isA;//A连续操作次数,若为true则下一次操作不能为A
int isB3;//B连续操作次数,若为3则下一次操作不能为B
int isC3;//C连续操作次数,若为3则下一次操作不能为C
int num;//总操作次数
}Data;
void inputInitial();//原始魔板输入
void inputTarget();//目标魔板输入
void A(Data a,string op);//A操作
void B(Data a,string op);//B操作
void C(Data a,string op);//C操作
void operateChange(Data a,string op);//始进行A/B/C操作
void showArr(Data a);//显示魔板数组
bool compareArr(int a1[2][4],int a2[2][4]);//比较数组
void compareResult();//结果比较,显示最优解
SequenceStack mystack;//堆栈,用来存储操作序列结果
MySequenceStack ss;//堆栈类,操作堆栈
Data init,target;//原始魔板数组、目标魔板数组
//初始化输入原始魔板数组
void inputInitial(){
printf("初始化原始魔板数组:");
scanf("%1d%1d%1d%1d%1d%1d%1d%1d",&init.d[0][0],&init.d[0][1],&init.d[0][2],&init.d[0][3],
&init.d[1][3],&init.d[1][2],&init.d[1][1],&init.d[1][0]);
//注意%1d,只取一位数赋值给数组对应的数。
}
//初始化输入目标魔板数组
void inputTarget(){
printf("初始化目标魔板数组:");
scanf("%1d%1d%1d%1d%1d%1d%1d%1d",&target.d[0][0],&target.d[0][1],&target.d[0][2],&target.d[0][3],
//注意%1d,只取一位数赋值给数组对应的数。
}
//进行A操作 a:要进行变换的魔板数据,op:操作序列结果
void A(Data a,string op){
Data temp,aa;//用新变量来获取魔板数据,不改变原来的魔板数据
temp=a;
aa=a;
//进行变换操作
aa.d[0][0]=temp.d[1][0];
aa.d[0][1]=temp.d[1][1];
aa.d[0][2]=temp.d[1][2];
aa.d[0][3]=temp.d[1][3];
aa.d[1][0]=temp.d[0][0];
aa.d[1][1]=temp.d[0][1];
aa.d[1][2]=temp.d[0][2];
aa.d[1][3]=temp.d[0][3];
//标记A、B、C的连续操作次数,当不是连续操作,其他操作归零,否则增加操作次数
aa.isA=true;
aa.isB3=0;
aa.isC3=0;
aa.num++;
op.append("A");//op记录当前操作
//比较操作后的魔板数组是否与目标魔板数组一致,若相同则输出,否则继续操作
if(compareArr(aa.d,target.d)){
StackData sd;
sd.op=op;
ss.StackPush(&mystack,sd);//堆栈中加入操作序列结果
op.clear();//操作序列结果清空
}else{
operateChange(aa,op);//继续操作
}
}
//进行B操作 a:要进行变换的魔板数据,op:操作序列结果
void B(Data a,string op){
Data temp,aa;//用新变量来获取魔板数据,不改变原来的魔板数据
temp=a;
aa=a;
//进行变换操作
aa.d[0][0]=temp.d[0][3];
aa.d[0][1]=temp.d[0][0];
aa.d[0][2]=temp.d[0][1];
aa.d[0][3]=temp.d[0][2];
aa.d[1][0]=temp.d[1][3];
aa.d[1][1]=temp.d[1][0];
aa.d[1][2]=temp.d[1][1];
aa.d[1][3]=temp.d[1][2];
//标记A、B、C的连续操作次数,当不是连续操作,其他操作归零,否则增加操作次数
aa.isA=false;
aa.isB3++;
aa.isC3=0;
aa.num++;
op.append("B");//op记录当前操作
//比较操作后的魔板数组是否与目标魔板数组一致,若相同则输出,否则继续操作
if(compareArr(aa.d,target.d)){
StackData sd;
sd.op=op;
ss.StackPush(&mystack,sd);//堆栈中加入操作序列结果
op.clear();//操作序列结果清空
}else{
operateChange(aa,op);//继续操作
}
}
//进行C操作 a:要进行变换的魔板数据,op:操作序列结果
void C(Data a,string op){
Data temp,aa;//用新变量来获取魔板数据,不改变原来的魔板数据
temp=a;
aa=a;
//进行变换操作
aa.d[0][1]=temp.d[1][1];
aa.d[0][2]=temp.d[0][1];
aa.d[1][1]=temp.d[1][2];
aa.d[1][2]=temp.d[0][2];
//标记A、B、C的连续操作次数,当不是连续操作,其他操作归零,否则增加操作次数
aa.isA=false;
aa.isB3=0;
aa.isC3++;
aa.num++;
op.append("C");//op记录当前操作
//比较操作后的魔板数组是否与目标魔板数组一致,若相同则输出,否则继续操作
if(compareArr(aa.d,target.d)){
StackData sd;
sd.op=op;
ss.StackPush(&mystack,sd);//堆栈中加入操作序列结果
op.clear();//操作序列结果清空
}else{
operateChange(aa,op);//继续操作
}
}
//进行A、B、C操作
void operateChange(Data a,string op){
if(a.num<MAX){//判断是否大于最大的操作次数
if(a.isA){//判断上一次操作为A,若是则只能进行B、C操作
B(a,op);
C(a,op);
}else
if(a.isB3==3){//判断上3次操作为B,若是则只能进行A、C操作
A(a,op);
C(a,op);
}else
if(a.isC3==3){//判断上3次操作为C,若是则只能进行A、B操作
A(a,op);
B(a,op);
}else
//判断非A,连续3B,连续3C,若是则能进行A、B、C操作
if(!a.isA||a.isB3<3||a.isC3<3){
A(a,op);
B(a,op);
C(a,op);
}
}else{//大于限定的操作次数,停止操作,并清空当前操作序列结果
op.clear();
}
}
//显示魔板数组
void showArr(Data a){
printf("a[0]=%d %d %d %d\n",a.d[0][0],a.d[0][1],a.d[0][2],a.d[0][3]);
printf("a[1]=%d %d %d %d\n\n",a.d[1][0],a.d[1][1],a.d[1][2],a.d[1][3]);
}
//比较两个数字是否一样
bool compareArr(int a1[2][4],int a2[2][4]){
for(int i=0;i<2;i++){
for(int j=0;j<4;j++){
if(a1[i][j]!=a2[i][j])
return false;
}
}
return true;
}
//比较最终结果,选择最优
void compareResult(){
StackData best,temp;
if(ss.StackNotEmpty(mystack)){
ss.StackPop(&mystack,&best);//出栈
for(int i=1;i<=mystack.top;i++){
ss.StackPop(&mystack,&temp);
if(best.op.size()>=temp.op.size()){//长度最小的即最优
best=temp;
}
}
cout<<best.op<<endl;//输出最优操作序列结果结果
}else{
printf("no result");//若无结果则提示
}
}
int main()
{
//初始化原始魔板数组
inputInitial();
showArr(init);
init.isA=false;
init.isB3=0;
init.isC3=0;
init.num=0;
//初始化目的魔板数组
inputTarget();
showArr(target);
//初始化堆栈
ss.StackInitialize(&mystack);
//进行A\B\C操作
string op;
operateChange(init,op);
//结果比较
compareResult();
return 0;
}
【结果】
采用递归来解有点局限,只能解操作变换较小的,当操作序列结果太多,会计算缓慢,甚至会因为程序无法及时释放空间,导致内存资源分配问题而进入死锁,然后程序被强制停止。此方法适用于初学者,大家相互交流。