Tromino 谜题

题目 Tromino 谜题

Tromino指一个由棋盘上的三个1*1方块组成的 L 型骨牌。如何用 Tromino 覆盖一个缺少了了一个方块(可以在棋盘上任何位置)的2^n*2^n棋盘(下图展示了n=3情况)。除了这个缺失的方块,Tromino应该覆盖棋盘上的所有方块,Tromino可以任意转向但不能由重叠。

                              

设计内容及要求:

(1)为此问题设计一个分治算法,分析算法的时间复杂度;

(2)实现所设计的算法,并以图形化界面演示覆盖过程。

1问题分析与解决思路

通过对问题的分析,题目要求用分治法来解决该问题。该问题中缺失方块的位置是任意的,棋盘大小是2n次方(n为正整数)的矩阵,所以我们先来考虑最小规模即当n=1时的情况。这种情况下无论缺失的方块在哪个位置,我们只需要将剩下的三个方块填充就好,相当于放置一个骨牌。当n=2时,方块数为4*4,划分子问题前,我们先将棋盘分为四个象限,确定缺失方块的象限后,将其它三个象限距离中心位置最近的一个方块填充。此时我们再将其划分为四个方块数为2*2的矩阵,将已经填充的方块看作缺失方块,则每个小规模矩阵都有一个缺失方块,即它们是规模相同的子问题,依次递归最后将整个棋盘填充完整。如图2.1.3所示,将8*8棋盘中的123填充后,再将其划分为四个部分得到如图2.1.2所示的4*4棋盘,将4*4棋盘中的1,2,3方块填充后再划分得到如图2.1.1所示的2*2棋盘,填充后继续递归最终完成整个棋盘的填充。实际编程时可让相应的位置坐标赋值为k*ik为中点位置,i为象限,i取值为1-4,界面动画展示时根据不同的值用不同颜色显示方块即可实现不同骨牌的区分。

                                                                        

 图1.1 n=1时填充             图1.2  n=2 时填充及划分                                        1.3  n=3 时填充及划分

模型建立与算法描述

记棋盘为size*size的二维数组T,初始化为0,缺失方块位置为x,y,参数mn分别记录棋盘横向位置的开始和结束(列的取值范围,更确切的说n为纵向长度,即下标的最大值+1),参数l,r分别记录棋盘纵向位置的开始和结束(行的取值范围,rn

建立数学模型:

T[k][j-1]=T[k][j]=T[k-1][j]=1*k(x<ky<j)

T[k-1][j-1]=T[k][j]=T[k-1][j]=2*k(x>=ky<j)

T[k-1][j-1]=T[k][j-1]=T[k-1][j]=3*k,(x>=ky>=j

T[k-1][j-1]=T[k][j-1]=T[k][j]=4*k,(x<ky>=j

其中k=(m+n)/2j=(l+r)/2

我们将上述分析过程和解决的思路进一步归纳为以下步骤:

1)如果n-m>=2,执行步骤(2),否则结束过程。

2)将棋盘进行划分象限,(找寻中心位置即第一象限右下角坐标k,j),判断缺失方块的象限,将其它三个象限距离中心最近的位置T[k-1][j-1]T[k][j-1]T[k][j]T[k-1][j]中的与缺失方块不在一个象限的其余三个的值置为缺失方块象限与k的乘积。执行步骤(3

3)以象限划分将棋盘划分为四个等规模的子问题,将已赋值位置和缺失位置都看做缺失位置参数,相应象限范围作为参数m,n,l,r。分别对四个子问题重复上述步骤(1)(2

算法伪代码描述:

T=[[0 for i in range(size)] for i in range(size)]#定义二维0矩阵作为棋盘

算法 Tromino(m,n,l,r,x,y)

#输入:矩阵范围m,n,l,r及缺失位置坐标x,y

#采用分治思想将棋盘T进行赋值,得到仅有位置x,y值为0的棋盘矩阵T

    if n-m >=2:

        k=int((m+n)/2)

        j=int((l+r)/2)

#缺失位置在第一象限

        if x<k and y<j:

            T[k][j-1]=1*k

            T[k][j]=1*k

            T[k-1][j]=1*k

#将四个子问题递归

            Tromino(m,k,l,j,x,y)

            Tromino(k,n,l,j,k,j-1)

            Tromino(k,n,j,r,k,j)

            Tromino(m,k,j,r,k-1,j)  

#缺失位置在第二象限

        elif x>=k and y<j:

            T[k-1][j-1]=2*k

            T[k][j]=2*k

            T[k-1][j]=2*k

            Tromino(m,k,l,j,k-1,j-1)

            Tromino(k,n,l,j,x,y)

            Tromino(k,n,j,r,k,j)

            Tromino(m,k,j,r,k-1,j)

#缺失位置在第三象限       

        elif x>=k and y>=j:

            T[k-1][j-1]=3*k

            T[k][j-1]=3*k

            T[k-1][j]=3*k

            Tromino(m,k,l,j,k-1,j-1)

            Tromino(k,n,l,j,k,j-1)

            Tromino(k,n,j,r,x,y)

            Tromino(m,k,j,r,k-1,j)

#缺失位置在第四象限         

        else:

            T[k-1][j-1]=4*k

            T[k][j-1]=4*k

            T[k][j]=4*k

            Tromino(m,k,l,j,k-1,j-1)

            Tromino(k,n,l,j,k,j-1)

            Tromino(k,n,j,r,k,j)

            Tromino(m,k,j,r,x,y)

3 算法实现与复杂度分析

3.1 数据结构

python中的二维列表表示棋盘。分析题目可知棋盘是2^n阶方阵,使用python中的二维列表(类似于c语言中的二维数组)可以很方便的描述棋盘的特性,并且像c语言中的数组那样可以很容易的进行取某一位置的值的操作。

3.2 实现步骤

1)考虑最小规模即棋盘大小为2*2,此时n=0,m=2,l=0,r=2,假设x=0,y=1。此时中心位置k=(m+n)/2=1j=(l+r)/2=1,判断缺失位置在第一象限,将剩余的二、三、四象限的离中心最近位置进行赋值,完成一个骨牌的放置。在这种情况下,棋盘刚好填满。

2)当棋盘规模大于2*2时,此时只需在上述步骤放置好第一个骨牌后,将棋盘按象限划分为四个相同规模的子问题,分别重复执行上述操作即可

