2011-09-17 28 views
8

Đây là một dự án thú vị nhỏ mà tôi đã bắt đầu thử và tối đa hóa cơ hội chiến thắng trong hồ bơi hockey văn phòng của chúng tôi. Tôi đang cố gắng tìm cách tốt nhất để chọn 20 người chơi sẽ cho tôi nhiều điểm nhất trong giới hạn mức lương tối đa.Hockey pool Algorithm

Ví dụ, hãy tưởng tượng rằng các dữ liệu thô được làm bằng

  1. Các Tên cầu thủ
  2. Chức vụ (Forward, thủ phòng vệ, Goalie)
  3. lượng dự đoán điểm cho mùa này
  4. Lương.

Bây giờ tôi muốn 20 người chơi sẽ cho tôi điểm cao nhất trong giới hạn mức lương X. Sau đó, như một giai đoạn 2, tôi muốn làm điều tương tự tuy nhiên trong trường hợp này, tôi chỉ muốn 12 tiền đạo, 6 vệ sĩ và 2 thủ môn. Bây giờ cách rõ ràng là chỉ cần đi mọi kết hợp có thể, tuy nhiên trong khi điều này sẽ làm việc nó không phải là một lựa chọn hợp lệ như với 500 người chơi, điều này sẽ có quá nhiều sự kết hợp có thể. Tôi có thể thêm một số bộ lọc thông minh để giảm 500 người chơi trong top 50 trở đi, top 30 người bảo vệ và 15 thủ môn hàng đầu nhưng vẫn, đây sẽ là một quá trình rất chậm.

Tôi tự hỏi nếu có các thuật toán khác để thực hiện điều này. Đây chỉ là cho vui và không phải là một yêu cầu kinh doanh quan trọng. Nhưng nếu bạn có một số suy nghĩ về cách tiến hành, vui lòng cho tôi biết.

Lần thử đầu tiên của tôi là sử dụng thuật toán ba lô với một số trợ giúp từ các nguồn khác. Dường như chỉ làm việc với mức lương như một tham số. Tôi đang đấu tranh để tìm ra cách thêm tham số đội 20 cầu thủ. Của nó trong. Net nhưng nên dễ dàng để chuyển đổi sang Java.

Tôi đã nghĩ đến việc thực hiện một vòng lặp riêng biệt để tìm ra các đội tốt nhất với 20 người chơi không có tiền lương và sau đó so sánh hai danh sách cho đến khi tôi tìm thấy đội cao nhất trong hai danh sách. Không chắc.

