24点扩展-任意多数经过四则运算得到一个新数(n数n点,matlab程序)

24点扩展-任意多数经过四则运算得到一个新数(n数n点,matlab程序)

水一篇。由于家长群里经常发各种‘益智’题,比如4个6怎样凑成5,123456怎样得到20呀之类的,所以不厌其烦,干脆写一个程序,一劳永逸。

1 大概思路

以下面问题为例:
1 2 3,如何添加 加减乘除 运算和括号,得到4。
(要加入指数幂运算,可以添加^符号,我怕电脑算炸,没敢加)

正确答案为:
2*(3-1)=4
(3+2)-1=4
。。。等等

观察等式,式子中包含了数字、符号和运算顺序三大类。改变这三大类,我们可以得到不同的排列组合

在这里插入图片描述
如上图所示,其中N代表参与排列的数字,图中123三个,所以N=3。其中数字的排列种类有N!,以阶乘的速度增长。符号的变化种类为4^(N-1)种,以指数速度增长。而计算顺序有(N-1)!,也是以阶乘的速度在增长。

因此,如果把所有组合都暴力尝试一遍,则会有:
N ! ∗ 4 N − 1 ∗ ( N − 1 ) ! N!*4^{N-1}*(N-1)! N!4N1(N1)!
当N等于3的时候,只需要计算192次;
当N等于4的时候,需要计算9216次;
当N等于5的时候,需要计算737280次。
这种爆炸式增长使得在计算数据量较大的数据时,会非常慢。

因此不用说,这种算法的缺点就是计算量太大。而且从算法设计本身,就涉及到3层循环计算,对matlab十分不友好,会导致速度更慢。而且算法采用字符串调用eval函数的方法,matlab的eval函数速度又比较慢,所以效率更差。(没救了,没救了)

1.1 数字排列算法

这里直接采用matlab里的函数perms()进行排列组合,包含了输入向量所有可能的排序。

1.2 符号排序算法

符号排序属于重复的全排列,每个符号都可以重复的出现。
这里采用的是下面这篇文章的思路:
matlab如何实现重复数全组合问题

文章采用ndgrid函数生成不重复的N维网格,然后将每个独一无二的网格点坐标,作为全排列的点。

1.3 计算顺序括号算法

括号算法采用逐级合并的方式进行,以下面的图为例(Mermaid流程图打不出括号,用<>代替括号):

1 , 2 , 3 , 4
<1#2> , 3 , 4
1 , <2#3> , 4
1 , 2 , <3#4>
<<1#2>#3> , 4
<1#2> , <3#4>
<1#<2#3>> , 4
1 , <<2#3>#4>
<1#2> , <3#4>
1 , <2#<3#4>>
<<1#2>#3>#4
<1#2>#<3#4>
<1#<2#3>>#4
1#<<2#3>#4>
<1#2>#<3#4>
1#<2#<3#4>>4

感觉不好看,又重新画了一下
在这里插入图片描述
首先每两个取一次1括号合并,进行保存。之后再每次取2个合并进行保存。一直到所有列表里的元素只剩下一个,说明合并完成,得到所有的括号顺序。

2 程序代码

%24点
clear
clc
%初始条件
Number=[6 6 6 6];
Result=24;
N=numel(Number);
Symbol={'+','-','*','/'};
N_Symbol=1:numel(Symbol);
%生成列表
Number_List = perms(Number);%生成数字排列
Number_List=unique(Number_List,'rows');
N_Symbol_List = Full_Choose(N_Symbol,N-1);%生成符号排列
P_List = Full_Parenthese_List(N);%生成括号排列
%生成替换符
S_Replace=cell(1,N-1);%创建符号替换符
for k=1:N-1
    S_Replace{k}=['s',num2str(k,'%02.0f')];
end
N_Replace=cell(1,N);%创建数字替换符
for k=1:N
    N_Replace{k}=['x',num2str(k,'%02.0f')];
end

%定义结果cell
Result_List={};
m=1;
f=waitbar(0,'进度条');
N_Sum=size(P_List,1)*size(N_Symbol_List,1);
m2=1;
%进行数字、符号、括号的三层循环
for x=1:size(P_List,1)
    %括号
    Str_Temp1=P_List{x,1};
    for y=1:size(N_Symbol_List,1)
        %符号
        S_L_temp=N_Symbol_List(y,:);
        S_Replace_New=Symbol(S_L_temp);%生成替换的符号
        Str_Temp2=replace(Str_Temp1,S_Replace,S_Replace_New);
        for z=1:size(Number_List,1)
            N_Replace_New=string( Number_List(z,:) );
            Str_Temp3=replace(Str_Temp2,N_Replace,N_Replace_New);
            X=eval([Str_Temp3]);
            if abs(X-Result)<1e-5
                Result_List{m,1}=[Str_Temp3,'=',num2str(Result)];
                m=m+1;
            end
        end
        %进度条
        waitbar(m2/N_Sum,f,'进度条');
        m2=m2+1;
    end
