麻将的玩法规则众多,核心的玩法是一致的,本文将根据联发科2017年编程挑战赛的复赛题规则来实现。

牌的表示方式

ABCDEFGHI代表一到九萬,abcdefghi代表一到九条,123456789代表一到九饼

三种胡牌牌型

  • 普通牌型,14张牌,形如:3+3+3+3+2。其中数字2代表两张相同的牌可成一组,形如XX。数字3代表三张相同或者连续的牌可成一组,形如XXXXYZ
  • 龙七对,14张形如:2+2+2+2+2+2+2
  • 带杠,即普通牌型里三张一样的牌XXX可以升级成XXXX,称为一道杠。每多一道杠,牌总数可以增加一张。最多可以有4道杠,因此带杠的胡牌,牌总数最多可以达18张。

样例

  • ABCeee345456DD 胡牌,组牌方式为 ABC+eee+345+456+DD,普通牌型。
  • ABeeee345456DD 炸胡,因为AB两张牌未能形成组牌。
  • AAAABC123456333 炸胡,虽然看似组牌OK(AAA+ABC+124+345+456+333)但是不符合任何一种胡牌牌型。
  • AADDFF1133aagg 胡牌,暗七对。
  • AAAABBBBCCCCDDDD88 胡牌,3+3+3+3+2牌型,升级了4道杠。
  • AAA123789 炸胡,不符合任何一种牌型。
  • AAA111345666DEF88 炸胡,不符合任何一种牌型。

算法实现思路

1、普通牌型为3n+2的形式,和龙7对均为14张牌,若有杠则最多有18张牌,因此第一步可以判定,如果牌数小于14或者大于18,则必定不能胡牌;

2、对牌进行从小到大排序,方便后续判断。如果手牌数是14张,可以先判定是否是龙7对(对对胡),其特点是每张奇数位的牌都和它后一位的牌相等。如果不是对对胡,则进入步骤3;

3、3n+2形式的普通牌型里面有一个对子,因此判断是不是胡牌牌型,可以先找出其中的一个对子。一张牌可能有2张也可能有4张,可以组成对子也可能组成暗杠或者杠,又或者是和后面的牌组成顺子。不管情况有多少种,对子一定是出现在重复的牌之中,只要每次遍历去除一个对子即可。接下来进入步骤4;

4、去除一个对子后,判定是否是3n牌型,即是否是全部由顺子或者暗杠组成。由于牌已经经过排序,所以只要观察第一张牌即可。

  • 如果第一张牌的数量只有一张或者两张,则这张牌必须和后面的牌组成顺子,否则不能胡牌。如果存在这样的顺子,去除这个顺子
  • 如果第一张牌的数量有三张或者四张,则可能组成一个暗杠,或者是和后面的牌组成顺子(先不考虑有杠的情况),去除这个暗杠(顺子)

一直循环以上的判断,满足条件则去掉这三张牌,直到牌数为0时,返回“胡牌”,否则回到步骤3中,将之前去除的对子放回,继续删除下一个对子。如果步骤3中尝试过所有的对子还没能满足胡牌条件时,则返回“不胡牌”

5、如果牌数为15张,则至少包含一个4张牌的杠,否则不胡牌。如果包含多个杠,则依次遍历删除一个杠,再进入步骤3,判断是不是3n牌型。如果遍历完所有的杠后还不能胡牌,则返回“不胡牌”;

6、如果牌数是16张,则至少包含2个杠,依次遍历删一对杠的组合,余下同步骤5类似。同理,牌数为17张和18张时方法类似。

C++源代码

