C语言魔板问题递归解法

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;
}

【结果】
操作结果
采用递归来解有点局限,只能解操作变换较小的,当操作序列结果太多,会计算缓慢,甚至会因为程序无法及时释放空间,导致内存资源分配问题而进入死锁,然后程序被强制停止。此方法适用于初学者,大家相互交流。

源码下载链接

猜你喜欢

转载自blog.csdn.net/qq_25740691/article/details/78397013