参考
回溯算法团灭排列/组合/子集问题
回溯算法最佳实践:合法括号生成
查表思路(无自成顺,无牌的位置)
一开始以为这是个坑(指视频),思路和lua其实都不错,代码上有学习和借鉴
对比 查表法1
查表1 | 查表2 | |
---|---|---|
生成key值方式 | 把手牌抽象压缩转换 | 先对手牌颜色分类,然后再对手牌抽象转换 |
缺点 | 过于抽象,特别是要找到最大的百搭,还有字成顺,一个文件数据量过大 | —— |
优点 | —— | 分而治之,减少单个文件量,更简单的满足一些需求 |
生成表的结构(Key,Value)
- Key
同种颜色牌的数量组成 - Value (借鉴 查表1)
用uint32_t 存储,记录一些我需要知道的牌的信息- 开始bit位 占据的bit位 值的范围 对子的数量 0 4 (0~1) 对子的值 4 4 (0~9) 最大的百搭值 8 4 (0~9) 百搭的值1 12 4 (0~9) 百搭的值2 16 4 (0~9) 百搭的值3 20 4 (0~9) 百搭的值3 24 4 (0~9)
例:
能胡的手牌 (0x12,0x12,0x12),(0x21,0x22,0x23,0x27,0x28,0x29),(0x32,0x33,0x34),(0x43,0x43),
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | key | value | |
---|---|---|---|---|---|---|---|---|---|---|---|
万 | 0 | 3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 30,000,000 | 0 |
筒 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 111,000,111 | 0 |
条 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 11,100,000 | 0 |
字 | 0 | 0 | 2 | 0 | 0 | 0 | 0 | - | - | 20,000 | 0011 0001(49) |
生成所有能胡表(用回溯法)
先了解下dfs, 跟着参考两篇讲dfs文章回溯算法团灭排列/组合/子集问题
和回溯算法最佳实践:合法括号生成先了解下dfs的解题思路
DFS实践 - 简单版基础麻将胡牌所有可能
获取所有生成代码和表
生成基础胡表
def get_base_table():
e_list = [0, 0, 0, 0, 0, 0, 0, 0, 0]
gen_table_sub(e_list,0,0)
#生成所有胡牌可能
def gen_table_sub(e_list,count,value):
if count > 3:
return;
for i in range(0,16):
if i < 9:
if e_list[i] > 1 : continue
e_list[i] += 3
else:
index = i - 9 # 大于9 的用来加顺子
if e_list[index] >= 4 or e_list[index + 1] >= 4 or e_list[index + 2] >= 4:
continue
e_list[index] += 1
e_list[index + 1] += 1
e_list[index + 2] += 1
add_table(e_list,value)
gen_table_sub(e_list,count + 1,value)
if i < 9:
e_list[i] -= 3
else:
index = i - 9
e_list[index] -= 1
e_list[index + 1] -= 1
e_list[index + 2] -= 1
3.用add_table()加入表中
base_table = {}
eye_base_table = {}
def add(key,value):
if key % 3 == 0:
if key not in base_table:
base_table.setdefault(key, [])
base_table[key] = 0
else:
if key not in eye_base_table:
eye_base_table.setdefault(key, [])
eye_base_table[key] = value
def add_table(e_list,value):
key_num = 0
for i in range(0,9):
key_num = key_num * 10 + e_list[i]
#控制重复
if key_num in ttt:
return
ttt.setdefault(key_num, True)
add(key_num,value)
4.再依次对9个空格加上对子
# 生成带对子的基础胡表
def gen_eye_base_table():
e_list = [0, 0, 0, 0, 0, 0, 0, 0, 0]
for i in range(0,9):
e_list[i] = 2
value = 1 + ((i + 1) << 4) + (0 << 8)
add_table(e_list,value)
gen_table_sub(e_list,0,value)
e_list[i] = 0
2.百搭基础胡牌表
1.对基础胡牌每一种胡牌结果进行减牌,少一张牌就是多一个癞子
也就是对add_table() 方法进行修改,再之前的基础上再减牌,改为parse_table()
def parse_table(cards,eye,value,value_count):
if not check_add(cards,0,eye,value):
return
parse_table_sub(cards,1,eye,value,value_count)
2.parse_table_sub() 递归找到所有百搭的情况(# 注释的是计算最大百搭值)
def parse_table_sub(cards,baida_num,eye,value,value_count):
# tmp_value = copy.deepcopy(value)
# tmp_value_count = copy.deepcopy(value_count)
for i in range(0,9):
if cards[i] == 0:
continue
cards[i] -= 1
tmp_value[0] |= (i + 1) << value_count[0]
tmp_value_count[0] += 4
if not check_add(cards,baida_num,eye,tmp_value):
# flag, last_value = get_last_key_value(cards, baida_num, eye)
# if flag == True:
# tt_value = copy.deepcopy(tmp_value)
# maxBaiDaCard = 0
# for j in range(0, 4):
# baiDaCard = (tt_value[0] >> (12 + j * 4)) & 0xF
# if baiDaCard > maxBaiDaCard:
# maxBaiDaCard = baiDaCard
#
# tt_value[0] |= (maxBaiDaCard << 8)
# last_max_value = last_value >> 8 & 0xF
# max_value = tt_value[0] >> 8 & 0xF
# if max_value > last_max_value:
# add_not_first_time(cards, baida_num, eye, tt_value)
cards[i] += 1
tmp_value = copy.deepcopy(value)
tmp_value_count = copy.deepcopy(value_count)
continue
if baida_num < 4:
parse_table_sub(cards,baida_num + 1,eye,tmp_value,tmp_value_count)
cards[i] += 1
# tmp_value = copy.deepcopy(value)
# tmp_value_count = copy.deepcopy(value_count)
3.用check_add() 存入表中(# 注释的是计算最大百搭值)
def add_key(key,baida_num,eye,value):
if eye == True:
if key not in eye_table[baida_num]:
eye_table[baida_num].setdefault(key, [])
eye_table[baida_num][key] = value[0]
else:
if key not in table[baida_num]:
table[baida_num].setdefault(key, [])
table[baida_num][key] = value[0]
def check_add(cards,baida_num,eye,value):
key = 0
for i in range(0,9):
key = key * 10 + cards[i]
if key == 0:
return False
nor_table = {}
if eye == False:
nor_table = baida_table[baida_num]
else:
nor_table = baida_eye_table[baida_num]
if key in nor_table:
return False
nor_table.setdefault(key,True)
# for i in range(0,9):
# if cards[i] > 4:
# return True
#
# tmp_value = copy.deepcopy(value)
# maxBaiDaCard = 0
# for i in range(0, 4):
# baiDaCard = (tmp_value[0] >> (12 + i * 4)) & 0xF
# if baiDaCard > maxBaiDaCard:
# maxBaiDaCard = baiDaCard
#
# tmp_value[0] |= (maxBaiDaCard << 8)
add_key(key,baida_num,eye,tmp_value)
return True
3.字成顺胡牌表
这个思路跟其他的不一样
1.把字牌[0,0,0,0,0,0,0]拆成两部分[0,0,0,0],[0,0,0]
2.前面4个是风牌的,后面是箭牌
3.获得箭牌一个顺子的可能或者单个顺子的可能 同理可得 风牌
# 风牌
base_feng_tb = [
[0,0,0,0],
[0,1,1,1],
[1,0,1,1],
[1,1,0,1],
[1,1,1,0],
[3,0,0,0],
[0,3,0,0],
[0,0,3,0],
[0,0,0,3]
]
# 箭牌
base_jian_tb = [
[0,0,0],
[1,1,1],
[3,0,0],
[0,3,0],
[0,0,3]
]
4.对base_feng_tb 排列组合,我们可以就拿一个列表项,两个,也可以全部相加,可以把问题转换成 9个拿一个的可能,9个拿二个的可能 一直到 9个拿9个的可能,这样就得到所有胡牌可能的风牌
(同理得到 所有胡牌可能的箭牌)
5.根据不能超过4,把胡牌可能的风牌和箭牌拼凑成一个key即可
判断是否是胡牌
用伪代码来演示(用unordered_map存储)
游戏初始化时候读取表,只读一次
生成手牌数量列表
if 没有百搭 :
if 字牌的key不存在:
return False
elif 万牌的key不存在:
return False
elif 筒牌的key不存在:
return False
elif 条牌的key不存在:
return False
return True
else:
对子数量dd
万牌的key需要a个百搭,有对子dd+=1
筒牌的key需要b个百搭,有对子dd+=1
条牌的key需要c个百搭,有对子dd+=1
字牌的key需要d个百搭,有对子dd+=1
总共的百搭为n
if dd == 0:#没有将
if (a + b + c + d) + 2 <= n:
return True
return False
elif dd == 1: # 有将
if (a + b + c + d) <= n:
return True
return False
else : # 有多个对子,需要 对子 - 1 个百搭
if (a+b+c+d) + dd - 1 <= n:
return True
return False