根据字段获取大型对象列表的最有效组合


9

我希望在给定预算和组合上限的情况下,最大限度地增加星级。

问题示例:

用500欧元的预算,只参观允许的最大餐厅或以下,就餐并收集尽可能多的星星。

我正在寻找一种有效的算法,该算法可以处理最多100个最大餐厅的100万个餐厅实例。

注意,这是我昨天问的一个问题的交叉发布: Java:基于字段获取大型对象列表的最有效组合

下面的解决方案将为r8餐厅分配每颗星15美元的价格,这意味着在生成列表时,它将首先放入列表中,而剩下的70美元只能再获得2颗星,总共4颗星。但是,如果明智的做法是跳过r8餐厅(即使这是每星级最高的美元),那么r1餐厅实际上是预算的更好选择,因为这是100美元的费用和5星的价格。

任何人都可以帮助解决问题并超越当前的解决方案吗?

import itertools

class Restaurant():
  def __init__(self, cost, stars):
    self.cost = cost
    self.stars = stars
    self.ratio = cost / stars

  def display(self):
    print("Cost: $" + str(self.cost))
    print("Stars: " + str(self.stars))
    print()

r1 = Restaurant(100, 5)
r2 = Restaurant(140, 3)
r3 = Restaurant(90, 4)
r4 = Restaurant(140, 3)
r5 = Restaurant(120, 4)
r6 = Restaurant(60, 1)
r7 = Restaurant(40, 1)
r8 = Restaurant(30, 2)
r9 = Restaurant(70, 2)
r10 = Restaurant(250, 5)

print()
print("***************")
print("** Unsorted: **")
print("***************")
print()

restaurants = [r1, r2, r3, r4, r5, r6, r7, r8, r9, r10]

for restaurant in restaurants:
  print(restaurant.ratio, restaurant.stars)

print()
print("***************")
print("**  Sorted:  **")
print("***************")
print()

sorted_restaurants = sorted(restaurants, key = lambda x: x.ratio, reverse = True)

for restaurant in sorted_restaurants:
  print(restaurant.ratio, restaurant.stars)

print()
print("*********************")
print("** Begin Rucksack: **")
print("*********************")
print()

max = 5
budget = 100

spent = 0
quantity = 0

rucksack = []

for i in itertools.count():

  if len(rucksack) >= max or i == len(sorted_restaurants):
    break

  sorted_restaurants[i].display()

  if sorted_restaurants[i].cost + spent <= budget:
    spent = spent + sorted_restaurants[i].cost
    rucksack.append(sorted_restaurants[i])

print("Total Cost: $" + str(sum([x.cost for x in rucksack])))
print("Total Stars: " + str(sum([x.stars for x in rucksack])))

print()
print("*****************")
print("** Final List: **")
print("*****************")
print()

for restaurant in rucksack:
  restaurant.display()

2
这是背包吗?原谅我,我略读了一下。
肯尼·奥斯特罗姆

1
这与背包的概念相同– budget=背包的最大重量(以公斤为单位),max=背包可以容纳的物品数量,stars=该物品的某些值,以及cost=物品的重量(公斤)
AK47,19年

3
发布的代码有什么问题?
cricket_007 '19