end

close(f);
%删除重复结果
Result_List=unique(Result_List);%删除重复元素
%输出
Result_List





function FullList=Full_Choose(n_List,n)
%全排列,可以重复
%n_List元素
%n是全排列的位数
V_Str='Xgrid';
V_Str_Sum='';
%思路是利用ndgrid方法,实现全排列。用eval函数执行
for k=1:n
    V_Str_Sum=[V_Str_Sum,V_Str,num2str(k),','];
end
V_Str_Sum(end)=' ';
Eval_Str=['[',V_Str_Sum,']=ndgrid(','n_List',');'];
eval(Eval_Str);
V_Str_Sum2=[];
for k=n:-1:1
    V_Str_Sum2=[V_Str_Sum2,V_Str,num2str(k),'(:),'];
end
V_Str_Sum2(end)=' ';
Eval_Str=['FullList=[',V_Str_Sum2,'];'];
eval(Eval_Str);

FullList=n_List(FullList);%把生成好的列表带入到nlist中
FullList=unique(FullList,'rows');
end

function NL=Full_Plus(N)
%将一个整数N输出为若干整数相加的形式
NL_i=ones(1,N);
NL=[];
NL=[NL;NL_i];%总的列表
%m=sum(NL_i>0);
NL_k=NL_i;%上一步循环中列表的行数
for k=2:N
    %循环,每次循环k内生成的元素,比上一次循环减一
    NL_k2=[];
    for j=1:size(NL_k,1)
        NL_i=NL_k(j,:);%从上一步中结果抽取一个
        NL_k_temp=Plus2List(NL_i);%执行两两相加,输出结果
        NL_k2=[NL_k2;NL_k_temp];
    end
    
    NL_k2=unique(NL_k2,'rows');%删除重复元素
    NL_k=NL_k2;
    NL=[NL;NL_k];%加入总的列表
end

end

function NL_k=Plus2List(NL_i)
%输入1个行向量,输出所有两两相加的和
N=numel(NL_i);%行向量的元素
NL_i_num=sum(NL_i>0);
Plus_List=nchoosek(1:NL_i_num,2);%需要两两相加的列表
N_Plus=size(Plus_List,1);%计算循环次数
NL_k=zeros(N_Plus,N);%初始化输出矩阵
for k=1:N_Plus
    NL_temp=NL_i;%创建临时向量
    NL_temp(Plus_List(k,1))=NL_temp(Plus_List(k,1))+NL_temp(Plus_List(k,2));
    NL_temp(Plus_List(k,2))=0;
    NL_temp=sort(NL_temp,'descend');
    NL_k(k,:)=NL_temp;
end
NL_k=unique(NL_k,'rows');%删除重复元素
end

function P_L_1=Full_Parenthese_List(N)
%生成括号组合,采用两两合并的思想
P_L_1=cell(1,N);%创建元素
for k=1:N
    P_L_1{k}=['x',num2str(k,'%02.0f')];
end
%生成括组合
for k=1:N-1
    P_L_2={};
    for j=1:size(P_L_1,1)
        P_L_temp1=P_L_1(j,:);
        P_L_temp2=Combine_Parenthese(P_L_temp1);
        P_L_2=[P_L_2;P_L_temp2];
    end
    P_L_1=P_L_2;
end
%去掉收尾多余的括号
for k=1:size(P_L_1,1)
    str_temp=P_L_1{k};
    str_temp(end)=[];
    str_temp(1)=[];
    P_L_1{k}=str_temp;
end
%把加号替换为's1','s2'...
for k=1:size(P_L_1,1)
    str_temp=P_L_1{k};
    idx = strfind(str_temp,'+++');
    for j=numel(idx):-1:1
        str_temp(idx(j):idx(j)+2)=['s',num2str(j,'%02.0f')];
    end
    P_L_1{k}=str_temp;
end

end


function P_L_2=Combine_Parenthese(P_L_1)
%把PL1中的元素两两组合
N=numel(P_L_1);
P_L_2=cell(N-1,N-1);
for k=1:size(P_L_2,1)
    for j=1:size(P_L_2,2)
        if j==k
            P_L_2(k,j)={ ['(',P_L_1{1,j},'+++',P_L_1{1,j+1},')'] };%用+++占位,作为符号
        elseif j>k
            P_L_2(k,j)={ P_L_1{1,j+1} };
        elseif j<k
            P_L_2(k,j)={ P_L_1{1,j} };
        end
    end
end
end

以[1,2,3,4,5]五个数为例,要求等于78,输出结果为:

