第五次实验报告
程序语言: python
姓名: unicorn
学号: 12345678910
日期:2023/5/20
一、 问题重述
给定一块长为L,宽为W的石板,现在需要从石板上分别切割出n个长度为li,宽度为wi的石砖。切割的规则是石砖的长度方向与石板的长度方向保持一致,同时满足一刀切的约束(即刀无法拐弯,无法中途停下)。问如何切割使得所使用的石材利用率最高?
其他要求:回溯算法。任给一个输入实例,能输出切割所需要实际高度,能用图形演示切割的过程。
二、 问题分析
这里我只考虑算最小长度(高度),不像上次还算了最大面积,所以这次是先将石板按照长度/高度从高到低排列。然后为了实现最后可视化,定义了一个nowstone数列和每次到底更新一个beststone来记录当前和最佳的石板的x和y坐标。每层的Backtracking_cut实现判断石板是否增加高度,不增加高度则在剩余面积列表remainlist中寻找一个可容纳的进行切割,否则则增加高度切割,每次切割前后都会保存状态以便回溯。因为这个石板面积不好回溯,或者是我对回溯算法的造诣还不够深。所以这个限界函数我只找到了minheight + lis[index].height <= bestheight。当满足这个条件时可以增加高度。否则要么要放在剩余面积要么就直接return。约束函数好像没有,但是可以找到相反的约束函数,虽然有点奇怪。就是找剩余面积中有没有可以容纳的如果找不到那么就只能放在最左边增加高度或者return。
三、 伪代码
切割函数伪代码如下:
def Backtracking_cut(index):
if index > num - 1:
if minheight < bestheight:
bestheight = minheight
beststone = nowstone.copy()
return
# 备份数据
if C(index):
# 更新数据
Backtracking_cut(index + 1)
# 恢复数据
if minheight + lis[index].height <= bestheight:
# 更新数据
Backtracking_cut(index + 1)
# 恢复数据
elif minheight + lis[index].height <= bestheight: #
# 更新数据
Backtracking_cut(index + 1)
# 恢复数据
else:
return
四、代码
代码如下:
from typing import List, Any
import turtle
import time
import copy
Benchmark = 1 # 默认为benchmark数据初始量为1
number = -1
# 放大倍率:为了让turtle看得更清楚而进行放大
N = 2
# 基准,将石板左小角往左下角移动
x_datum = 300
y_datum = 200
Length = 1000 # 无数据默认100000
minheight = 0
bestheight = 50000000
class stone: # 结构体
def __init__(self):
self.height = 0
self.width = 0
self.area = 0
self.visited = False
self.used = True
self.x = 0
self.y = 0
class remain: # 结构体
def __init__(self):
self.x = 0
self.y = 0
self.leftwidth = 0
self.leftheight = 0
def toString(self):
return str(self.leftwidth) + " " + str(self.leftheight)
# 矩形
def rectangle(x, y, l, w, k):
x *= N
x -= x_datum
y *= N
y -= y_datum
l *= N
w *= N
idc = turtle.Turtle()
if k == 0:
idc.color('black', 'blue')
else:
idc.color('black', 'green')
idc.up()
idc.hideturtle()
idc.goto(x, y)
idc.down()
idc.begin_fill()
idc.forward(w)
idc.left(90) # 逆时针旋转90度
idc.forward(l)
idc.left(90)
idc.forward(w)
idc.left(90)
idc.forward(l)
idc.left(90)
idc.end_fill()
def qiege(x, y, l, w, i):
global max_len, number
number += 1
print("第" + str(number+1) + "次切割石板" + str(i) + "石板宽为" + str(lis[i].width) + "石板高为" + str(
lis[i].height) + "此时" + str(
x) + " " + str(y) + " " + str(l) + " " + str(w))
rectangle(x, y, l, w, 0)
rectangle(x, y, lis[i].height, lis[i].width, 1)
def swapPositions(listt, pos1, pos2):
listt[pos1], listt[pos2] = listt[pos2], listt[pos1]
return listt
def C(index):
global remainlist
lenth = len(remainlist)
for i in range(0, lenth):
# 本算法的关键如何选取这个
if lis[index].width < remainlist[i].leftwidth and lis[index].height < remainlist[i].leftheight:
# 更新remainlist和lis和nowstone
nowstone.append([remainlist[i].x, remainlist[i].y])
#
remainlist.append(remain())
j = len(remainlist) - 1
remainlist[j].x = remainlist[i].x
remainlist[j].y = remainlist[i].y+lis[index].height
remainlist[j].leftwidth = remainlist[i].leftwidth
remainlist[j].leftheight = remainlist[i].leftheight - lis[index].height
#
remainlist[i].x += lis[index].width
remainlist[i].leftwidth -= lis[index].width
remainlist[i].leftheight = lis[index].height
#
lis[index].used = False
#
return True
break
if i == len(remainlist) - 1:
return False
remainlist = []
beststone = []
bestremain =[]
def Backtracking_cut(index):
global bestheight, minheight, nowstone, beststone, remainlist,bestremain
# next就是下一个可以的分支,next=1,只能0放弃1,next=2,只能1放弃0,next=3都行
if index > num - 1:
# 结果更优就取,不行就算了。
if minheight < bestheight:
bestheight = minheight
beststone = nowstone.copy()
return
# 记录数据,并且更新面积
remainlist1 = remainlist.copy()
a = lis[index]
if C(index): # 当前点可以为0
lis[index].used = False
Backtracking_cut(index + 1)
# 恢复成原来的值
lis[index].used = True
remainlist = remainlist1.copy()
nowstone.pop()
lis[index] = a
if minheight + lis[index].height <= bestheight: # 1可以:# 当前点不能0,但是可以1:做出当前点1的操作,但是并不限制下一步
# 更新remainlist和lis和nowstone
nowstone.append([0, minheight])
remainlist.append(remain())
remainlist[len(remainlist) - 1].y = minheight
remainlist[len(remainlist) - 1].x = lis[index].width
remainlist[len(remainlist) - 1].leftwidth = Width - lis[index].width
remainlist[len(remainlist) - 1].leftheight = lis[index].height
minheight = minheight + lis[index].height
Backtracking_cut(index + 1)
remainlist = remainlist1.copy()
nowstone.pop()
minheight -= lis[index].height
lis[index] = a
elif minheight + lis[index].height <= bestheight: # 1可以:# 当前点不能0,但是可以1:做出当前点1的操作,但是并不限制下一步
# 更新remainlist和lis和nowstone
nowstone.append([0, minheight])
remainlist.append(remain())
remainlist[len(remainlist) - 1].y = minheight
remainlist[len(remainlist) - 1].x = lis[index].width
remainlist[len(remainlist) - 1].leftwidth = Width - lis[index].width
remainlist[len(remainlist) - 1].leftheight = lis[index].height
minheight = minheight + lis[index].height
Backtracking_cut(index + 1)
remainlist = remainlist1.copy()
nowstone.pop()
minheight -= lis[index].height
lis[index] = a
else:
return
lis = [] # 石头数据的结构体数组
nowstone = []
total_area = 0
max_len = 0
num = int(input())
Width = int(input())
for i in range(0, num):
lis.append(stone()) # 添加一个结构体
string = input()
array = string.split(' ')
n = int(array[0])
if i != n:
print("数据出错,请检查")
break
lis[i].width = int(array[1])
lis[i].height = int(array[2])
# lis[i].area = lis[i].length * lis[i].width
# 进行数据预处理 简单的冒泡排序
for i in range(0, num - 1):
for j in range(0, num - 1 - i):
if lis[j].height < lis[j + 1].height:
swapPositions(lis, j, j + 1)
# qiege(0,0,Length,Width,1)
rectangle(0, 0, Length, Width, 0)
turtle.setup(width=1000, height=800)
# 颜色
turtle.color('red', 'pink')
# 笔粗细
turtle.pensize(1)
# 速度
turtle.speed(0)
turtle.delay(0)
turtle.hideturtle()
# 第一个石砖必须为1
nowstone.append([0, 0])
remainlist.append(remain())
remainlist[0].x = lis[0].width
remainlist[0].leftwidth = Width - lis[0].width
remainlist[0].leftheight = lis[0].height
minheight += lis[0].height
print(remainlist[0].leftwidth)
print(remainlist[0].leftwidth)
Backtracking_cut(1)
print(beststone)
total = 0
for i in range(0, num):
qiege(beststone[i][0], beststone[i][1], lis[i].height, lis[i].width, i)
print("最小高度为" + str(bestheight) )
# 点击窗口关闭
window = turtle.Screen()
window.exitonclick()
五、 实验结果
输出大概如图
跟上次的递归算法的比较(个例):
回溯算法:
递归算法:
六、 遇到的一些问题
这个实验已经是在之前的基础上做的了,只是没想到还会遇到这么多问题。
第一、就是这个分支的问题,因为对回溯算法还是不太理解,所以不太清楚怎么设计这个剪枝,一开始就有错误,对本分支的判断却剪掉了下一分支的枝。后面才整理一下。
第二、然后最近比较忙,写这个比较专注不了,所以第一遍写完低级错误很多,debug占了好久时间
第三、因为还是想要实现可视化,所以还需要记录x和y,所以比单纯回溯更麻烦一些,需要专门有个列表来记录最好的点的x和y的集合。然后这个列表的更新就出现了很多问题,比如一开始的可视化是直接在图上无规则的画,然后也是发现一些对剩下面积的更新错了。一步一步debug才看出来。
七、 总结
这次问题有点多:
第一、这里我只考虑算最小长度(高度),不像上次还算了最大面积,所以这次是把石板们按照高度从大到小来排序,所以可能有些面积优先会有更好的结果。
第二、由于使用的递归加上回溯再加上约束和限界设置都比较简单,可能会超时,所以对于数据比较大的不太友好。
第三、由于找不到约束条件,所以就用了相反条件的约束函数,导致可能会有一些情况下无法遍历到,而且约束函数的设计也比较粗糙,后期还可以模仿我上次的比如每次使用了剩余面积就把剩余面积排序还有就是先比较一下有没有长宽刚好符合的,直接切更好。
经过测试,大多数情况下还是上次的类贪心的算法结果更好,但是也有少部分情况,该算法会表现出较好的结果,比如我在结果中展示的。虽然结果没有之前更好,但是一题多解的思维模式也让我扩展了想法,头脑风暴让我进步,也加深了对回溯法的理解吧。
如果需要旧版代码或者老师给的测试数据可以私聊笔者
切割石板递归算法(大部分样例更优)