namespace HockeyPoolCalculator 
{ 
public class ZeroOneKnapsack 
{ 

protected List<Item> itemList = new List<Item>(); 
protected int maxSalary = 0; 
protected int teamSize = 0; 
protected int teamSalary = 0; 
protected int points = 0; 
protected bool calculated = false; 

public ZeroOneKnapsack() { } 

public ZeroOneKnapsack(int _maxSalary) 
{ 
setMaxSalary(_maxSalary); 
} 

public ZeroOneKnapsack(List<Item> _itemList) 
{ 
setItemList(_itemList); 
} 

public ZeroOneKnapsack(List<Item> _itemList, int _maxSalary) 
{ 
setItemList(_itemList); 
setMaxSalary(_maxSalary); 
} 

// calculte the solution of 0-1 knapsack problem with dynamic method: 
public virtual List<Item> calcSolution() 
{ 
int n = itemList.Count; 

setInitialStateForCalculation(); 
if (n > 0 && maxSalary > 0) 
{ 
List<List<int>> playerList = new List<List<int>>(); 
List<int> salaryList = new List<int>(); 

//initialise list 
playerList.Add(salaryList); 
for (int j = 0; j <= maxSalary; j++) 
salaryList.Add(0); 
// Loop through players 
for (int i = 1; i <= n; i++) 
{ 
List<int> prev = salaryList; 
playerList.Add(salaryList = new List<int>()); 
for (int j = 0; j <= maxSalary; j++) 
{ 
if (j > 0) 
{ 
int wH = itemList.ElementAt(i - 1).getSalary(); 
// Is the players salary more than the current calculated salary? If yes, then keep current max points, else get the highest amount between previous max points at that salary and new max points. 
salaryList.Add((wH > j)?prev.ElementAt(j): Math.Max(prev.ElementAt(j),itemList.ElementAt(i - 1).getPoints() + prev.ElementAt(j - wH))); 
} 
else 
{ 
salaryList.Add(0); 
} 
} // for (j...) 
} // for (i...) 
points = salaryList.ElementAt(maxSalary); 

for (int i = n, j = maxSalary; i > 0 && j >= 0; i--) 
{ 
int tempI = playerList.ElementAt(i).ElementAt(j); 
int tempI_1 = playerList.ElementAt(i - 1).ElementAt(j); 
if ((i == 0 && tempI > 0)||(i > 0 && tempI != tempI_1)) 
{ 
Item iH = itemList.ElementAt(i - 1); 
int wH = iH.getSalary(); 
iH.setInKnapsack(1); 
j -= wH; 
teamSalary += wH; 
} 
} // for() 
calculated = true; 
} // if() 
return itemList; 
} 

// add an item to the item list 
public void add(String name, int Salary, int value) 
{ 
if (name.Equals("")) 
name = "" + (itemList.Count() + 1); 
itemList.Add(new Item(name, Salary, value)); 
setInitialStateForCalculation(); 
} 

// add an item to the item list 
public void add(int Salary, int value) 
{ 
add("", Salary, value); // the name will be "itemList.size() + 1"! 
} 

// remove an item from the item list 
public void remove(String name) 
{ 
for (int pointer = 0; pointer <= itemList.Count-1; pointer++) 

{ 
itemList[pointer].getName().Equals(""); 

if (itemList.ElementAt(pointer).getName().Equals(itemList.ElementAt(pointer).getName())) 
{ 
itemList.Remove(itemList.ElementAt(pointer)); 
} 
} 
setInitialStateForCalculation(); 
} 

// remove all items from the item list 
public void removeAllItems() 
{ 
itemList.Clear(); 
setInitialStateForCalculation(); 
} 

public int getPoints() 
{ 
if (!calculated) 
calcSolution(); 
return points; 
} 

public int getSolutionSalary() { return teamSalary; } 
public bool isCalculated() { return calculated; } 
public int getMaxSalary() { return maxSalary; } 

public void setTeamSize(int _teamSize) 
{ 
teamSize = _teamSize; 
} 

public int getTeamSize() 
{ 
return teamSize; 
} 

public void setMaxSalary(int _maxSalary) 
{ 
maxSalary = Math.Max(_maxSalary, 0); 
} 

public void setItemList(List<Item> _itemList) { 
if (_itemList != null) { 
itemList = _itemList; 
foreach (Item item in _itemList) { 
item.checkMembers(); 
} 
} 
} 

// set the member with name "inKnapsack" by all items: 
private void setInKnapsackByAll(int inKnapsack) { 
foreach (Item item in itemList) 
if (inKnapsack > 0) 
item.setInKnapsack(1); 
else 
item.setInKnapsack(0); 
} 

// set the data members of class in the state of starting the calculation: 
protected void setInitialStateForCalculation() 
{ 
setInKnapsackByAll(0); 
calculated = false; 
points = 0; 
teamSalary = 0; 
teamSize = 0; 
} 

} 
} 

Cảm ơn sự giúp đỡ của bạn!

+0

Đối với giai đoạn 1, bạn không quan tâm đến vị trí nào cả? Ví dụ, bạn không muốn có ít nhất một thủ môn? –

Trả lời

3

Thật không may, bạn không nên mong đợi tìm ra giải pháp tốt cho vấn đề này vì nó là NP-hard. Trừ khi P = NP, không có bất kỳ thuật toán đa thức thời gian nào và tìm kiếm toàn diện có thể là một trong những thuật toán tốt nhất (mặc dù bạn có thể sử dụng một số thuật toán để tăng tốc).

