2013-05-04 52 views
16

我經常教大型的入門級編程課程(400-600名學生),考試時間到了,我們經常不得不把課程分成不同的房間,以確保每個人都有考試席位。按姓氏將人分爲房間?

爲了保持物流的簡單性,我通常會用姓將課程分開。例如,我可能會把學生姓名A - H送到一個房間,姓I - L給第二個房間,M - S給第三個房間,而T - Z送到第四個房間。

這樣做的挑戰在於,房間的容量常常大不相同,而且很難找到一種方法來分類課程,使得每個人都適合。例如,假設姓氏的分佈(爲簡單起見)以下:

  • 姓開始使用A:25
  • 姓與B開始:150
  • 姓以C開頭: 200
  • 姓與d開始:50

假設我有配備了容量350,50,和50貪婪算法查找房間分配可能是房間整理到容量的降序排列,然後嘗試按照該順序填寫房間。不幸的是,這並不總是奏效。例如,在這種情況下,正確的選擇是將姓A放入一個大小爲50的房間,姓名B-C放入大小爲350的房間,姓D放入另一個大小爲50的房間。貪婪算法會把姓名A和B放入350人的房間,然後找不到其他人的座位。

只需嘗試房間排序的所有可能排列,然後在每個排序上運行貪婪算法,就很容易解決此問題。這可以找到一個可以工作的任務或報告沒有任何任務。但是,我想知道是否有更有效的方法來實現這一點,因爲房間的數量可能在10到20之間,檢查所有的排列可能是不可行的。

總之,正規的問題陳述如下:

你給了學生們的姓氏的頻率直方圖中的一類,與房間和能力的名單。您的目標是通過姓氏的第一個字母來分配學生,以便爲每個房間分配一個連續的字母塊並且不超過其容量。

是否有一個有效的算法,或者至少有一個高效的合理房間大小?

編輯:很多人都問過這個連續的條件。規則是

  • 每間客房都應當在大多數被分配連續的字母塊,並
  • 沒有字母應該分配到兩個或多個房間。

例如,您不能將A - E,H - N和P - Z放入同一個房間。你也不能把A-C放在一個房間裏,B-D放在另一個房間裏。

謝謝!

+0

你能把在兩個不同房間裏的姓氏以同一個字符開頭的學生分開嗎? – Bill 2013-05-04 17:34:33

+0

@比爾 - 爲了這個問題,我們說不。從邏輯上講,如果我們爲同一封信提供多個房間,並且每個人都只顯示其中一個,那將是非常糟糕的。 – templatetypedef 2013-05-04 17:38:44

+1

你不需要拆分一個字母。 – starblue 2013-05-04 17:38:59

回答

2

這個問題是NP-Complete因此沒有已知的polynomial時間(aka效率)解決方案(只要人們不能證明P = NP)。您可以減少揹包或垃圾箱問題的實例,以證明您的問題是NP-complete

要解決這個問題,你可以使用0-1揹包問題。這裏是: 首先選擇最大的教室大小,並嘗試分配儘可能多的學生組(使用0-1揹包),即等於房間的大小。你保證不會分裂一羣學生,因爲這是0-1揹包。一旦完成,請繼續下一個最大的教室。

(您可以使用任何已知的啓發式解決揹包問題。)

這裏是降低 - 你需要0-1揹包的一般情況下降低您的問題的一個具體實例。 所以我們來看一個0-1揹包的一般實例。讓我們拿一個袋子,它的重量是W,你有x_1, x_2, ... x_n組,它們相應的權重是w_1, w_2, ... w_n

現在減少---這個一般情況減少到你的問題如下: 你有一個教室,座位容量爲W。每個x_i (i \in (1,n))是一組學生,其最後一個字母以i開頭,他們的數字(又稱組的大小)爲w_i

現在你可以證明是否存在0-1揹包問題的解決方案,你的問題有一個解決方案...和converse ....如果沒有解決方案0-1揹包,那麼你的問題沒有解決辦法,反之亦然。

請記住減少的重要事項 - 針對您問題的特定實例的已知NP-C問題的一般情況。

希望這有助於:)使用某種形式的解決方案DP上[m, 2^n]空間,其中m是字母數字(26英語)和n是客房數量

+0

'儘可能多的學生你可以'這裏的「價值」是什麼?如果「價值」是羣體的數量,那麼你可能會冒險不適合小羣體的最大羣體。 – nhahtdh 2013-05-04 18:00:38

+0

該值與您在0-1揹包問題的此迭代中考慮的類的大小相同 – Bill 2013-05-04 18:01:26

+0

如果它是該類的大小,那麼您將如何考慮2種具有相同總大小的分組方式?另一件事:這些團體必須是連續的。 – nhahtdh 2013-05-04 18:02:30

5

它是可以解決的。使用m == 26n == 20它將需要大約100 MB的空間和〜1秒的時間。 下面是解決方案,我剛纔在C#實現的(它會成功編譯的C++和Java也將需要只是一些小的改動):