3.3 实现技巧

1)分治:将棋盘划分为多个相同规模的子问题,得到子问题的解,最终合并得到整个问题的解;

2)递归:用递归实现对划分的子问题求解,层层细分再层层整合最终求得问题的解。

3.4 时间、空间复杂度分析

1 时间复杂度分析

本题采用分治算法,每次将问题分为4个规模相同的子问题,每个子问题的基本操作为赋值运算(3次),经计算该算法下在棋盘大小为n(方块数为2^n*2^n)的情况下,基本操作次数为3*4^(n-2),时间效率类型属于Ω2^n)类型。

 

2 空间复杂度分析

在本题中,所使用的变量为二维列表,其它变量也不随运行规模的增大而增多,其空间复杂度为O(n^2+C),其中n表示棋盘真正的大小,即数组矩阵的行数或列数,C为常数。

4 程序实现及运行结果分析

4.1 程序实现(源码Python3.6)

from tkinter import *
from tkinter import ttk
from tkinter import scrolledtext
import time
import threading

class TrominoApp:
#变量、组件定义及初始化
def __init__(self,master):
self.speed=0.2
self.select=0
self.colors=['blue','red','green','brown','purple','pink','yellow','orange','gold','crimson','orchid','indigo']#颜色数组
#标签
self.lb1=Label(master,text="棋盘大小(2^n)",font=('微软雅黑',8),bg='Wheat')
self.lb1.place(x=130,y=30,anchor=NW)#定位
self.lb2=Label(master,text="缺失方块x坐标",font=('微软雅黑',8),bg='Wheat')
self.lb2.place(x=330,y=30,anchor=NW)
self.lb3=Label(master,text="缺失方块y坐标",font=('微软雅黑',8),bg='Wheat')
self.lb3.place(x=530,y=30,anchor=NW)
#文本框
self.t1=Entry(master,width=10,insertborderwidth=10)#输入文本框
self.t1.place(x=220,y=30)#设置文本框的位置
self.t2=Entry(master,width=10,insertborderwidth=10)
self.t2.place(x=420,y=30)
self.t3=Entry(master,width=10,insertborderwidth=10)
self.t3.place(x=620,y=30)
self.t4=scrolledtext.ScrolledText(master ,width=36,bg='lightyellow',
height=31,borderwidth = 3,font=('Arial',13),wrap=WORD)#滚动文本框
self.t4.place(x=10,y=70)
#按钮
self.bt1=Button(master,text="查看结果",width=8,height=1,bg='Plum',font=
('微软雅黑',8),command=self.run)#设置按钮及点击事件
self.bt1.place(x=460,y=630)
self.bt2=Button(master,text="动画演示",width=8,height=1,bg='Plum',font=
('微软雅黑',8),command=self.cartoon)#设置按钮及点击事件
self.bt2.place(x=600,y=630)
self.bt3=Button(master,text="重置",width=8,height=1,bg='Plum',font=
('微软雅黑',8),command=self.CliktheButton2)#设置按钮及点击事件
self.bt3.place(x=730,y=30)
self.bt4=Button(master,text="清空结果",width=8,height=1,bg='Plum',font=
('微软雅黑',8),command=self.clear)#设置按钮及点击事件
self.bt4.place(x=730,y=630)

#画布
self.canvas1 =Canvas(master ,
width = 512, # 指定Canvas组件的宽度
height = 512, # 指定Canvas组件的高度
bg = 'white') # 指定Canvas组件的背景色
self.canvas1.place(x=370,y=70)#画布定位

#============================"查看结果"按钮点击事件
def run(self):
self.select=0
self.CliktheButton1()
#============================"动画演示"按钮点击事件
def cartoon(self):
self.select=1
self.CliktheButton1()
#==========================="重置"按钮点击事件
def CliktheButton2(self):
#清空输入框
self.t1.delete('0','end')
self.t2.delete('0','end')
self.t3.delete('0','end')
#清空画布
x=ALL
self.canvas1.delete(x)
return 0

#==========================="清空结果"按钮点击事件
def clear(self):
self.t4.delete(1.0,END)
#清空画布
x=ALL
self.canvas1.delete(x)
return 0

#============================由run和cartoon调用
def CliktheButton1(self):
#获取输入值
self.n=int(self.t1.get())
self.x=int(self.t2.get())
self.y=int(self.t3.get())
if self.n<1:
self.t4.insert(END,'\n++++++++++++++++++++++++++++++++\n错误!!!棋盘大小必须大于0\n')
else:
self.size=2**self.n
#信息及结果显示
if self.x>=self.size or self.y>=self.size:
self.t4.insert(END,'\n++++++++++++++++++++++++++++++++\n错误!!!坐标越界\n')
else:
self.t4.insert(END,'\n++++++++++++++++++++++++++++++++\n棋盘大小:'+str(self.size)+'*'+str(self.size)+'\n'+'x:'+str(self.x)+' '+'y:'+str(self.y)+'\n')
self.cellwidth=round(int(self.canvas1['width'])/self.size)#设置方块大小
self.T=[[0 for i in range(self.size)] for i in range(self.size)]
t1=time.perf_counter()
self.Tromino(0,self.size,0,self.size,self.x,self.y)
t=time.perf_counter()-t1
self.t4.insert(END,'运行时间:'+str(t)+'\n')
#清空画布
x=ALL
self.canvas1.delete(x)
#结果演示
self.cellx=self.x*self.cellwidth
self.celly=self.y*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill="black",outline="black")
self.canvas1.update()#更新画布
time.sleep(0.3)
if self.select:
self.Display(0,self.size,0,self.size,self.x,self.y)#调用动画演示函数
else:
for i in range(self.size):
for j in range(self.size):
index=self.T[i][j]
if index==0:
color='black'
else:
color=self.colors[index%(len(self.colors)-1)]
self.cellx=i*self.cellwidth
self.celly=j*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")
self.canvas1.update()#更新画布
#============================动画演示函数由CliktheButton1调用
def Display(self,m,n,l,r,x,y):
k=int((m+n)/2)
j=int((l+r)/2)
if n-m >=2:
if x<k and y<j:#1--缺失位置在第一象限
#第二、三、四象限离中心最近位置置为1*k,相当于放置一个骨牌

color=self.colors[(1*k)%(len(self.colors)-1)]

self.cellx=k*self.cellwidth
self.celly=(j-1)*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")

self.cellx=k*self.cellwidth
self.celly=j*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")

self.cellx=(k-1)*self.cellwidth
self.celly=j*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")
self.canvas1.update()#更新画布
time.sleep(self.speed)
#递归 分为四个相同规模的棋盘 四个象限
self.Display(m,k,l,j,x,y)#1
self.Display(k,n,l,j,k,j-1)#2
self.Display(k,n,j,r,k,j)#3
self.Display(m,k,j,r,k-1,j)#4
elif x>=k and y<j:#2

color=self.colors[(2*k)%(len(self.colors)-1)]

self.cellx=(k-1)*self.cellwidth
self.celly=(j-1)*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")

self.cellx=(k-1)*self.cellwidth
self.celly=j*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")

self.cellx=k*self.cellwidth
self.celly=j*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")
self.canvas1.update()#更新画布
time.sleep(self.speed)

#递归
self.Display(m,k,l,j,k-1,j-1)#1
self.Display(k,n,l,j,x,y)#2
self.Display(k,n,j,r,k,j)#3
self.Display(m,k,j,r,k-1,j)#4
elif x>=k and y>=j:#3

color=self.colors[(3*k)%(len(self.colors)-1)]

self.cellx=(k-1)*self.cellwidth
self.celly=j*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")

self.cellx=(k-1)*self.cellwidth
self.celly=(j-1)*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")

self.cellx=k*self.cellwidth
self.celly=(j-1)*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")
self.canvas1.update()#更新画布
time.sleep(self.speed)
#递归
self.Display(m,k,l,j,k-1,j-1)#1
self.Display(k,n,l,j,k,j-1)#2
self.Display(k,n,j,r,x,y)#3
self.Display(m,k,j,r,k-1,j)#4
else:#4