Để thấy rằng vấn đề này là NP-hard, chúng tôi sẽ hiển thị cách giảm knapsack problem thành thời gian đa thức. Với bất kỳ trường hợp của vấn đề tập hợp con sum gồm một tập S = {(trọng lượng , giá trị), (trọng lượng , giá trị), ..., (trọng lượng n, giá trị n)} và giới hạn trọng lượng k, chúng tôi có thể xây dựng một trường hợp của vấn đề khúc côn cầu của bạn bằng cách tạo một tập hợp người chơi có mức lương là trọng số và điểm dự kiến ​​là giá trị. Sau đó, chúng tôi cố gắng tìm sự kết hợp trọng lượng tối đa của người chơi có mức lương không vượt quá k, sau đó sẽ giống với số tiền tối đa bạn có thể thực hiện trong vấn đề ba lô ban đầu không vượt quá trọng lượng mục tiêu.

Vì mã bạn đã đăng hiển thị, mặc dù, có một thuật toán thời gian giả để giải quyết vấn đề về ba lô.Giả sử tiền lương thấp (hoặc bạn có thể bình thường hóa chúng với số lượng nhỏ), bạn có thể sử dụng điều này để có hiệu quả tốt.

Trong khi nghi ngờ rằng có một thuật toán đa thời gian để có được câu trả lời chính xác, nếu bạn đồng ý với một giải pháp tối ưu, có một thuật toán đa thời gian để xấp xỉ các giải pháp cho vấn đề ba lô. Để biết chi tiết, hãy xem these notes, chi tiết hai thuật toán. Điều thú vị là, họ dựa vào thuật toán thời gian pseudopolynomial mà bạn dường như đang sử dụng, vì vậy có lẽ họ sẽ dễ dàng thực hiện?

