前言:
博主16年底开始从事休闲类麻将游戏开发,开始接触赖子算法时,当时互联网找不到任何资料,于是自己琢磨了三天开始尝试写出第一个算法并商用至今,开源给行业内其他公司使用,其中思路部分来源于《算法导论》机械工业第三版第16章2节: 贪心算法原理。
关于贪心
贪心算法就是做出一系列选择来使原问题达到最优解。在每一个决策点,都是做出当前看来的最优选择,比如在活动选择问题里面,我们总是在一个问题的基础上选择结束时间最早的活动,之后再在剩下活动的基础上选出结束时间最早的活动,以此类推,直到没有活动可以进行选择。但是遗憾的是这种算法并不是总能得到最优解,并且是否能得到最优解还取决于对于贪心策略的选择。
一般来说,设计贪心算法涉及到下面几个步骤:
- 确定问题的最优子结构
- 基于问题的最优子结构设计一个递归算法
- 证明我们做出的贪心选择,只剩下一个子问题
- 证明贪心选择总是安全的
- 设计一个递归算法实现贪心策略
- 将贪心算法转化为迭代算法
到目前为止已经写过三款不同的赖子算法,分别为:
1. 递归贪心算法 (<1ms级 , 复杂度为:最好O(n),最差O(n^3) )
2. 优化型迭代贪心算法 ( us级 , 复杂度为:最好O(n),最差O(n^2) )
3. 哈希表查找 (us级 , 复杂度为:O(1) )
以上所有版本均支持多赖子,哪怕手牌是23张,全是赖子,均不影响复杂度,差异甚小
在征得源项目老板的同意下,分享第一种算法给游戏爱好者们学习与进步,废话少说,直接上代码如下:
-----------------------------------------------------------------------------------------------------------------------
//会牌核心算法递归版本, 由原胡牌算法演进而来,检测更全面 by iori 2017/1/10
bool laizi_hupai_base(int *pi_mj, int i_num, int *pi_select,int laizi_val)
{
//pi_select[]用于在递归调用时记录已经访问过的牌
int i,j,k;
int i_max,i_min,i_mid;
int i_mark1,i_mark2;
int i_have3=0 ,laizi_num = 0;
for(i=0; i<i_num; i++)
{
if( pi_select[i] )
continue;
else
i_mark1 = i;
for( j=i+1; j<i_num; j++)
{
if( i == j )
continue;
if(pi_select[j])
continue;
else
i_mark2 = j;
for( k=j+1; k<i_num; k++)
{
if( i==k || j==k )
continue;
if( pi_select[k] )
continue;
i_have3 = 1;
laizi_num = 0;
i_max = std::max(pi_mj[i], std::max(pi_mj[j], pi_mj[k]));
i_min = std::min(pi_mj[i], std::min(pi_mj[j], pi_mj[k]));
i_mid = (i_max + i_min) / 2;
//判断选中的是否为赖子,这里传入多个val,可以支持多赖
if (pi_mj[i] == laizi_val) laizi_num++;
if (pi_mj[j] == laizi_val) laizi_num++;
if (pi_mj[k] == laizi_val) laizi_num++;
if (laizi_num >= 2) //能成有会刻或者顺,默认刻
{
// do something 根据你的项目需求可以保存数据
}
else
if( (pi_mj[i] == pi_mj[j] && pi_mj[i] == pi_mj[k]) || (laizi_num == 1 && (pi_mj[i] == pi_mj[j] || pi_mj[j] == pi_mj[k] || pi_mj[i] == pi_mj[k])) ) //表示能组成刻子
{
// do something
}
else if (laizi_num == 1)//判断是否能成有会顺
{
if(pi_mj[i] == laizi_val)
{
if(pi_mj[j] - pi_mj[k] != -1 && pi_mj[j] - pi_mj[k] != -2 && pi_mj[j] - pi_mj[k] != 1 && pi_mj[j] - pi_mj[k] != 2 )
continue;
//这里根据项目不同的牌值区分 中发白与风牌过滤掉,博主这个项目没有,就不补上了
}else if(pi_mj[j] == laizi_val)
{
if(pi_mj[i] - pi_mj[k] != -1 && pi_mj[i] - pi_mj[k] != -2 && pi_mj[i] - pi_mj[k] != 1 && pi_mj[i] - pi_mj[k] != 2 )
continue;
}else if(pi_mj[k] == laizi_val)
{
if(pi_mj[i] - pi_mj[j] != -1 && pi_mj[i] - pi_mj[j] != -2 && pi_mj[i] - pi_mj[j] != 1 && pi_mj[i] - pi_mj[j] != 2 )
continue;
}
}else
if( i_max-i_min == 2 && ( pi_mj[i] == i_mid || pi_mj[j] == i_mid || pi_mj[k] == i_mid)) //表示能组成顺子
{
// do something
}else
continue;
//记录下来 下面递归
pi_select[i] = pi_select[j] = pi_select[k] = 1;
if( !laizi_hupai_base(pi_mj, i_num, pi_select,laizi_val) )
{
pi_select[i] = pi_select[j] = pi_select[k] = 0; //递归一次结束,返回
continue;
}
//选择的3张成刻、顺,而剩下的和了
return true;
}
//只剩两张牌, 相等 或者 其中一张为癞子时,满足牌型
if( (i_have3 == 0) &&( pi_mj[i_mark1] == pi_mj[i_mark2] || pi_mj[i_mark1] == laizi_val || pi_mj[i_mark2] == laizi_val ))
{
return true; //这里不退出,可以保存下来计算出所有能胡的牌型
}
}
}
return false;
}
------------------------------------------------------------------ end by Iori
算法好不好,跑一跑就知道,打上time log就明了,如有疑问,欢迎留言,支持原创,支持开源!