{'(((1+3)*4)*5)-2=78'}
    {'(((1+3)*5)*4)-2=78'}
    {'(((1+5)*4)+2)*3=78'}
    {'(((3+1)*4)*5)-2=78'}
    {'(((3+1)*5)*4)-2=78'}
    {'(((5+1)*4)+2)*3=78'}
    {'((1+3)*(4*5))-2=78'}
    {'((1+3)*(5*4))-2=78'}
    {'((3+1)*(4*5))-2=78'}
    {'((3+1)*(5*4))-2=78'}
    {'((4*(1+3))*5)-2=78'}
    {'((4*(1+5))+2)*3=78'}
    {'((4*(3+1))*5)-2=78'}
    {'((4*(5+1))+2)*3=78'}
    {'((4*5)*(1+3))-2=78'}
    {'((4*5)*(3+1))-2=78'}
    {'((5*(1+3))*4)-2=78'}
    {'((5*(3+1))*4)-2=78'}
    {'((5*4)*(1+3))-2=78'}
    {'((5*4)*(3+1))-2=78'}
    {'(2+((1+5)*4))*3=78'}
    {'(2+((5+1)*4))*3=78'}
    {'(2+(4*(1+5)))*3=78'}
    {'(2+(4*(5+1)))*3=78'}
    {'(4*((1+3)*5))-2=78'}
    {'(4*((3+1)*5))-2=78'}
    {'(4*(5*(1+3)))-2=78'}
    {'(4*(5*(3+1)))-2=78'}
    {'(5*((1+3)*4))-2=78'}
    {'(5*((3+1)*4))-2=78'}
    {'(5*(4*(1+3)))-2=78'}
    {'(5*(4*(3+1)))-2=78'}
    {'3*(((1+5)*4)+2)=78'}
    {'3*(((5+1)*4)+2)=78'}
    {'3*((4*(1+5))+2)=78'}
    {'3*((4*(5+1))+2)=78'}
    {'3*(2+((1+5)*4))=78'}
    {'3*(2+((5+1)*4))=78'}
    {'3*(2+(4*(1+5)))=78'}
    {'3*(2+(4*(5+1)))=78'}

可以看到,基本结果就是只有4*20-2,((6 *4)+2) *3这两种,有非常多的结果是可以通过加法或乘法的交换律消去的。这也是需要改进的不足。

3 更新匿名函数方法

之前的eval函数特别慢,在思考有没有什么可以替代的。于是采用匿名函数来进行替换。

但是实际上并没有快多少。这里给出算法:

%24点
clear
clc
%初始条件
Number=[1 2 3 4 5 6];
Result=91;
N=numel(Number);
Symbol={'+','-','*','/'};
S{1}=@(a,b) a+b;
S{2}=@(a,b) a-b;
S{3}=@(a,b) a*b;
S{4}=@(a,b) a/b;
N_Symbol=1:numel(Symbol);
%生成列表
Number_List = perms(Number);%生成数字排列
Number_List=unique(Number_List,'rows');
N_Symbol_List = Full_Choose(N_Symbol,N-1);%生成符号排列
P_List = Full_Parenthese_List(N);%生成括号排列
PN_List = CycleNum(N-1:-1:1);
PN_Symbol_List = sortrows(perms(1:(N-1)));%生成符号顺序替换排列
%生成替换符
S_Replace=cell(1,N-1);%创建符号替换符
for k=1:N-1
    S_Replace{k}=['s',num2str(k,'%02.0f')];
end
N_Replace=cell(1,N);%创建数字替换符
for k=1:N
    N_Replace{k}=['x',num2str(k,'%02.0f')];
end

%定义结果cell
Result_List={};
m=1;
N_Sum=size(P_List,1)*size(N_Symbol_List,1);
%进行数字、符号、括号的三层循环
for x=1:size(P_List,1)
    t_start= cputime;
    %括号
    Str_Temp1=P_List{x,1};
    for y=1:size(N_Symbol_List,1)
        %符号
        S_L_temp=N_Symbol_List(y,:);
        S_Replace_New=Symbol(S_L_temp);%生成替换的符号
        Str_Temp2=replace(Str_Temp1,S_Replace,S_Replace_New);
        for z=1:size(Number_List,1)

            X1=Number_List(z,:);%数字列表,代表4个数字
            Y1=S_L_temp;%运算符,1加法,2减法,3乘法
            %运算顺序
            Z1=PN_List(x,:);
            Z2=PN_Symbol_List(x,:);
            
            %已知数字,运算符,运算顺序,计算结果
            Temp=X1(:)';
            for k=1:N-1
                Zk=Z1(k);
                Yk=Y1(Z2(k));%这里运算符顺序的定义和原版程序不同
                Temp2=[Temp(1:Zk-1),S{Yk}(Temp(Zk),Temp(Zk+1)),Temp(Zk+2:end)];
                Temp=Temp2;
            end
            
            if abs(Temp-Result)<1e-5
                N_Replace_New=string( Number_List(z,:) );
                Str_Temp3=replace(Str_Temp2,N_Replace,N_Replace_New);
                Result_List{m,1}=[Str_Temp3,'=',num2str(Result)];
                m=m+1;
            end
        end
    end
