能看懂的3D Tic-Tac-Toe问题_gruobi求解

问题描述

简单来说就是在下面333的立方体中插入14个小球,让球(或者非球)共线的数量最小。
那这里再补充解释一下什么叫共线:
“三个小球只要同一行,或者同一列,或者同一对角线”
在这里插入图片描述

问题建模

模型是比较显而易见的,主要是辅助决策变量Lines的处理。
在这里插入图片描述

官方解答

意图比较明显,枚举所有的共线情况。但是不知道有没有人看懂了三个for循环里是什么鬼?我是看麻了。。。。。

#-*-coding:utf-8-*-
import gurobipy as gp
from gurobipy import GRB

# tested with Python 3.7.0 & Gurobi 9.0
lines = []
size = 3

for i in range(size):
    for j in range(size):
        for k in range(size):
            if i == 0:
                lines.append(((0,j,k), (1,j,k), (2,j,k)))
            if j == 0:
                lines.append(((i,0,k), (i,1,k), (i,2,k)))
            if k == 0:
                lines.append(((i,j,0), (i,j,1), (i,j,2)))
            if i == 0 and j == 0:
                lines.append(((0,0,k), (1,1,k), (2,2,k)))
            if i == 0 and j == 2:
                lines.append(((0,2,k), (1,1,k), (2,0,k)))
            if i == 0 and k == 0:
                lines.append(((0,j,0), (1,j,1), (2,j,2)))
            if i == 0 and k == 2:
                lines.append(((0,j,2), (1,j,1), (2,j,0)))
            if j == 0 and k == 0:
                lines.append(((i,0,0), (i,1,1), (i,2,2)))
            if j == 0 and k == 2:
                lines.append(((i,0,2), (i,1,1), (i,2,0)))
lines.append(((0,0,0), (1,1,1), (2,2,2)))
lines.append(((2,0,0), (1,1,1), (0,2,2)))
lines.append(((0,2,0), (1,1,1), (2,0,2)))
lines.append(((0,0,2), (1,1,1), (2,2,0)))
model = gp.Model('Tic_Tac_Toe')
isX = model.addVars(size, size, size, vtype=GRB.BINARY, name="isX")
isLine = model.addVars(lines, vtype=GRB.BINARY, name="isLine")
x14 = model.addConstr(isX.sum() == 14)
for line in lines:
    model.addGenConstrIndicator(isLine[line], False, isX[line[0]] + isX[line[1]] + isX[line[2]] >= 1)
    model.addGenConstrIndicator(isLine[line], False, isX[line[0]] + isX[line[1]] + isX[line[2]] <= 2)
model.setObjective(isLine.sum())
model.optimize()
import matplotlib.pyplot as plt
# % matplotlib
# inline

fig, ax = plt.subplots(1, 3, figsize=(10, 5))
for i in range(3):
    ax[i].grid()
    ax[i].set_xticks(range(4))
    ax[i].set_yticks(range(4))
    ax[i].tick_params(labelleft=False, labelbottom=False)

for cell in isX.keys():
    if isX[cell].x > 0.5:
        ax[cell[0]].add_patch(plt.Rectangle((cell[1], cell[2]), 1, 1))

plt.show()

自己的方法

开玩笑,我可是学过高等数学的男人。哦,好像跟高等数学没多大关系。但是,三点共线的判断方法我还是知道的,方法如下:
对于空间中三点x,y,z,我们可以通过三点形成的两个向量是否平行判断是否共线。
第一步:得到向量xy=x-y 和向量xz=x-z。
第二步:计算xy 和xy的叉乘。
第三步:如果叉乘结果为0,则两向量共线(也就是我们三个for循环中的第二个if)。

注意: 为了避免((1,1,0),(1,1,1),(1,1,2))和((1,1,0),(1,1,2),(1,1,1))这种由于位置不同但是表示同一条线的点被同时放进allLinesList,我们将[x,y,z]转化为了set的数据结构,因为set是不考虑元素的位置关系的。

#-*-coding:utf-8-*-
import numpy as np
import gurobipy as gp
from gurobipy import GRB
lenth=[0,1,2]
width=[0,1,2]
height=[0,1,2]

model = gp.Model('Tic_Tac_Toe')
allX = model.addVars(lenth, width, height, vtype=GRB.BINARY, name="allX")
# 同一水平线,或者垂直线,或者对角线上为一个直线,总过32条线
allPoints=allX.keys()

allLinesList=[]
for x in allPoints:
    for y in allPoints:
        for z in allPoints:
            if x!=y and x!=z and y!=z:
                linexy=[item1-item2 for item1,item2 in zip(x,y)]
                linexz = [item1 - item2 for item1, item2 in zip(x, z)]
                if np.cross(linexy, linexz).any() == 0 and set([x, y, z]) not in allLinesList:
                    allLinesList.append(set([x, y, z]))
allLinesList=[tuple(item) for item in allLinesList]
allLines=model.addVars(allLinesList,vtype=GRB.BINARY,name="allLine")

model.addConstr(gp.quicksum(allX)==14)

#将allLines和x联系起来
for line in allLinesList:
    model.addGenConstrIndicator(allLines[line],0,(allX[line[0]]+allX[line[1]]+allX[line[2]])>=1)
    model.addGenConstrIndicator(allLines[line], 0, (allX[line[0]] + allX[line[1]] + allX[line[2]]) <=2)

model.setObjective(gp.quicksum(allLines),GRB.MINIMIZE)


import matplotlib.pyplot as plt
model.optimize()

fig, ax = plt.subplots(1, 3, figsize=(10, 5))
for i in range(3):
    ax[i].grid()
    ax[i].set_xticks(range(4))
    ax[i].set_yticks(range(4))
    ax[i].tick_params(labelleft=False, labelbottom=False)

for cell in allX.keys():
    if allX[cell].x > 0.5:
        ax[cell[0]].add_patch(plt.Rectangle((cell[1], cell[2]), 1, 1))

plt.show()

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值