Team 1
Project 1
The measuring cup problem
問題簡介
今天有 \(n\) 個量杯。你想要用這幾個量杯,
經過下面給定的三種操作,來讓最後某個量杯中有 \(t\) 的水量。
-
把量杯裝滿水
-
把量杯中的水倒到另外一個量杯中
-
把量杯中的水倒光
今天,我們將使用 廣度優先搜尋演算法
(Breadth-First Search, BFS)
來解決這個問題
就是窮舉法
另外,要讓程式紀錄一種組合的資訊
建立一個陣列來存初始組合狀態,
以及由這個初始狀態窮舉出的組合狀態。
# iterate data
data = list()
# the data of a combination
class Combination:
def __init__(self, init_father_index: int, init_step: int, init_list: list):
self.father_index = init_father_index
self.step = init_step
self.cups_volume = init_list
\(self.father\_index\)
用於步驟回朔,註明:「這個組合是哪個組合的下一步」
其中哪個組合(父節點)在 \(data\) 中的位置
\(self.step\)
註明:這個組合的最少步驟數
\(self.cups\_volume\)
註明:這個組合中,每個量杯各自的水量
data = [comb0]
# initialize first combination
data.append(Combination(0, 0, [0] * n))
最初的資料
(父節點為自己、步驟為0、各量杯水量都為零)
方法一:倒滿量杯
# try 1: fill cup to maximum volume
for cup_index in range(n):
# if the cup is already full, then skip this step
if data[cur_p].cups_volume[cup_index] == cup_max[cup_index]:
continue
new_comb = data[cur_p].cups_volume.copy()
# fill the cup of index 'cup_index' to its maximum volume
new_comb[cup_index] = cup_max[cup_index]
find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
if find:
break
方法二:量杯互倒
# try 2: pour water from cup a to b
for a_index in range(n):
for b_index in range(n):
# situation 1: if a-cup is b-cup, then skip this step
# situation 2: if a-cup is empty, then skip this step
# situation 3: if b-cup is full, then skip this step
if a_index == b_index \
or data[cur_p].cups_volume[a_index] == 0 \
or data[cur_p].cups_volume[b_index] == cup_max[b_index]:
continue
new_comb = data[cur_p].cups_volume.copy()
# criterion: delta value <= a-cup
# situation 1: if a-cup currently has more water than b_max - b-cup,
# then the delta value is b_max - b-cup
# situation 2: if a-cup currently has less water than b_max - b-cup,
# then the delta value is just a-cup itself
delta_volume = min(cup_max[b_index] - new_comb[b_index], new_comb[a_index])
new_comb[a_index] -= delta_volume
new_comb[b_index] += delta_volume
find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
if find:
break
if find:
break
方法三:清空量杯
# try 3: pour the water out of the cup
for cup_index in range(n):
# if the cup is empty, then skip this step
if data[cur_p].cups_volume[cup_index] == 0:
continue
new_comb = data[cur_p].cups_volume.copy()
# pour the water out of the cup of index 'cup_index'
new_comb[cup_index] = 0
find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
if find:
break
假設 \(comb1\) 是 \(comb0\) 的
下一步驟其中的一種可能組合
先檢查想要的水量有沒有出現在
這個組合的各量杯的水量中。
如果在,那就直接將答案設為 \(comb1\),
將其加進 \(data\) 中,並跳過之後的所有窮舉。
comb1
\(\downarrow\)
data
\(\downarrow\)
data = [comb0, comb1]
# the needed volume is already in the new combination
if t in detect_comb:
ans = Combination(father_ind, needed_step, new_comb)
return True
但,如果它不是答案,
但也並非重複組合(不在 \(data\) 中),
那就把它加進 \(data\) 的後面 ;
不然就將它捨去。
comb1
\(\downarrow\)
❌
comb1
\(\downarrow\)
data
\(\downarrow\)
data = [comb0, comb1]
# the combination is already in the data
if detect_comb in [cups_data.cups_volume for cups_data in data]:
return False
# if two former test failed, that means this is a new combination
data.append(Combination(father_ind, needed_step, new_comb))
return False
要找到甚麼時候呢?
-
直接找到答案
-
找完所有的窮舉組合
while cur_p < len(data):
\(cur\_p\) : 目前所在位置
讓我們複習一下整個流程!
- 輸入初始數值
- 開始對三種倒水方法窮舉
- 如果找到答案,就設為正確答案,跳出迴圈
- 如果重複了,就捨去
- 如果是新的,就把它放到queue後面,等待在稍後窮舉它的下一步
- 如果找到答案了,就印出來,並且循著父節點回朔步驟
- 如果找完所有可能的組合卻沒有答案,就跳出程式
扣得
print('--> Programed and run by phantom0174 <--')
# cup count
n = int(input('請輸入杯子的個數><\n'))
# needed volume
t = int(input('請輸入你想要的水量><\n'))
# cup max volume
cup_max = list(input('請輸入各杯子的最大容量>< (要以空格隔開)\n').split(' '))
cup_max = list(map(int, cup_max))
# current pop index
cur_p = int(0)
# the data of a combination
class Combination:
def __init__(self, init_father_index: int, init_step: int, init_list: list):
self.father_index = init_father_index
self.step = init_step
self.cups_volume = init_list
# answer object
ans = Combination(0, 0, list())
# iterate data
data = list()
# initialize first combination
data.append(Combination(0, 0, [0] * n))
# the function for status checking
# if we find the answer, return True; otherwise, return False
def check_and_manipulate(father_ind, needed_step, detect_comb: list) -> bool:
global data, ans
# the needed volume is already in the new combination
if t in detect_comb:
ans = Combination(father_ind, needed_step, new_comb)
return True
# the combination is already in the data
if detect_comb in [cups_data.cups_volume for cups_data in data]:
return False
# if two former test failed, that means this is a new combination
data.append(Combination(father_ind, needed_step, new_comb))
return False
# main program
# initialize find status, which is equivalent to 'flag'
find = bool(False)
while cur_p < len(data):
print(f'Searching... ({cur_p}/{len(data) - 1})')
# try 1: fill cup to maximum volume
for cup_index in range(n):
# if the cup is already full, then skip this step
if data[cur_p].cups_volume[cup_index] == cup_max[cup_index]:
continue
new_comb = data[cur_p].cups_volume.copy()
# fill the cup of index 'cup_index' to its maximum volume
new_comb[cup_index] = cup_max[cup_index]
find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
if find:
break
if find:
break
# try 2: pour water from cup a to b
for a_index in range(n):
for b_index in range(n):
# situation 1: if a-cup is b-cup, then skip this step
# situation 2: if a-cup is empty, then skip this step
# situation 3: if b-cup is full, then skip this step
if a_index == b_index \
or data[cur_p].cups_volume[a_index] == 0 \
or data[cur_p].cups_volume[b_index] == cup_max[b_index]:
continue
new_comb = data[cur_p].cups_volume.copy()
# criterion: delta value <= a-cup
# situation 1: if a-cup currently has more water than b_max - b-cup,
# then the delta value is b_max - b-cup
# situation 2: if a-cup currently has less water than b_max - b-cup,
# then the delta value is just a-cup itself
delta_volume = min(cup_max[b_index] - new_comb[b_index], new_comb[a_index])
new_comb[a_index] -= delta_volume
new_comb[b_index] += delta_volume
find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
if find:
break
if find:
break
if find:
break
# try 3: pour the water out of the cup
for cup_index in range(n):
# if the cup is empty, then skip this step
if data[cur_p].cups_volume[cup_index] == 0:
continue
new_comb = data[cur_p].cups_volume.copy()
# pour the water out of the cup of index 'cup_index'
new_comb[cup_index] = 0
find = check_and_manipulate(cur_p, data[cur_p].step + 1, new_comb)
if find:
break
if find:
break
cur_p += 1
if not find:
print('不要為難我了qwq, 這是不可能的任務')
else:
print(f'恭喜!\\^~^ 你只要 {ans.step} 步就可以得到 {t} 的水量!')
step_list = list()
step_list.append(ans.cups_volume)
# initialize current father_index to search
father_index = ans.father_index
# get the cups_volume data from father_index, and update the next father_index
while father_index != 0:
step_list.append(data[father_index].cups_volume)
father_index = data[father_index].father_index
# because we're searching from back, we'll need to reverse the step logs
step_list.reverse()
print('以下是步驟!')
for step in step_list:
print(step)
謝謝聆聽
第一組 資訊專題報告
By phantom0174
第一組 資訊專題報告
- 469