python 回溯法 子集樹模板 系列 —— 6、排課問題


問題

某鄉村小學有六個年級,每個年級有一個班,共六個班。

周一到周五,每天上6節課,共計30節課。

開設的課程

一年級:語(9)數(9)書(2)體(2)美(2)音(2)德(2)班(1)安(1)
二年級:語(9)數(9)書(2)體(2)美(2)音(2)德(2)班(1)安(1)
三年級:語(8)數(8)英(4)體(2)美(2)音(2)德(2)班(1)安(1)
四年級:語(8)數(8)英(4)體(2)美(2)音(2)德(2)班(1)安(1)
五年級:語(8)數(8)英(4)體(2)美(2)音(2)德(2)班(1)安(1)
六年級:語(8)數(8)英(4)體(2)美(2)音(2)德(2)班(1)安(1)

要求
  • 各門課課時必須相符
  • 周一最后一節課班會,周五最后一節課安全教育是固定的。
  • 上午只能排語、數、英
  • 全校只有兩位音樂老師
  • 三年級的數學不能排在周五上午第三節(三年級數學潘老師家里有事)

分析

將每一個(時間空間)點對,作為一個元素。這些元素都具有各自的[狀態1,狀態2,狀態3,狀態4,狀態5,狀態6,狀態7,狀態8,狀態9]共9個狀態

時間空間) --> 狀態

一種狀態對應一個課程。

對每一個元素,遍歷相應的9種狀態。

解的長度是固定的,30 * 6 的二維數組

套用子集樹模板即可。

本問題只需要解決存在性。也就是說,只要找到一個符合要求的解就ok了。

此問題的本質就是,各(時,空)點對的取各自狀態搭配的問題。

代碼


'''排課問題'''

# 作者:hhh5460 
# 寫於:2017年5月30日22時33分
# 聲明:此算法的版權歸本人所有


m = 30  # 一周課時數(時間)
n = 6   # 全校班級數(空間)
o = 30 * 6  # 元素個數,即(時, 空)點對的個數

# 6個班開始的課程(狀態空間)
a = [['語','數','書','體','美','音','德','班','安'], # 一年級
     ['語','數','書','體','美','音','德','班','安'], # 二年級
     ['語','數','英','體','美','音','德','班','安'], # 三年級
     ['語','數','英','體','美','音','德','班','安'], # 四年級
     ['語','數','英','體','美','音','德','班','安'], # 五年級
     ['語','數','英','體','美','音','德','班','安']] # 六年級

# 課時數
b = [[9,9,2,2,2,2,2,1,1],
     [9,9,2,2,2,2,2,1,1],
     [8,8,4,2,2,2,2,1,1],
     [8,8,4,2,2,2,2,1,1],
     [8,8,4,2,2,2,2,1,1],
     [8,8,4,2,2,2,2,1,1]]

x = [[0 for _ in range(n)] for _ in range(m)] # 一個解,m*n 的二維數組


is_found = False  # 結束所有遞歸標志!!!!!

# 沖突檢測
def conflict(t, s):
    '''只考慮剛剛排的x[t][s]'''
    
    global m,n,o,a,b,x
    
    # 一、各門課課時數必須相符(縱向看)
    # 1.前面已經排的課時不能超
    if [r[s] for r in x[:t+1]].count(x[t][s]) > b[s][a[s].index(x[t][s])]: # 黑科技,不要眼花!
        return True
    # 2.后面剩的課時不能不夠
    if [r[s] for r in x[:t+1]].count(x[t][s]) + (m-t-1) < b[s][a[s].index(x[t][s])]:
        return True
    
    # 二、周一最后一節課班會,周五最后一節課安全教育是固定的。
    # 1.周一最后一節課班會
    if x[t][s] == '班' and t != 5:
        return True
    # 2.周五最后一節課安全教育
    if x[t][s] == '安' and t != 29:
        return True
    
    # 三、上午只能排語、數、英
    if t % 6 in [0,1,2] and x[t][s] not in ['語','數','英']:
        return True
        
    # 四、只有兩個音樂老師(橫向看)
    # 前面已經排的班級不能有3個及以上的班級同時上音樂課
    if x[t][s] == '音' and x[t][:s+1].count('音') >= 3: 
        return True
    
    # 五、三年級的數學不能排在周五上午第三節(三年級數學潘老師家里有事)
    if x[t][s] == '數' and t==5*n+3-1:
        return True
    
    return False # 無沖突
    
    
# 套用子集樹模板
def paike(t, s): # 到達(t,s)時空點對的位置
    global m,n,o,a,b,x, is_found
    
    if is_found: return # 結束所有遞歸
    
    if t == m:  # 超出最尾的元素
        #print(x)
        show(x) # 美化版
        is_found = True # 只需找一個
    else:
        for i in a[s]: # 遍歷第s個班級的對應的所有狀態,不同的班級狀態不同
            x[t][s] = i
            if not conflict(t, s): # 剪枝
                ns = [s + 1, 0][s + 1 == n] # 行掃描方式
                nt = [t, t + 1][s + 1 == n]
                paike(nt, ns) # 去往(nt, ns)時空點對
                
# 可視化一個解x
def show(x):
    import pprint
    
    pprint.pprint(x[:6])    # 全校的周一課表
    pprint.pprint(x[6:12])  # 全校的周二課表
    pprint.pprint(x[12:18]) # 全校的周三課表
    pprint.pprint(x[18:24]) # 全校的周四課表
    pprint.pprint(x[24:])   # 全校的周五課表


# 測試
paike(0, 0) # 從時空點對(0,0)開始

效果圖

正在運行中... ... 稍后奉上。

補:現在時間2017年6月1日7時40分,程序已運行34+時,還沒有結果,囧!

之所以這么慢,其原因可能是遞歸太多了!

好吧,那只能等我以后將遞歸版本改為迭代版本,再運行看其結果如何了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM