[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张牌,如何洗牌才能保证随机性.可以这么考虑,从最末尾一张牌开始洗,对于每一张牌,编号在该牌前面的牌中任意一张选一张和当前牌进行交换,直至洗到第一张牌为止.参考代码 ...
随机推荐
- JavaScript中trim 方法实现
Java中的 String 类有个trim() 能够删除字符串前后的空格字符.jQuery中也有trim()方法能够删除字符变量前后的字符串. 可是JavaScript中却没有对应的trim() 方法 ...
- git 显示多个url地址推送
前提 一般来说,我们为git增加远程库,一般都是git remote add origin <url> ( 你可以使用真实的地址来代替 \<url\> ) 但是你可能想要把你的 ...
- db2 blob EMPTY_BLOB()
--针对BLOG类型的列,写入一个0长度的字符串
- CMake error:System Error:No such file or directory CMake error:Could not open file for write in copy operation xxxx.ros_Config.cmake.tmp.
微型电脑或嵌入式与电脑还是有点不同的,在微型电脑上ros indigo 版本下利用catkin编译如果你遇到如下错误: CMake error:System Error:No such file or ...
- ASCII码表(0-255)
- Javascript间歇调用和超时调用
间歇调用:每隔指定的时间就执行一次代码 超时调用:在指定的时间过后执行代码 HTML Code 12345678910111213141516171819 <!DOCTYPE html& ...
- 1624 取余最长路(set)
1624 取余最长路 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 佳佳有一个n*m的带权矩阵,她想从(1,1)出发走到(n,m)且只能往右往下移动,她能得到的娱 ...
- The E-pang Palace(暴力几何)
//暴力的几何题,问,n个点可以组成的矩形,不相交,可包含的情况下,最大的面积,还有就是边一定与 x y 轴平行,所以比较简单了 //暴力遍历对角线,搜出所有可能的矩形,然后二重循环所有矩形,判断一下 ...
- 字符串 转 时间戳 unix_timestamp('2018-07-01 0:0:0')
SELECT FROM_UNIXTIME(createtime,'%Y%m%d') AS d, s.app,SUM(o.val) FROM orders o LEFT JOIN app_type s ...
- 在Nuxt中使用 Highcharts
npm进行highchars的导入,导入完成后就可以进行highchars的可视化组件开发了 npm install highcharts --save 1.components目录下新建一个char ...