1
@ cricket_007根据订单,为r8餐厅分配每颗星15美元,这意味着在生成列表时,它将首先放入列表中,而剩下的70美元只能获得2星。但是,如果它是足够聪明,可以跳过(即使它是每个明星的比例最好美元,该r1餐厅实际上是为预算更好的选择,因为它是100 $的成本和5星
AK47

Answers:


5

听起来您的问题与背包问题几乎相同:在一定重量和体积约束下最大化价值。基本上,价值=星星总数,重量=价格,背包极限=总预算。现在,总“项目”(餐厅访问)还有一个额外的约束,但这并没有改变要点。

您可能知道也可能不知道,背包问题是NP难题,这意味着没有多项式时间缩放算法。

但是,可能存在使用动态编程的有效伪多项式算法,并且当然存在有效的启发式方法,例如您似乎已经发现的“贪婪”启发式方法。这种启发式方法涉及首先开始填充最高的“密度”项目(每块钱最多的星星)。如您所见,这种启发式方法在某些情况下无法找到真正的最优值。

动态编程方法在这里应该很好。它基于递归:给定预算B和剩余访问次数V,在总餐厅R中,最适合参观的餐厅是哪一组?

看到这里:https : //zh.wikipedia.org/wiki/背包的问题#0 / 1_背包的问题

基本上,我们m为“最大星级” 定义了一个数组,其中 m[i, b, v]是当我们允许访问的餐厅数量达到(并包括)餐厅数量i,最多消费和最多b访问最多v餐厅(限制)时可获得的最大星级数量。

现在,我们自下而上地填充此数组。例如, m[0, b, v] = 0对于所有的值b,并v因为如果我们不能去任何餐厅,我们不能让任何明星。

此外,m[i, b, 0] = 0对于所有的值i,并b因为如果我们用完了所有我们的访问,我们不能得到任何更多的星星。

下一行也不太难:

m[i, b, v] = m[i - 1, b, v] if p[i] > bp[i]在餐厅用餐的价格 在哪儿i?这行说什么?好吧,如果餐厅i比我们剩下的钱还贵(b),那么我们不能去那里。这意味着无论我们包括的餐厅数量最多i还是最多,我们可以得到的最大星级数量都是相同的i - 1

下一行有点棘手:

m[i, b, v] = max(m[i-1, b, v]), m[i-1, b - p[i], v-1] + s[i]) if p[i] <= b

ew s[i]是从ibtw 餐厅获得的星级数量。

这行说什么?这是动态编程方法的核心。考虑到最大星数时,我们在查看时可到的餐厅(包括)i,然后在结果解决方案中我们要么去那里,要么不去那里,我们“只是”必须看看这两个路径中的哪个导致更多星星:

如果我们不去餐厅i,那么我们会保留相同的金额和剩余的访问量。我们在这条路径上可获得的最大星星数量与我们甚至不看餐厅的情况相同i。那是的第一部分max

但是,如果我们确实去餐厅用餐i,那么我们所剩的p[i]钱就更少了,拜访的次数减少了,s[i]星级也更多了。那是的第二部分max

现在的问题很简单:两者中的哪个更大。

您可以创建此数组,并使用相对简单的for循环填充它(从Wiki中汲取灵感)。但是,这只是为您提供星级数量,而不是实际要访问的餐馆列表。为此,在的计算中添加一些额外的簿记w


我希望这些信息足以使您朝正确的方向前进。

或者,您可以根据二进制变量和二次目标函数来编写问题,并在D-Wave量子退火仪上解决问题:-p如果您想了解更多有关此信息,请给我发送消息。


关于多项式时间,最多10个餐厅意味着可以用蛮力解决问题,迭代最多10个餐厅的所有组合,并在O(n ^ 10)时间内保持最佳状态。现在,我也不想运行n = 10 ^ 6的O(n ^ 10)算法,但这是多项式时间。
kaya3 '19

“ 10家餐厅”是真正固定的数字,还是在上面的示例中是固定的,而对于另一个示例,该数字可能会更大?
Lagerbaer

这是一个很好的问题,尚不清楚在分析运行时间时要概括出问题的哪些参数。当然,没有已知的解决方案是k的多项式,如果我们只对小k的问题感兴趣,那只是一个很弱的结论。
kaya3

餐厅的“最大”数量可能会改变。该迭代可以是10,并且下它可以是5
AK47

@ AK47无论如何,我上面概述的算法应该非常简洁。多维数组的大小由您的预算,餐厅数量和访问次数决定,而O(1)填写数组的一个条目就需要O(1),因此算法的运行时间为O(R B V)。
Lagerbaer

2

使用与我的回答相同的想法:

在总计为S的n个正数的集合中,其中至少一个小于S除以n(S / n)

您可以从潜在的“最便宜”的餐厅开始建立清单。

该算法的步骤:

  • 查找5个价格低于500/10的餐厅,每个餐厅的星级不同,每星级的成本最低。例如r1,r2,r3,r4,r5
  • 对于上述每个值,找到另外5家价格<(500-cost(x))/ 9且星级不同的餐厅。再次为每颗星星选择最低的费用
  • 直到您到达10家餐厅并且您的预算不超过预算为止。
  • 重新运行上述3个步骤,以限制1-​​9家餐厅。
  • 保留产生最多星星的解决方案

当然,您不能重新选择餐厅。

我认为最坏的情况是,您将不得不计算5x5x5 ... = 5 ^ 10 + 5 ^ 9 + ... + 5 ^ 2 + 5(约等于1200万)解决方案。

在JavaScript中

