注册 登录
编程论坛 数据结构与算法

麻将胡牌算法

qunxingw 发布于 2013-01-14 22:34, 30182 次点击
对于此游戏的基本算法本人独自想了好久,用了一个很笨的方法,总算有了一个结果,但对此算法没有严谨的证明,只是感觉上好象是这么回事。基本思想是,(麻将最多手上14只,)在第15张位置加上一个将牌,那么再让这个数组不相同的三个数 组合 若是一句话,或是相同的三个数就让这三个数置0。如果胡牌的话,则此数组应该全部为0了。 这种方法不严谨 。如2 2 3 3 4 4 5 5 6 6 7 7 8 9这样的数7已先和前面的组合了,最后改变以除掉将的方式 见19楼。
希望有兴趣的朋友再帮我测试最后的程序,谢谢。
程序代码:

#include<iostream>
using namespace std;
int main()
{
    cout<<"如果胡牌则显示,不胡牌则以下无输出"<<endl;
   
    int i,j,k,x,temp,flat=0;
    int a[15]={1,1,1,2,2,2,4,5,6,8,8,8,9,9};// 牌已排序。
   
   
    for (x=0; x<14; x++)  //在数组最后位置加一个数,便以下判断是否全部清0
    {
        if(a[x]==a[x+1])
        {
          temp=a[x];            
          a[14]=a[x];
        }
       else a[x]=temp;
      
        for(i=0; i<15; i++)                     //找出连续或相同的三个数,并标记为0
            for(j=0; j<15; j++)
                for(k=0; k<15; k++)   
                {
                    if(i==j||j==k||i==k) continue;
                    if(a[i]+1==a[j] && a[j]+1==a[k]&& a[i]!=0 || a[i]==a[j] && a[k]==a[j] )
                    {
                        a[i]=0; a[j]=0; a[k]=0;
                    }
                }               
                int    s=0;   
                for(i=0;i<15;i++)   
                  {  s+=a[i];

cout<<a[i];}
cout<<endl;
cout<<s<<"s    ";
               /* if(s!=0) //若加的将不胡,则下次试其他将时还回原值。
               {
                a[x]=temp;
                a[x+1]=temp;
                a[14]=flat;
                }
*/
               if(s==0)
                {
                    cout<<"恭喜您!已胡牌!"<<endl;//如果数组里全部是0说明已胡牌,
                  
                  
// break;
                }
               
    }
    getchar();
    return 0;
}



[ 本帖最后由 qunxingw 于 2013-1-16 19:48 编辑 ]
31 回复
#2
wp2319572013-01-14 22:41
各地麻将规则都不一样

比如 缺少 “幺” 不能胡
     缺少  ”坎“ 不能胡
     断门  不能胡
     等等 很多
#3
qunxingw2013-01-14 22:45
那些暂不考虑,只是常规的,万(1--9)  条用11--19。如此==
#4
qunxingw2013-01-15 08:08
另加1个判断条件 将不为0
#5
azzbcc2013-01-15 09:03
没懂啥意思,15张?不是最多只能有14张或17张么?相公了吧
#6
qunxingw2013-01-15 09:43
加的那张相当于验证码,仅用于判断。是虚的,如
77778899 此时7作将,我仍可加7使之组成3个7
#7
azzbcc2013-01-15 09:56
算法有漏洞,a[14]多次置 0了

int a[15]={1, 1,  2, 3, 3, 4, 4, 5,  6, 6, 8, 8, 9, 9};
#8
qunxingw2013-01-15 10:19
a14是我专门设计的,相当于存放第15牌作验证码,目的是循环判断将牌,并把以前作将牌而又不胡的牌覆盖掉,不干扰后面。加1使牌数为3的倍数。置0并没有关系,相当于不胡牌,因不是3的倍数。
#9
azzbcc2013-01-15 10:26
版主木有试一下我给的数组么,想了很久的,笨来打算给出 7组将牌,但是这样也是一种胡法,所以给了 4组将牌,这样漏洞就很明显了