Xin lỗi để vạch trần hy vọng của bạn cho một giải pháp tốt đẹp với toán học ... Vấn đề NP-hard có xu hướng làm điều đó. :-(

+0

Tôi nghĩ rằng bóng tối bị thất lạc ở đây. Các nút cổ chai trong chất lượng giải pháp chắc chắn sẽ được trong chất lượng của các dự đoán chứ không phải là thời gian/lỗi nó cần để làm tối ưu hóa đội cho các dự đoán.Hãy xem xét rằng tôi thực sự nghi ngờ anh ta sẽ nhận được 3 chữ số chính xác trong các dự đoán, và sau đó nếu anh ta giải quyết vấn đề với 4 chữ số sig, anh ta có thể ước tính nó trong thời gian 10^ý nghĩa * số người chơi trong giải đấu ~ = 10^4 * 500 = 5 triệu tính toán. –

+0

Bạn có thể giải thích, nếu bạn có thời gian và sở thích, tại sao giải pháp @ mike không hoạt động. Nếu nó đã được làm việc thì điều này sẽ không được NP-cứng, hoặc nó sẽ? – Unapiedra

+0

Chắc chắn! Thuật toán anh ta mô tả là một thuật toán tham lam sẽ luôn lấy ra người chơi đắt nhất trên mỗi đô la trong suốt quá trình quét. Tuy nhiên, bạn có thể xây dựng các ví dụ mà người chơi này cần phải có trong giải pháp tối ưu, và đưa anh ta ra luôn để lại cho bạn một tình huống tồi tệ hơn. Hãy thử suy nghĩ về một trường hợp điều này sẽ đúng. – templatetypedef

3

enter image description here

Lô các cầu thủ trên một đồ thị, trục X là điểm, trục Y là tiền lương, zero tại gốc. Người chơi thấp hơn bên phải sẽ được mong muốn cầu thủ ghi bàn cao giá rẻ, máy nghe nhạc trên bên trái sẽ quét một bán kính có nguồn gốc từ chiều ngang ngược chiều kim đồng hồ theo chiều kim đồng hồ theo chiều dọc Y, tạo thành một lát bánh ngày càng phát triển, cho đến khi một trong hai 20 người chơi nằm trong slice hoặc tổng số tiền lương bên trong slice đạt đến giới hạn, nếu bạn đạt đến $ cap nhưng không phải 20 người chơi, hãy xóa cầu thủ "left leftmost" bên trong slice và tiếp tục. , bạn có thể xóa một cầu thủ ghi bàn thấp khỏi slice để nhường chỗ cho điểm số cao hơn người chơi sắp bước vào lát cắt, làm cho tổng chi phí cho mỗi điểm không cần thiết tăng lên, nhưng vì nó là tiền vui và bạn không phải là chủ sở hữu thực sự, hãy tìm nó.

+0

Đây là một thuật toán tham lam tốt, nhưng không có cách nào đảm bảo rằng nó đưa ra một giải pháp tối ưu (trừ khi bạn vừa tìm được thuật toán chứng minh P = NP!) – templatetypedef

+0

Không có yêu cầu nào là tối ưu, nó chỉ là một cách dễ dàng để tấn công vấn đề với khả năng cao là tốt như mọi phương pháp khác. (Ngay cả một giải pháp tối ưu để chọn không đảm bảo anh ấy sẽ giành chiến thắng trong hồ bơi, vì các màn trình diễn trong quá khứ của người chơi sẽ không đảm bảo thành công trong tương lai của họ.) Lưu ý rằng ngoại trừ một hoặc hai người chơi đầu tiên chỉ đơn giản là lựa chọn theo thứ tự chi phí thấp nhất cho mỗi điểm, đó là một cách đơn giản hơn để chọn và có lẽ cũng tốt như mọi thứ khác. –

0

Vấn đề có thể khó, nhưng bạn có thể sử dụng thực tế là thủ môn của bạn sẽ không chơi phạm tội (chính thức hơn: các thực thể bạn chọn có các danh mục khác nhau) để cải thiện thời gian chạy của bạn.

Ngoài ra, bạn có thể muốn tìm giải pháp gần đúng cho sự cố của mình. Sử dụng giá trị đó để ràng buộc giải pháp tối ưu và tìm kiếm nó.

0

Bạn có thể dễ dàng xây dựng nó dưới dạng ILP. Giải quyết chúng cũng là NP-Hard, nhưng ví dụ về vấn đề có vẻ không lớn như vậy, vì vậy nó có thể giải được hoàn hảo (một người giải quyết, ví dụ như lpsolve). Ngay cả khi nó không phải là giải pháp hoàn hảo vì kích thước vấn đề, có tồn tại heuristics tốt cho ILP.

2

Cách thú vị để giải quyết loại sự cố đó là sử dụng genetic algorithm. Và trên thực tế, tôi đã làm điều đó cho hồ bơi khúc côn cầu của riêng mình!

Bạn có thể thấy the whole Scala code here nếu bạn tò mò, nhưng trái tim của nó là:

def GA(nbIter: Int, popSize: Int, tournamentSize: Int) = { 
    def loop(iter: Int, pop: Seq[LineUp]): LineUp = { 
    val best = pop.maxBy(_.fitness) 
    println("\nBest of generation " + iter + best) 
    if (iter == nbIter) 
     best 
    else { 
     val newPop = for { 
     _ ← Stream.continually() 
     x = tournament(pop, tournamentSize).head 
     y = (x.mutants take 5) maxBy (_.fitness) 
     } yield y 
     loop(iter + 1, best +: newPop.take(popSize - 1)) 
    } 
    } 
    val initialPop = LineUp.randomStream.take(popSize) 
    loop(0, initialPop) 
} 

Nó bắt đầu bằng cách tạo ra một bộ sưu tập ngẫu nhiên của đội hình hợp lệ (tôn trọng nắp lương và hạn chế vị trí). Đối với mỗi lần lặp lại, sau đó tạo một tập hợp mới bằng cách sử dụng kết hợp của tournament selectionhill climbing. "Thể hình" được định nghĩa đơn giản là số điểm dự kiến ​​cho một đội hình có mức lương thấp nhất làm cầu nối.

Tất nhiên, nếu danh sách người chơi của bạn không quá lớn và bạn có thể đủ khả năng để thuật toán chạy một lúc, bạn sẽ tìm thấy một giải pháp khá tối ưu.