function Restaurant(name, cost, stars) {
    this.name = name;
    this.cost = cost;
    this.stars = stars;
}

function RestaurantCollection() {
    var restaurants = [];
    var cost = 0;
    this.stars = 0;

    this.addRestaurant = function(restaurant) {
        restaurants.push(restaurant);
        cost += restaurant.cost;
        this.stars += restaurant.stars;
    };

    this.setRestaurants = function(clonedRestaurants, nCost, nStars) {
        restaurants = clonedRestaurants;
        cost = nCost;
        this.stars += nStars;
    };
    this.getAll = function() {
        return restaurants;
    };

    this.getCost = function() {
        return cost;
    };
    this.setCost = function(clonedCost) {
        cost = clonedCost;
    };

    this.findNext5Restaurants = function(restaurants, budget, totalGoal) {
        var existingRestaurants = this.getAll();
        var maxCost = (budget - cost) / (totalGoal - existingRestaurants.length);
        var cheapestRestaurantPerStarRating = [];
        for(var stars = 5; stars > 0; stars--) {
            var found = findCheapestRestaurant(restaurants, stars, maxCost, existingRestaurants);
            if(found) {
                cheapestRestaurantPerStarRating.push(found);
            }
        }
        return cheapestRestaurantPerStarRating;
    };

    this.clone = function() {
        var restaurantCollection = new RestaurantCollection();
        restaurantCollection.setRestaurants([...restaurants], this.getCost(), this.stars);
        return restaurantCollection;
    };
}

function findCheapestRestaurant(restaurants, stars, maxCost, excludeRestaurants) {
     var excludeRestaurantNames = excludeRestaurants.map(restaurant => restaurant.name);
     var found = restaurants.find(restaurant => restaurant.stars == stars && restaurant.cost <= maxCost && !excludeRestaurantNames.includes(restaurant.name));
     return found;
}

function calculateNextCollections(restaurants, collections, budget, totalGoal) {
    var newCollections = [];
    collections.forEach(collection => {
        var nextRestaurants = collection.findNext5Restaurants(restaurants, budget, totalGoal);
        nextRestaurants.forEach(restaurant => {
            var newCollection = collection.clone();
            newCollection.addRestaurant(restaurant);
            if(newCollection.getCost() <= budget) {
                 newCollections.push(newCollection);
            }
        });
    });
    return newCollections;
};

var restaurants = [];
restaurants.push(new Restaurant('r1', 100, 5));
restaurants.push(new Restaurant('r2',140, 3));
restaurants.push(new Restaurant('r3',90, 4));
restaurants.push(new Restaurant('r4',140, 3));
restaurants.push(new Restaurant('r5',120, 4));
restaurants.push(new Restaurant('r6',60, 1));
restaurants.push(new Restaurant('r7',40, 1));
restaurants.push(new Restaurant('r8',30, 2));
restaurants.push(new Restaurant('r9',70, 2));
restaurants.push(new Restaurant('r10',250, 5));

restaurants.sort((a, b) => a.cost - b.cost);
var max = 5;
var budget = 100;

var total = max;
var totalCollections = [];

for(var totalGoal = total; totalGoal > 0; totalGoal--) {
    var collections = [new RestaurantCollection()];

    for(var i = totalGoal; i > 0; i--) {
        collections = calculateNextCollections(restaurants, collections, budget, totalGoal);
    }
    totalCollections = totalCollections.concat(collections);
}

var totalCollections = totalCollections.map(collection => { 
      return {
          name: collection.getAll().map(restaurant => restaurant.name),
          stars: collection.stars,
          cost: collection.getCost()
      }
});

console.log("Solutions found:\n");
console.log(totalCollections);

totalCollections.sort((a, b) => b.stars - a.stars);
console.log("Best solution:\n");
console.log(totalCollections[0]);


嘿@Jannes Botis,100000家餐厅花费27秒:repl.it/repls/StripedMoralOptimization您认为可以对其进行优化以处理100万条记录吗?
AK47

瓶颈是findCheapestRestaurant()中的.filter()函数,您可以在创建餐厅后按成本对(sort)餐馆进行排序,并使用.find()而不是filter(),因为仅找到的第一个会最便宜。我在链接中进行了更改。但是我认为最好的解决方案是为具有成本索引的餐厅使用数据库(例如mysql),以便您可以将.filter()替换为条件选择。
Jannes Botis
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.