如上我举出的例子,明明没胡的

#10
qunxingw2013-01-15 11:19
回复 9楼 azzbcc
原语句if(a[x]==a[x+1]),有误,数组出界了。谢谢,你再帮帮测试下。
#11
azzbcc2013-01-15 11:39
不是数组越界不越界的问题

int a[15]={1, 2, 2, 3, 3, 4,  5, 5, 6, 6,  8, 8, 9, 9};
#12
azzbcc2013-01-15 11:56
没考虑数组越界,所以又有新问题了

int a[15]={1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 9, 9, 9};//是胡牌
#13
azzbcc2013-01-15 12:00
仔细想了一下,12楼例子与数组越界无关,噢,建议斑竹重新想一个吧

[ 本帖最后由 azzbcc 于 2013-1-15 12:02 编辑 ]
#14
qunxingw2013-01-15 13:23
回复 13楼 azzbcc
谢谢,这个问题找到了,查了下,发现选将不胡牌时,要恢复原来的数据。需要调整,呵呵,

[ 本帖最后由 qunxingw 于 2013-1-15 13:34 编辑 ]
#15
qunxingw2013-01-15 20:41
#include<iostream>
using namespace std;
int main()
{//测试数据{1,1,2, 3, 3, 4, 4, 5, 6, 6,8,8,9,9};
    //{1, 1, 1, 3, 3, 4, 4, 5, 5, 6, 6, 9, 9, 9};测试数据
    //{1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 9, 9, 9}
    cout<<"如果胡牌则显示,不胡牌则以下无输出"<<endl;  
    int i,j,k,x,temp,flat=0;//{1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 6, 9, 9, 9}测试数据
   
    int a[15]={1, 1, 1, 3, 3, 4, 4, 5, 5, 6, 6, 9, 9, 9};// 牌已排序。
   
    for (x=0; x<14; x++)  //在数组最后位置加一个数,便以下判断是否全部清0
    {
        if(a[x]==a[x+1] && a[x]!=0)
        {
            temp=a[x];
            a[14]=a[x];
        }
        
        for(i=0; i<15; i++)                     //找出连续或相同的三个数,并标记为0
            for(j=0; j<15; j++)
                for(k=0; k<15; k++)   
                {
                    if(i==j||j==k||i==k) continue;
                    if(a[i]+1==a[j] && a[j]+1==a[k]&& a[i]!=0 || a[i]!=0 && a[i]==a[j] && a[k]==a[j] )
                    {
                        a[i]=0; a[j]=0; a[k]=0;
                    }
                }               
                int    s=0;   
                for(i=0;i<15;i++)   
                {
                //    cout<<a[i];
                    s+=a[i];
                }
            //    cout<<endl;
                if(s!=0)
                {
                    a[x]=temp;
                    a[x+1]=temp;
                    a[14]=0;        
                //    cout<<"  s="<<s<<endl;
                }
                if(s==0)
                {
                    cout<<"恭喜您!已胡牌!"<<endl;//如果数组里全部是0说明已胡牌,
                    
                       break;
                }
               
    }
   
    return 0;
}
#16
azzbcc2013-01-15 21:22
跟进看了一下,后面 2个 6会被temp覆盖,所以应该还是有问题的,不过也许理解错了,算法还没看懂,额
#17
a4257532622013-01-15 21:43
回复 2楼 wp231957
汗颜!!!!
#18
qunxingw2013-01-15 21:51
以下是引用azzbcc在2013-1-15 21:22:20的发言:

跟进看了一下,后面 2个 6会被temp覆盖,所以应该还是有问题的,不过也许理解错了,算法还没看懂,额