color=self.colors[(4*k)%(len(self.colors)-1)]

self.cellx=(k-1)*self.cellwidth
self.celly=(j-1)*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")

self.cellx=k*self.cellwidth
self.celly=(j-1)*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")

self.cellx=k*self.cellwidth
self.celly=j*self.cellwidth
self.canvas1.create_rectangle(self.cellx,self.celly,self.cellx+self.cellwidth,self.celly+self.cellwidth,fill=color,outline="black")
self.canvas1.update()#更新画布
time.sleep(self.speed)
#递归
self.Display(m,k,l,j,k-1,j-1)#1
self.Display(k,n,l,j,k,j-1)#2
self.Display(k,n,j,r,k,j)#3
self.Display(m,k,j,r,x,y)#4

##############################核心算法 分治法骨牌填充棋盘
def Tromino(self,m,n,l,r,x,y):
if n-m >=2:
#定位中心位置
k=int((m+n)/2)
j=int((l+r)/2)
if x<k and y<j:#1--缺失位置在第一象限
#第二、三、四象限离中心最近位置置为1*k,相当于放置一个骨牌
self.T[k][j-1]=1*k#2
self.T[k][j]=1*k#3
self.T[k-1][j]=1*k#4
#递归 分为四个相同规模的棋盘 四个象限
self.Tromino(m,k,l,j,x,y)#1
self.Tromino(k,n,l,j,k,j-1)#2
self.Tromino(k,n,j,r,k,j)#3
self.Tromino(m,k,j,r,k-1,j)#4
elif x>=k and y<j:#2
self.T[k-1][j-1]=2*k#1
self.T[k][j]=2*k#3
self.T[k-1][j]=2*k#4
#递归
self.Tromino(m,k,l,j,k-1,j-1)#1
self.Tromino(k,n,l,j,x,y)#2
self.Tromino(k,n,j,r,k,j)#3
self.Tromino(m,k,j,r,k-1,j)#4
elif x>=k and y>=j:#3
self.T[k-1][j-1]=3*k#1
self.T[k][j-1]=3*k#2
self.T[k-1][j]=3*k#4
#递归
self.Tromino(m,k,l,j,k-1,j-1)#1
self.Tromino(k,n,l,j,k,j-1)#2
self.Tromino(k,n,j,r,x,y)#3
self.Tromino(m,k,j,r,k-1,j)#4
else:#4
self.T[k-1][j-1]=4*k#1
self.T[k][j-1]=4*k#2
self.T[k][j]=4*k#3
#递归
self.Tromino(m,k,l,j,k-1,j-1)#1
self.Tromino(k,n,l,j,k,j-1)#2
self.Tromino(k,n,j,r,k,j)#3
self.Tromino(m,k,j,r,x,y)#4
#=================================主函数,界面窗口创建及调用
def main():
root=Tk()
root.title('Tromino演示系统')#设置主窗口标题
root.configure(background='lightblue')
#geometry(‘axb+c+d’) axb代表初始化时主窗口的大小,c,d代表了初始化时窗口所在位置
root.geometry('900x800+10+5')
app=TrominoApp(root)
root.resizable(0,0)
root.mainloop()
#==================================程序运行入口
if __name__ == "__main__" : main()

    

4.2 测试及运行结果分析

1 测试分析

如图4.2.1-4.2.6所示为本题的运行结果截图。其中黑色方块表示缺失方块,为区分每个骨牌,用同种颜色表示一个骨牌,不同骨牌用不同颜色表示。

4.2.1所示为输入棋盘大小为0时系统显示的错误信息提示,图4.2.2为棋盘大小为1(即2*2),缺失位置为(11)时,系统显示结果,右边画布显示图形可视化,显示结果正确(由于列表下标从0开始计数因此,坐标(11)对应第二行第二列);左边信息框显示棋盘信息及运行花费时间,此时花费时间为1.650000000008589*10e-5s。图4.2.3--4.2.6为改变棋盘大小和缺失位置时演示结果,结果显示运行时间与棋盘大小成正相关。

   

                        4.2.1                                                       4.2.2                                                      图4.2.3

      

                      图4.2.4                                                         图4.2.5                                                      图4.2.6 

2 运行结果分析

2.4.2.1和图2.4.2.9为某次执行时设置不同棋盘大小得到的运行时间之间的比较。当棋盘大小在较小范围内时,用时变化较小,随后(棋盘大小不断增大)就呈现指数爆炸增长。

4.2.1 棋盘大小-运行时间表

 

 

图4.2.9 棋盘大小-运行时间折线图 

 

 

 

猜你喜欢

转载自www.cnblogs.com/gangpei/p/12611741.html