#include <iostream>
#include <vector>
#include <algorithm> using std::vector;
using std::cout; template <typename T>
void showVector(vector <T> & lst);
vector <int> findReptPos(vector <char> & lst);
bool isDDH(const vector <char> & lst);
bool isHU3N(vector <char> & lst);
vector <char> delDUI(const vector <char> & lst, const int x);
bool is3N(vector <char> lst);
int numOfFirst(vector <char> lst);
bool checkGroup(vector <char> lst, const int num);
void delGroup(vector <char> & lst, const int num);
vector <char> delGANG(const vector <char> & lst, const int x);
vector <int> findGangPos(const vector <char> & lst); int main(int argc, char **argv)
{ char *mahjong = argv[1];
vector <char> lst; for (int i = 0; mahjong[i] != '\0'; ++i)
{
lst.push_back(mahjong[i]);
} std::sort(lst.begin(), lst.end()); // 从小到大排序 /*cout << "sort: ";
showVector(lst);*/ int num = lst.size(); // 麻将牌数量 if ((num < 14) || (num > 18))
{
cout << "BAD";
return 0;
} if (num == 14) // 14张牌,2种胡牌法
{
if (isDDH(lst)) // 如果是对对胡
{
cout << "GOOD";
return 0;
}
if (isHU3N(lst))
{
cout << "GOOD";
}
else
{
cout << "BAD";
} return 0; } if (num == 15)
{
vector <int> pos = findGangPos(lst); // 查找杠的位置 if (pos.size() < 1)
{
cout << "BAD";
}
else
{
// 依次删除一个杠,再判断是否是3N牌型
for (int i = 0; i < pos.size(); ++i)
{
vector <char> newLst = delGANG(lst, pos[i]);
/*cout << "delGANG/";
showVector(newLst);*/ if (isHU3N(newLst))
{
cout << "GOOD";
return 0;
} } cout << "BAD";
}
}
if (num == 16)
{
vector <int> pos = findGangPos(lst); // 查找杠的位置 if (pos.size() < 2) // 少于2个杠肯定不胡牌
{
cout << "BAD";
}
else
{
// 依次删除不同的2个杠组合,再判断是否是3N牌型
for (int i = 0; i < pos.size() - 1; ++i)
{
for (int j = i + 1; j < pos.size(); ++j)
{
// 注意先删除后一个杠,否则位置会变动
vector <char> newLst1 = delGANG(lst, pos[j]);
vector <char> newLst2 = delGANG(newLst1, pos[i]); /*cout << "delGANG/";
showVector(newLst2);*/ if (isHU3N(newLst2))
{
cout << "GOOD";
return 0;
}
}
} cout << "BAD";
}
}
if (num == 17)
{
vector <int> pos = findGangPos(lst); // 查找杠的位置 if (pos.size() < 3) // 少于3个杠肯定不胡牌
{
cout << "BAD";
}
else
{
// 依次删除不同的3个杠组合,再判断是否是3N牌型
for (int i = 0; i < pos.size() - 2; ++i)
{
for (int j = i + 1; j < pos.size() - 1; ++j)
{
for (int k = j + 1; k < pos.size(); ++k)
{
// 注意先删除后一个杠,否则位置会变动
vector <char> newLst1 = delGANG(lst, pos[k]);
vector <char> newLst2 = delGANG(newLst1, pos[j]);
vector <char> newLst3 = delGANG(newLst2, pos[i]); /*cout << "delGANG/";
showVector(newLst3);*/ if (isHU3N(newLst3))
{
cout << "GOOD";
return 0;
}
}
}
} cout << "BAD";
}
}
if (num == 18)
{
vector <int> pos = findGangPos(lst); // 查找杠的位置 if (pos.size() != 4) // 不是4个杠肯定不胡牌
{
cout << "BAD";
}
else
{
// 直接删除4个杠
vector <char> newLst1 = delGANG(lst, pos[3]);
vector <char> newLst2 = delGANG(newLst1, pos[2]);
vector <char> newLst3 = delGANG(newLst2, pos[1]);
vector <char> newLst4 = delGANG(newLst3, pos[0]); /*cout << "delGANG/";
showVector(newLst4);*/ if (newLst4[0] == newLst4[1])
{
cout << "GOOD";
}
else
{
cout << "BAD";
}
}
} return 0;
} // 显示列表内容
template <typename T>
void showVector(vector <T> & lst)
{
vector <T>::iterator iter; // 迭代器
for (iter = lst.begin(); iter != lst.end(); iter++)
{
cout << *iter << " ";
}
cout << "\n";
} // 查找重复牌的位置
vector <int> findReptPos(vector <char> & lst)
{
vector <int> pos; // 储存重复牌的位置
int temp_pos = 0; if (lst.size() <= 1) // 牌数小于等于1,直接返回
{
return pos;
} // lst.size() >= 2
vector <char>::iterator iter2;
iter2 = lst.begin();
++iter2; // 迭代器不支持算数运算,只能++/--/advance链式操作
if (lst.front() == *iter2)
{
pos.push_back(temp_pos);
} vector <char>::iterator it_front;
vector <char>::iterator it_back; for (auto iter1 = iter2; iter1 != (--lst.end()); iter1++) // 从第二位到倒数第二位
{
++temp_pos; // 位置更新 it_front = iter1; --it_front;
it_back = iter1; ++it_back; // 不等于前面的且等于后面的
if ((*iter1 != *it_front) && (*iter1 == *it_back))
{
pos.push_back(temp_pos);
}
} return pos;
} // 是否是对对胡
bool isDDH(const vector <char> & lst)
{
vector <char> newLst = lst; for (int i = 0; i <= 12; ++i)
{
if (newLst[i] != newLst[i + 1])
{
return false;
}
++i;
} return true;
} // 是否是普通牌型3n
bool isHU3N(vector <char> & lst)
{ vector <int> pos = findReptPos(lst); // 找重复牌的位置
for (int i = 0; i < pos.size(); ++i) // 依次去掉重复的对子
{
vector <char> newLst = delDUI(lst, pos[i]); // 删除一对牌 /*cout << "delDUI/";
showVector(newLst);*/ if (is3N(newLst))
{
return true;
}
}
return false;
} // 是否是N个顺子或者暗杠
bool is3N(vector <char> lst)
{ if (lst.size() % 3 != 0)
{
return false;
} while (lst.size() > 0)
{ if (lst.size() >= 3)
{
int num = numOfFirst(lst); // 计算第一张牌的重复数量 if (checkGroup(lst, num)) // 检查是否有第一个顺子或者暗杠
{ delGroup(lst, num); // 删除这组顺子或者暗杠 }
else
{
return false;
} } /*cout << "//";
showVector(lst);*/
} return true;
} // 检查是否有第一个顺子或者暗杠
bool checkGroup(vector <char> lst, const int num)
{
if ((num == 1) && (lst[1] == lst[0] + 1))
{
// 第二个数可能有重复情况
for (int i = 2; i < lst.size(); ++i)
{
if (lst[i] != lst[1])
{
if (lst[i] == lst[0] + 2)
{
return true;
}
else
{
return false;
}
}
} }
if ((num == 2) && (lst[2] == lst[1] + 1))
{
// 第三个数可能有重复情况
for (int i = 3; i < lst.size(); ++i)
{
if (lst[i] != lst[2])
{
if (lst[i] == lst[1] + 2)
{
return true;
}
else
{
return false;
}
}
} }
if (num >= 3)
{
return true;
} return false;
} // 删除这组顺子或者暗杠
void delGroup(vector <char> & lst, const int num)
{ if (num == 1)
{
vector <char>::iterator iter = ++lst.begin();
for (int i = 2; i < lst.size(); ++i)
{
++iter;
if (lst[i] == (1 + lst[1]))
{
lst.erase(iter);
lst.erase(lst.begin());
lst.erase(lst.begin());
break;
} }
} if (num == 2)
{
vector <char>::iterator iter = ++(++lst.begin());
for (int i = 3; i < lst.size(); ++i)
{
++iter;
if (lst[i] == (1 + lst[2]))
{
lst.erase(iter);
lst.erase(++lst.begin()); // 删除第二位的牌
lst.erase(++lst.begin());
break;
}
}
} if (num >= 3)
{
lst.erase(lst.begin());
lst.erase(lst.begin());
lst.erase(lst.begin());
}
} // 计算第一张牌的重复数量
int numOfFirst(vector <char> lst)
{
if (lst[0] != lst[1])
{
return 1;
}
if ((lst[0] == lst[1]) && (lst[1] != lst[2]))
{
return 2;
} if (lst[0] == lst[2])
{
if (lst.size() == 3)
{
return 3;
}
if ((lst.size() >= 4) && (lst[2] != lst[3])) // lst[3]一定保证size>=4
{
return 3;
} }
if ((lst[0] == lst[3]) && (lst.size() >= 4))
{
return 4;
}
return 0;
} // 删除x和x+1位置的一对牌
vector <char> delDUI(const vector <char> & lst, const int x)
{
vector <char> newLst; for (int i = 0; i < lst.size(); ++i)
{
if ((i != x) && (i != (x + 1)))
{
newLst.push_back(lst[i]);
}
} return newLst;
} // 删除起始位置为x的杠(4张牌)
vector <char> delGANG(const vector <char> & lst, const int x)
{
vector <char> newLst; for (int i = 0; i < lst.size(); ++i)
{
if ((i < x) || (i > x + 3))
{
newLst.push_back(lst[i]);
}
} return newLst;
} // 查找杠的位置
vector <int> findGangPos(const vector <char> & lst)
{
vector <int> pos;
int temp_pos = 0; if (lst.size() < 4)
{
return pos;
} for (int i = 0; i < lst.size() - 3; ++i)
{ if (lst[i] == lst[i + 3])
{
pos.push_back(temp_pos);
} ++temp_pos; // 位置更新
} return pos;
}

程序运行结果

程序从命令行参数取得输入数据,数据为一个字符串,代表一副牌。若这副牌达到胡牌条件,输出GOOD,否则输出BAD。

[C++] 麻将胡牌算法的更多相关文章

  1. 【洛谷5279】[ZJOI2019] 麻将(“胡牌自动机”上DP)

    点此看题面 大致题意: 给你13张麻将牌,问你期望再摸多少张牌可以满足存在一个胡的子集. 似乎ZJOI2019Day1的最大收获是知道了什么是胡牌? 一个显然的性质 首先我们要知道一个显然的性质,即对 ...

  2. 洗牌算法Fisher_Yates原理

    1.算法 http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 简单的原理如下图所示: 2.原理 总结下,洗牌算法Fisher_Yates ...

  3. C# 洗牌算法

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精   C#洗牌算法如下: class Program { ...

  4. js 随机数 洗牌算法

    function shuffle(arr){ var len = arr.length; for(var i = 0;i<len -1;i++) { var idx = Math.floor(M ...

  5. Fisher–Yates shuffle 洗牌算法(zz)

    1,缘起 最近工作上遇到一个问题,即将一组数据,比如[A,B,C,D,E]其中的两个B,E按随机排列,其他的仍在原来的位置: 原始数组:[A,B,C,D,E] 随机字母:[B,D] 可能结果:[A,B ...

  6. 519. Random Flip Matrix(Fisher-Yates洗牌算法)

    1. 问题 给定一个全零矩阵的行和列,实现flip函数随机把一个0变成1并返回索引,实现rest函数将所有数归零. 2. 思路 拒绝采样 (1)先计算矩阵的元素个数(行乘以列),记作n,那么[0, n ...

  7. 《Algorithms算法》笔记:元素排序(3)——洗牌算法

    <Algorithms算法>笔记:元素排序(3)——洗牌算法 Algorithms算法笔记元素排序3洗牌算法 洗牌算法 排序洗牌 Knuth洗牌 Knuth洗牌代码 洗牌算法 洗牌的思想很 ...

  8. 洗牌算法shuffle

    对这个问题的研究始于一次在群里看到朋友发的洗牌面试题.当时也不知道具体的解法如何,于是随口回了一句:每次从剩下的数字中随机一个.过后找相关资料了解了下,洗牌算法大致有3种,按发明时间先后顺序如下: 一 ...

  9. 随机洗牌算法Knuth Shuffle和错排公式

    Knuth随机洗牌算法:譬如现在有54张牌,如何洗牌才能保证随机性.可以这么考虑,从最末尾一张牌开始洗,对于每一张牌,编号在该牌前面的牌中任意一张选一张和当前牌进行交换,直至洗到第一张牌为止.参考代码 ...

随机推荐

  1. dm8148 开发只boot启动参数vram=128简介

    显存 全称显示内存,即显示卡专用内存.显存对于显卡就好比内存对于整台电脑,地位非常重要,它负责存储显示芯片需要处理的各种数据.显存容量的大小.性能的高低,直接影响着电脑的显示效果.目前,工作站显卡常用 ...

  2. 数据库 : Mysql - 日常应用

    #登录MYSQL数据库 MYSQL -u root -p #显示所有数据库 SHOW databases; #显示当前数据库与应用程序间进行了多少条连接 SHOW processlist; #使用某一 ...

  3. week 6: kernel regression

    华盛顿大学 machine learning regression 第六周笔记. 普通的回归方法是基于training set的整体性进行训练的,如果训练数据集 具有明显的分段性,那么普通的回归方法预 ...

  4. 三个内置模块shutil logging hashlib config

    高级的 文件.文件夹.压缩包 处理模块 shutil.copyfileobj(fsrc, fdst[, length])将文件内容拷贝到另一个文件中 1 import shutil 2 3 shuti ...

  5. Lumen migration(数据库协同)

    建立迁移文件/新建表文件 php artisan make:migration create_users_table 添加字段 php artisan make:migration add_colum ...

  6. mvc ajax给control传值问题

    jquery中的ajax操作给后台传值 $.ajax({ type: 'POST',   url: '<%=Url.Action("test","testIndex ...

  7. c语言 常用知识点

    强制类型转换 (int)(x+y) 输入 scanf("a=%f,b=%f",&a,&b);  a=1,b=1 char a; a=getchar(); 输入一个字 ...

  8. Uva10972(RevolC FaeLoN)

    题目链接:传送门 题目大意:给你一副无向图,问至少加多少条边使图成为边双联通图 题目思路:tarjan算法+缩点(如果已经是双连通图就直接输出0) #include <iostream> ...

  9. SharePoint服务器端对象模型 之 访问网站和列表数据(Part 2)

    (二)列表(SPList) 列表是SharePoint中最为重要的数据容器,我们一般保存在SharePoint中的所有数据,都是保存在列表中(文档库也是一种列表),因此列表对象在SharePoint的 ...

  10. wamp设置mysql默认编码

    来自:http://www.cnsecer.com/5984.html wamp下MySQL的默认编码是Latin1,不支持中文,要支持中文的话需要把数据库的默认编码修改为gbk或者utf8. 这里推 ...