的确还有问题,暂时想不出了。
#19
qunxingw2013-01-16 12:30
程序代码:
#include<iostream>
using namespace std;
int main()
{
     
    int i,j,k,x,temp,flat=0;
    int b[14]={2,2,3,3,4,4,5,5,6,6,7,7,8,9};// 牌已排序。

    int a[14]={0};
    for(i=0; i<14; i++)
            a[i]=b[i];

    for (x=0; x<13; x++)  //在数组最后位置加一个数,便以下判断是否全部清0
    {
        if(a[x]==a[x+1]&& a[x]!=0 )//改变策略,除掉将牌
        {         
          a[x]=0;
          a[x+1]=0;
        }
       else continue;
     
        for(i=0; i<14; i++)                     //找出连续或相同的三个数,并标记为0
            for(j=0; j<14; j++)
                for(k=0; k<14; k++)
                {
                    if(i==j||j==k||i==k) continue;
                    if( a[i]==a[j] && a[k]==a[j]||a[i]+1==a[j] && a[j]+1==a[k]&& a[i]!=0  )
                    {
                        a[i]=0; a[j]=0; a[k]=0;
                    }
                }            
                int    s=0;
                for(i=0;i<14;i++)
                  {
                s+=a[i];
                    }
              //cout<<a[i];}
           
// cout<<endl;
        
// cout<<s<<"s    ";
                if(s!=0) //若加的将不胡,则下次试其他将时还回原值。
                {
                         for(i=0; i<14; i++)
                         a[i]=b[i];
               
                }
               if(s==0)
                {
                   flat=1;
                   break;
                }
   }
          if(flat)  cout<<"恭喜您!已胡牌!"<<endl;
          else     cout<<"对不起!不能胡牌!"<<endl;
  
    getchar();
    return 0;
}

有兴趣的朋友试下看有没有问题,谢谢 。


[ 本帖最后由 qunxingw 于 2013-1-17 15:39 编辑 ]
#20
azzbcc2013-01-16 13:27
自己也写了一个,穷举的,也不知道对不对
程序代码:
#include <math.h>
#include <stdio.h>
#include <string.h>

#define Copy(a, b) memcpy((void *)a, (void *)b, 40)

int change(int *a, int flag[], int m)
{    //依据flag数组和 m值将 a处理(减 3)
    int n = 0;

    if (m & 1)    a[flag[1]] -= 3, ++n;
    if (m & 2)    a[flag[2]] -= 3, ++n;
    if (m & 4)    a[flag[3]] -= 3, ++n;
    if (m & 8)    a[flag[4]] -= 3, ++n;

    return n;
}
int find(int *a)
{    //找到 3张连续的牌,都标记,并返回真值
    for (int i = 1;i < 7;++i)
        if (a[i] && a[i+1] && a[i+2])
        {
            --a[i];--a[i+1];--a[i+2];
            return 1;
        }

    return 0;
}
int main()
{
    int i, j, m, n, flag = 0;
//  int a[10] = {0, 2, 1, 2, 2, 1, 2, 0, 2, 2};
//  int a[10] = {0, 2, 1, 2, 2, 1, 3, 0, 0, 3};
    int a[10] = {0, 3, 0, 2, 2, 2, 2, 0, 0, 3};    //a[i]存储 牌号 i的个数
    int b[10], c[10], temp1[5] = {0}, temp2[11] = {0};

    for (i = 1;i < 10;++i)
    {
        temp1[0] += a[i] >= 3;                    //temp1[0]为牌数不小于 3的个数
        if (temp1[0] && !temp1[temp1[0]])
            temp1[temp1[0]] = i;                //将不小于 3的牌数存储到temp1中
    }

    for (i = (int)pow(2, temp1[0]);i;--i)    //所有牌数不小于 3的共有 i种组合
    {
        Copy(b, a);
        m = change(b, temp1, i-1);            //m表示已标记为 3个相同 的个数
   
        for (j = 1;j < 10;++j)
        {
            temp2[0] += b[j] >= 2;                //同上,此时temp2存储不小于 2的牌数
            if (temp2[0] && !temp2[temp2[0]])
                temp2[temp2[0]] = j;
        }

        for (j = temp2[0];j;--j)
        {
            Copy(c, b);c[temp2[j]] -= 2;

            n = 0;while (find(c))    ++n;

            if (4 == m+n){flag = 1;break;}
        }
   
        if (flag)
        {
            puts("YES!");break;
        }
    }
    return 0;
}