end

%删除重复结果
Result_List=unique(Result_List);%删除重复元素
disp(Result_List)

function FullList=Full_Choose(n_List,n)
%全排列,可以重复
%n_List元素
%n是全排列的位数
V_Str='Xgrid';
V_Str_Sum='';
%思路是利用ndgrid方法,实现全排列。用eval函数执行
for k=1:n
    V_Str_Sum=[V_Str_Sum,V_Str,num2str(k),','];
end
V_Str_Sum(end)=' ';
Eval_Str=['[',V_Str_Sum,']=ndgrid(','n_List',');'];
eval(Eval_Str);
V_Str_Sum2=[];
for k=n:-1:1
    V_Str_Sum2=[V_Str_Sum2,V_Str,num2str(k),'(:),'];
end
V_Str_Sum2(end)=' ';
Eval_Str=['FullList=[',V_Str_Sum2,'];'];
eval(Eval_Str);

FullList=n_List(FullList);%把生成好的列表带入到nlist中
FullList=unique(FullList,'rows');
end


function P_L_1=Full_Parenthese_List(N)
%生成括号组合,采用两两合并的思想
P_L_1=cell(1,N);%创建元素
for k=1:N
    P_L_1{k}=['x',num2str(k,'%02.0f')];
end
%生成括组合
for k=1:N-1
    P_L_2={};
    for j=1:size(P_L_1,1)
        P_L_temp1=P_L_1(j,:);
        P_L_temp2=Combine_Parenthese(P_L_temp1);
        P_L_2=[P_L_2;P_L_temp2];
    end
    P_L_1=P_L_2;
end
%去掉收尾多余的括号
for k=1:size(P_L_1,1)
    str_temp=P_L_1{k};
    str_temp(end)=[];
    str_temp(1)=[];
    P_L_1{k}=str_temp;
end
%把加号替换为's1','s2'...
for k=1:size(P_L_1,1)
    str_temp=P_L_1{k};
    idx = strfind(str_temp,'+++');
    for j=numel(idx):-1:1
        str_temp(idx(j):idx(j)+2)=['s',num2str(j,'%02.0f')];
    end
    P_L_1{k}=str_temp;
end
end

function P_L_2=Combine_Parenthese(P_L_1)
%把PL1中的元素两两组合
N=numel(P_L_1);
P_L_2=cell(N-1,N-1);
for k=1:size(P_L_2,1)
    for j=1:size(P_L_2,2)
        if j==k
            P_L_2(k,j)={ ['(',P_L_1{1,j},'+++',P_L_1{1,j+1},')'] };%用+++占位,作为符号
        elseif j>k
            P_L_2(k,j)={ P_L_1{1,j+1} };
        elseif j<k
            P_L_2(k,j)={ P_L_1{1,j} };
        end
    end
end
end


function L=CycleNum(X)
%按每一位顺序进行循环
%输入3,2
%生成[1,1][1,2][2,1][2,2][3,1][3,2]
N=prod(X);
M=length(X);
Np=[cumprod(X,2,'reverse'),1];
L=mod(fix((0:N-1)'*(1./Np(2:1+M))),X);
L=L+1;
end

主要是循环太慢了。

4 尾声

以吐槽为主,没干货。


在我刚编写完程序以后,就有新鲜的题目发到群里了:
1 1 1 1 = 5
9 9 9 9 = 5
于是,我自信满满的把题目输入到程序里,结果发现居然没有解!

吓得我赶紧关掉程序,拿出纸和笔,叫醒我的大脑出来解题。
第一题,(1+1+1)!-1=5.
???什么时候可以加阶乘了,这要是加到程序里,计算机穷举一不小心岂不是炸了?

第二题, ( 9 + 9 ) / 9 + 9 = 5 (9+9)/9+\sqrt{9}=5 (9+9)/9+9 =5
???这不是作弊吗?根号本身就隐藏着2这个数,随意加根号的话要放进程序里,什么时候是个头?

最后来一道压箱子底的题:
2 2 2 = 5
答案为:




− log ⁡ 2 log ⁡ 2 2 = 5 -\log_{2}{\log_{2}{ \sqrt{ \sqrt{ \sqrt{ \sqrt{ \sqrt{2}}}}} }}=5 log2log22 =5
???什么时候log都上来了,我的天ORZ。这让计算机怎么猜。我只能说劳动者的智慧是无穷的,别问,问就是浪费了一天。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值