[C++] 麻将胡牌算法
麻将的玩法规则众多,核心的玩法是一致的,本文将根据联发科2017年编程挑战赛的复赛题规则来实现。
牌的表示方式
ABCDEFGHI
代表一到九萬,abcdefghi
代表一到九条,123456789
代表一到九饼
三种胡牌牌型
- 普通牌型,14张牌,形如:
3+3+3+3+2
。其中数字2
代表两张相同的牌可成一组,形如XX
。数字3
代表三张相同或者连续的牌可成一组,形如XXX
、XYZ
。- 龙七对,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++] 麻将胡牌算法的更多相关文章
- 【洛谷5279】[ZJOI2019] 麻将(“胡牌自动机”上DP)
点此看题面 大致题意: 给你13张麻将牌,问你期望再摸多少张牌可以满足存在一个胡的子集. 似乎ZJOI2019Day1的最大收获是知道了什么是胡牌? 一个显然的性质 首先我们要知道一个显然的性质,即对 ...
- 洗牌算法Fisher_Yates原理
1.算法 http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 简单的原理如下图所示: 2.原理 总结下,洗牌算法Fisher_Yates ...
- C# 洗牌算法
最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精 C#洗牌算法如下: class Program { ...
- js 随机数 洗牌算法
function shuffle(arr){ var len = arr.length; for(var i = 0;i<len -1;i++) { var idx = Math.floor(M ...
- Fisher–Yates shuffle 洗牌算法(zz)
1,缘起 最近工作上遇到一个问题,即将一组数据,比如[A,B,C,D,E]其中的两个B,E按随机排列,其他的仍在原来的位置: 原始数组:[A,B,C,D,E] 随机字母:[B,D] 可能结果:[A,B ...
- 519. Random Flip Matrix(Fisher-Yates洗牌算法)
1. 问题 给定一个全零矩阵的行和列,实现flip函数随机把一个0变成1并返回索引,实现rest函数将所有数归零. 2. 思路 拒绝采样 (1)先计算矩阵的元素个数(行乘以列),记作n,那么[0, n ...
- 《Algorithms算法》笔记:元素排序(3)——洗牌算法
<Algorithms算法>笔记:元素排序(3)——洗牌算法 Algorithms算法笔记元素排序3洗牌算法 洗牌算法 排序洗牌 Knuth洗牌 Knuth洗牌代码 洗牌算法 洗牌的思想很 ...
- 洗牌算法shuffle
对这个问题的研究始于一次在群里看到朋友发的洗牌面试题.当时也不知道具体的解法如何,于是随口回了一句:每次从剩下的数字中随机一个.过后找相关资料了解了下,洗牌算法大致有3种,按发明时间先后顺序如下: 一 ...
- 随机洗牌算法Knuth Shuffle和错排公式
Knuth随机洗牌算法:譬如现在有54张牌,如何洗牌才能保证随机性.可以这么考虑,从最末尾一张牌开始洗,对于每一张牌,编号在该牌前面的牌中任意一张选一张和当前牌进行交换,直至洗到第一张牌为止.参考代码 ...
随机推荐
- dm8148 开发只boot启动参数vram=128简介
显存 全称显示内存,即显示卡专用内存.显存对于显卡就好比内存对于整台电脑,地位非常重要,它负责存储显示芯片需要处理的各种数据.显存容量的大小.性能的高低,直接影响着电脑的显示效果.目前,工作站显卡常用 ...
- 数据库 : Mysql - 日常应用
#登录MYSQL数据库 MYSQL -u root -p #显示所有数据库 SHOW databases; #显示当前数据库与应用程序间进行了多少条连接 SHOW processlist; #使用某一 ...
- week 6: kernel regression
华盛顿大学 machine learning regression 第六周笔记. 普通的回归方法是基于training set的整体性进行训练的,如果训练数据集 具有明显的分段性,那么普通的回归方法预 ...
- 三个内置模块shutil logging hashlib config
高级的 文件.文件夹.压缩包 处理模块 shutil.copyfileobj(fsrc, fdst[, length])将文件内容拷贝到另一个文件中 1 import shutil 2 3 shuti ...
- Lumen migration(数据库协同)
建立迁移文件/新建表文件 php artisan make:migration create_users_table 添加字段 php artisan make:migration add_colum ...
- mvc ajax给control传值问题
jquery中的ajax操作给后台传值 $.ajax({ type: 'POST', url: '<%=Url.Action("test","testIndex ...
- c语言 常用知识点
强制类型转换 (int)(x+y) 输入 scanf("a=%f,b=%f",&a,&b); a=1,b=1 char a; a=getchar(); 输入一个字 ...
- Uva10972(RevolC FaeLoN)
题目链接:传送门 题目大意:给你一副无向图,问至少加多少条边使图成为边双联通图 题目思路:tarjan算法+缩点(如果已经是双连通图就直接输出0) #include <iostream> ...
- SharePoint服务器端对象模型 之 访问网站和列表数据(Part 2)
(二)列表(SPList) 列表是SharePoint中最为重要的数据容器,我们一般保存在SharePoint中的所有数据,都是保存在列表中(文档库也是一种列表),因此列表对象在SharePoint的 ...
- wamp设置mysql默认编码
来自:http://www.cnsecer.com/5984.html wamp下MySQL的默认编码是Latin1,不支持中文,要支持中文的话需要把数据库的默认编码修改为gbk或者utf8. 这里推 ...