[ 本帖最后由 azzbcc 于 2013-1-16 13:30 编辑 ]
#21
qunxingw2013-01-16 15:23
回复 20楼 azzbcc
谢谢你,看来看别人的东西更累,代价要高。
#22
azzbcc2013-01-17 04:12
在火车上无聊了,详细说下我的思路。

先标记暗刻牌,同时记录暗刻数m,在标记将牌,这里的标记,就是减去的意思。最后在剩下的牌里找连续牌,记为n,然后m+n=4就说明是胡牌了。

因为  3个相同 的个数与最后结果中的个数不一定相同,例如11123,虽然有3个1,但牌中没4,所以反而要做将牌。

假设有 j个这种  3个相同,就会产生 2^j种可能,再在对应的情况中找将牌,又有 很多情况了,所以才说是穷举。

这里再说下change函数作用,m值取值范围是 0 - 15,取其二进制位,1表示最后结果 这是一组暗刻,0表示这组 3个相同 是其他情况。比如 5 = 0101,就把找到的  3个相同 的第1 3组标记

代码中还有错误,记录将牌下标的temp2数组可能因为外循环累积,再前面加一个temp2[0] = 0就可以了。

下午到家后再研究研究版主的代码,嘿嘿:-)
#23
azzbcc2013-01-17 04:15
手机码字真心伤不起啊!!!
#24
qunxingw2013-01-17 12:20
回复 22楼 azzbcc
首先感谢参与。我对你的程序弱弱的能理解一点,整体上应该是一个不错的思路,在不加tmep2[0]=0的情况下,测试胡牌的都可以找到。加后也有问题,
同时测试数据:    0 2 3 2 2 2 1 1 1 0
                  0 3 3 3 3 0 1 0 1 0
#25
jk3332013-01-17 20:37
看个了大概,继续加强学习进度
#26
azzbcc2013-01-18 21:56
恩,是没想仔细,应该把temp2数组全部置 0的
#27
yaobao2013-01-18 23:07
呵呵   ,此贴很火啊
#28
qunxingw2013-01-18 23:17
回复 26楼 azzbcc
我有个老同学,他处理牌有你相同的方法 我也还没细看,一起粘在此吧。
程序代码:
#include<iostream>
using namespace std;
int main()
{
int i,x,flat=1;
    int b[14]={1,2,3,4,4,4,5,5,5,5,6,7,8,9};// 牌无需排序。

    int a[10]={0};
    for(i=0; i<14; i++)
    {
        a[b[i]-1]++;    //统计每种牌的数量
    }
    int xi,a2[10];
    for (x=0; x<10; x++)  //循环找一对将牌
    {
        for(i=0;i<10;i++)
            a2[i]=a[i];
        if(a2[x]>=2 )//,除掉将牌
        {
          a2[x]-=2;
        }
        else continue;
        xi=0;
        flat=1;
        for(i=0; i<10; i++)                     //找出连续或相同的三个数
        {
            if(a2[i]>=3)
            {
                a2[i]-=3;
                xi+=3;
            }
            if((a2[i]>0)&&(i>=8))
            {
                flat=0;
                break;
            }
            if(a2[i]==2)
               {
                   if((a2[i+1]>1)&&(a2[i+2]>1))
                   {
                       a2[i]-=2;
                       a2[i+1]-=2;
                       a2[i+2]-=2;
                   }
                   else
                   {
                       flat=0;
                       break;
                   }
               }
            else if(a2[i]==1)
               {
                   if((a2[i+1]>0)&&(a2[i+2]>0))
                   {
                       a2[i]--;
                       a2[i+1]--;
                       a2[i+2]--;
                   }
                   else
                   {
                       flat=0;
                       break;
                   }
               }
        }
        if(flat==1)
                   break;
   }
          if(flat)  cout<<"恭喜您!已胡牌!"<<endl;
          else     cout<<"对不起!不能胡牌!"<<endl;

    return 0;

}
#29
azzbcc2013-01-19 16:59
回复 28楼 qunxingw
把修订版放这。
程序代码:
#include <math.h>
#include <stdio.h>
#include <string.h>