int[] GetAssignments(int[] studentsPerLetter, int[] rooms) 
{ 
    int numberOfRooms = rooms.Length; 
    int numberOfLetters = studentsPerLetter.Length; 
    int roomSets = 1 << numberOfRooms; // 2^(number of rooms) 
    int[,] map = new int[numberOfLetters + 1, roomSets]; 

    for (int i = 0; i <= numberOfLetters; i++) 
     for (int j = 0; j < roomSets; j++) 
      map[i, j] = -2; 

    map[0, 0] = -1; // starting condition 

    for (int i = 0; i < numberOfLetters; i++) 
     for (int j = 0; j < roomSets; j++) 
      if (map[i, j] > -2) 
      { 
       for (int k = 0; k < numberOfRooms; k++) 
        if ((j & (1 << k)) == 0) 
        { 
         // this room is empty yet. 
         int roomCapacity = rooms[k]; 
         int t = i; 
         for (; t < numberOfLetters && roomCapacity >= studentsPerLetter[t]; t++) 
          roomCapacity -= studentsPerLetter[t]; 

         // marking next state as good, also specifying index of just occupied room 
         // - it will help to construct solution backwards. 
         map[t, j | (1 << k)] = k; 
        } 
      } 

    // Constructing solution. 
    int[] res = new int[numberOfLetters]; 
    int lastIndex = numberOfLetters - 1; 
    for (int j = 0; j < roomSets; j++) 
    { 
     int roomMask = j; 
     while (map[lastIndex + 1, roomMask] > -1) 
     { 
      int lastRoom = map[lastIndex + 1, roomMask]; 
      int roomCapacity = rooms[lastRoom]; 
      for (; lastIndex >= 0 && roomCapacity >= studentsPerLetter[lastIndex]; lastIndex--) 
      { 
       res[lastIndex] = lastRoom; 
       roomCapacity -= studentsPerLetter[lastIndex]; 
      } 

      roomMask ^= 1 << lastRoom; // Remove last room from set. 

      j = roomSets; // Over outer loop. 
     } 
    } 

    return lastIndex > -1 ? null : res; 
} 

來自實例OP問題:

int[] studentsPerLetter = { 25, 150, 200, 50 }; 
int[] rooms = { 350, 50, 50 }; 
int[] ans = GetAssignments(studentsPerLetter, rooms); 

答案是:

2 
0 
0 
1 

指示的空間爲每個學生的姓氏字母索引。如果分配不可行,我的解決方案將返回null


[編輯]

經過千自動生成的測試我的朋友已經發現,在代碼向後構建解決方案的錯誤。它不會影響主算法,因此修復這個錯誤將是對讀者的一個練習。

揭示該錯誤的測試用例是students = [13,75,21,49,3,12,27,7]rooms = [6,82,89,6,56]。我的解決方案沒有回答,但實際上有一個答案。請注意,解決方案的第一部分正常工作,但回答構造部分失敗。

+0

我想,這看起來更像是一個解決方案。該正確性取決於把最大數量的學生放在一個房間裏是否會讓我們錯過一個解決方案,我認爲這不是**的情況,但我不能想到一個證明。 – nhahtdh 2013-05-04 19:16:22

+1

完整的Java測試程序: https://gist.github.com/nhahtdh/5518441 – nhahtdh 2013-05-04 19:19:06

+1

至於我 - 證明似乎是直觀的,瑣碎的,但將它定義數學上正確是很困難的,我如果只是℃。關於解決方案的這一方面 - 比你可以稍微修改解決方案並檢查'非全部負載'還要高。只需移動map [t,j | (1 << k)] = k;'在它上面循環中的行。它不會改變解決方案的主要思想及其理論複雜性。 – SergeyS 2013-05-04 20:33:55

0

這是一個應該工作得相當好的方法,假設通過初始分配姓氏的常見假設。在約束範圍內儘可能緊湊地填充房間從最小容量到最大容量,無需回溯。

對於最後列出的最大房間而言,對於「其他人」尚未列出的似乎是合理的(至少對我而言)。

+0

你打算如何確保連續的名稱條件? – nhahtdh 2013-05-04 19:27:57

+0

OP是否有意合理地解決實際問題(即不允許它成爲NP),還是以一種稍微不同的形式重新引用一個衆所周知的NP問題?我正在將他的意圖解釋爲(1),而且看起來響應者正試圖將他拖入(2)。只有OP可以提供對所需內容的明確解釋。 – 2013-05-04 19:31:28

+0

換句話說,通過探索一個稍微簡單的問題,對於容量最大的房間來說,放寬連續的名稱選擇時,真實世界的解決方案可能更容易編碼並且性能更高。 – 2013-05-04 19:33:23

0

有什麼理由讓生活如此複雜?爲什麼你不能給每個學生分配註冊號碼,然後用你想要的方式分配註冊號碼:)你不需要編寫代碼,學生很開心,每個人都很開心。

+0

(這個答案可能是最好的評論)。這絕對有效,但我有興趣解決這個問題,既作爲一個算法有趣的練習,也因爲它提供了一個非常簡單的系統,學生可以輕鬆地使用它來確定他們的房間號碼。沒有必要爲學生準備一張大桌子。 – templatetypedef 2013-05-04 20:23:58