#define Copy(a, b) memcpy((void *)a, (void *)b, 40)

void initarray(int *a, int len, int *b, int mode)
{   //找到 b中不小于 mode的数,将其下标存入 a,a[0]是其个数
    memset(a, 0, len);

    for (int i = 1;i < 10;++i)
    {
        a[0] += b[i] >= mode;
        if (a[0] && !a[a[0]])
            a[a[0]] = i;
    }
}
int change(int *a, int flag[], int m)
{
    int n = 0;

    if (m & 1)    a[flag[1]] -= 3, ++n;
    if (m & 2)    a[flag[2]] -= 3, ++n;
    if (m & 4)    a[flag[3]] -= 3, ++n;
    if (m & 8)    a[flag[4]] -= 3, ++n;

    return n;
}
int find(int *a)
{
    for (int i = 1;i < 7;++i)
        if (a[i] && a[i+1] && a[i+2])
        {
            --a[i];--a[i+1];--a[i+2];
            return 1;
        }
   
    return 0;
}
int main()
{
    int i, j, m, n, flag = 0;
//  int a[10] = {0, 2, 1, 2, 2, 1, 2, 0, 2, 2};
//  int a[10] = {0, 2, 1, 2, 2, 1, 3, 0, 0, 3};
//  int a[10] = {0, 3, 0, 2, 2, 2, 2, 0, 0, 3};
//  int a[10] = {0, 2, 3, 2, 2, 2, 1, 1, 1, 0};
    int a[10] = {0, 3, 3, 3, 3, 0, 1, 0, 1, 0};
    int b[10], c[10], temp1[5], temp2[11];

    initarray(temp1, sizeof(temp1), a, 3);

    for (i = (int)pow(2, temp1[0]);i;--i)
    {
        Copy(b, a);
        m = change(b, temp1, i-1);
   
        initarray(temp2, sizeof(temp2), b, 2);
   
        for (j = temp2[0];j;--j)
        {
            Copy(c, b);c[temp2[j]] -= 2;
      
            n = 0;while (find(c))    ++n;
      
            if (4 == m+n){flag = 1;break;}
        }
   
        if (flag)    break;
    }
    if (flag)    puts("YES!");
    else         puts("NO!");

    return 0;
}
楼主的代码是还没改好吧,b数组没用到。想了一下应该是可以的,要加两个限定条件,有可能的将牌数 和 某处已被标记过,然后一次次把 b复制到 a就行。

至于楼上的代码,其实就是这个思路的实现,这样会有一个问题 1 2 2 3 3 3 4 4 5 这种,也就是说连续必须优先处理,,楼上代码就是这样做的。嗯,目前

对找 3个时 这个条件
程序代码:
if((a2[i]>0)&&(i>=8))
{
    flat=0;
    break;
}
表示疑惑,尚需努力啊!


[ 本帖最后由 azzbcc 于 2013-1-19 17:04 编辑 ]
#30
qunxingw2013-01-19 18:55
谢谢你的关注。19楼b数组大部分时候都用不上,从以前数据测试可以看出来。不过每次判断不胡牌时必竞修改了a数组的值。有时总是在最后选将时会出现错误。所以一不做二不休的,在后面如果不胡时,重新通过b还原a数组,以便选择下对将。
#31
小习小习2013-01-26 17:42
顶一下,最近在学打麻将。发现麻将文化也很博大精神
#32
landychun2016-10-31 14:44
广东这边有个 红中 可以代替任意牌的,算法又应怎么写呢?
1