รับการรวมกันที่มีประสิทธิภาพมากที่สุดของรายการวัตถุขนาดใหญ่ตามเขตข้อมูล


9

ฉันต้องการเพิ่มจำนวนดาวตามงบประมาณและขีด จำกัด สูงสุดของชุดค่าผสม

ตัวอย่างคำถาม:

ด้วยงบประมาณ 500 ยูโรเยี่ยมชมร้านอาหารที่อนุญาตสูงสุดหรือน้อยกว่ารับประทานอาหารและเก็บดาวให้ได้มากที่สุด

ฉันกำลังมองหาที่จะเขียนอัลกอริทึมที่มีประสิทธิภาพซึ่งสามารถประมวลผลอินสแตนซ์ร้านอาหาร 1 ล้านร้านได้สูงสุด 10 ร้านอาหาร

หมายเหตุนี่เป็นคำถามข้ามที่ฉันถามเมื่อวานนี้: Java: รับชุดค่าผสมที่มีประสิทธิภาพมากที่สุดของรายการวัตถุขนาดใหญ่ตามเขตข้อมูล

วิธีแก้ปัญหาด้านล่างจะกำหนด 15 $ ต่อดาวให้กับr8ร้านอาหารซึ่งหมายความว่าเมื่อสร้างรายชื่อมันจะใส่เข้าไปในรายการก่อนและส่วนที่เหลือ 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
นี่เป้นไหม? ยกโทษให้ฉันฉันไขมันต่ำ
Kenny Ostrom

1
มันเป็นแนวคิดเดียวกันของเป้ - budget= น้ำหนักเป้สูงสุดในกิโลกรัมmax= จำนวนของสินค้าที่เป้สามารถเก็บไว้stars= ค่าบางอย่างในรายการและcost= น้ำหนักรายการในกิโลกรัม
AK47

3
และปัญหาของรหัสที่โพสต์คืออะไร?
cricket_007

1
@ cricket_007 ตามคำสั่งมันกำหนด 15 $ ต่อดาวให้กับr8ร้านอาหารซึ่งหมายความว่าเมื่อสร้างรายการมันจะใส่มันเข้าไปในรายการก่อนและส่วนที่เหลือ 70 $ จะได้รับ 2 ดาวเท่านั้น อย่างไรก็ตามถ้ามันฉลาดพอที่จะข้ามไป (แม้ว่าจะเป็นเงินดอลลาร์ที่ดีที่สุดต่อดาวr1ร้านอาหารจะเป็นตัวเลือกที่ดีกว่าสำหรับงบประมาณเนื่องจากมีราคา 100 ดอลลาร์และ 5 ดาว
AK47

คำตอบ:


5

ดูเหมือนปัญหาของคุณจะเหมือนกับปัญหาเครื่องหลัง: ให้ได้ค่ามากที่สุดเนื่องจากข้อ จำกัด ด้านน้ำหนักและปริมาตร ค่าพื้นฐาน = ดาวทั้งหมด, น้ำหนัก = ราคา, จำกัด rucksack = งบประมาณทั้งหมด ขณะนี้มีข้อ จำกัด เพิ่มเติมของ "รายการ" ทั้งหมด (การเยี่ยมชมร้านอาหาร) แต่นั่นไม่เปลี่ยนส่วนสำคัญ

อย่างที่คุณทราบหรือไม่อาจทราบได้ว่าปัญหาเครื่องหลังนั้นเป็นปัญหาหนักซึ่งหมายความว่าไม่มีการรู้อัลกอริธึมที่มีการกำหนดเวลาแบบพหุนาม

อย่างไรก็ตามอาจมีอัลกอริทึม pseudopolynomial ที่มีประสิทธิภาพโดยใช้การเขียนโปรแกรมแบบไดนามิกและแน่นอนว่ามีฮิวริสติกที่มีประสิทธิภาพเช่นฮิวริสติก "โลภ" ที่คุณดูเหมือนจะค้นพบ ฮิวริสติกนี้เกี่ยวข้องกับการเริ่มเติมไอเท็ม "ความหนาแน่น" สูงสุด (ดาวส่วนใหญ่ต่อบั๊ก) ก่อน อย่างที่คุณได้เห็นแล้วฮิวริสติกนี้ไม่สามารถหาสิ่งที่เหมาะสมที่สุดในบางกรณี

วิธีการเขียนโปรแกรมแบบไดนามิกน่าจะดีอยู่ที่นี่ ขึ้นอยู่กับการเรียกซ้ำ: ด้วยงบประมาณ B และจำนวนการเยี่ยมชม V ที่เหลืออยู่ร้านอาหารที่ดีที่สุดในการเยี่ยมชมจากร้านอาหารทั้งหมด R คืออะไร

ดูที่นี่: https://en.wikipedia.org/wiki/Knapsack_problem#0/1_knapsack_problem

โดยทั่วไปเรากำหนดอาเรย์mสำหรับ "max stars" ซึ่ง 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] > b ซึ่งเป็นราคาของการรับประทานอาหารที่ร้านอาหารp[i] iบรรทัดนี้พูดว่าอะไร ถ้าร้านอาหารiแพงกว่าที่เรามีเหลือเงิน ( b) เราจะไปที่นั่นไม่ได้ ซึ่งหมายความว่าจำนวนเงินสูงสุดของดาวที่เราจะได้รับเหมือนกันว่าเราจะรวมถึงร้านอาหารขึ้นไปหรือเพียงแค่ขึ้นไปii - 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

วุ้ย. s[i]คือจำนวนดาวที่คุณได้รับจากร้านอาหารibtw

บรรทัดนี้พูดว่าอะไร เป็นหัวใจของวิธีการเขียนโปรแกรมแบบไดนามิก เมื่อพิจารณาจำนวนสูงสุดของดาวที่เราจะได้รับเมื่อมองไปที่ร้านอาหารและรวมไปถึงiจากนั้นในการแก้ปัญหาที่เกิดขึ้นเราไปที่นั่นหรือไม่และเรา "แค่" ต้องดูว่าเส้นทางใดในสองเส้นทางนี้ ดาว:

ถ้าเราไม่ไปร้านอาหารiเราก็เก็บเงินจำนวนเท่าเดิมและการเยี่ยมชมที่เหลืออยู่ จำนวนดาวสูงสุดที่เราจะได้รับในเส้นทางนี้เหมือนกับว่าเราไม่ได้ดูร้านอาหารiด้วยซ้ำ maxนั่นคือส่วนแรกใน

แต่ถ้าเราไปที่ร้านอาหารiเราก็เหลือp[i]เงินน้อยกว่าเยี่ยมน้อยกว่าและs[i]อีกหลายดวง maxนั่นเป็นส่วนที่สองใน

ตอนนี้คำถามง่าย ๆ : ข้อใดที่สองมีขนาดใหญ่กว่า

คุณสามารถสร้างอาเรย์นี้และเติมเต็มให้กับลูปง่าย ๆ (รับแรงบันดาลใจจากวิกิ) นี่เป็นเพียงปริมาณดาวที่คุณได้รับไม่ใช่รายการร้านอาหารที่ควรไปเยี่ยมชม wเพื่อที่เพิ่มบางทำบัญชีพิเศษในการคำนวณของ


ฉันหวังว่าข้อมูลจะเพียงพอที่จะทำให้คุณออกไปในทิศทางที่ถูกต้อง

หรือคุณสามารถเขียนปัญหาของคุณในแง่ของตัวแปรไบนารีและฟังก์ชันวัตถุประสงค์กำลังสองและแก้ปัญหาใน D-Wave ควอนตัม annelaer :-p ส่งข้อความถึงฉันถ้าคุณต้องการทราบเพิ่มเติมเกี่ยวกับเรื่องนั้น


เกี่ยวกับเวลาพหุนามสูงสุด 10 ร้านอาหารหมายถึงปัญหาสามารถแก้ไขได้โดยการใช้กำลังดุร้ายทำซ้ำในร้านอาหารรวมกันทั้งหมด 10 ร้านและรักษาร้านที่ดีที่สุดในเวลา O (n ^ 10) ตอนนี้ฉันไม่ต้องการเรียกใช้อัลกอริทึม O (n ^ 10) ด้วย n = 10 ^ 6 เช่นกัน แต่เป็นเวลาพหุนาม
kaya3

คือ "10 ร้านอาหาร" เป็นหมายเลขที่แน่นอนจริง ๆ หรือเพียงแค่แก้ไขในตัวอย่างข้างต้นและอาจใหญ่กว่าสำหรับตัวอย่างอื่นได้หรือไม่
Lagerbaer

นั่นเป็นคำถามที่ดีและยังไม่ชัดเจนว่าจะใช้พารามิเตอร์ใดของปัญหาในการวิเคราะห์เวลาทำงาน แน่นอนว่าไม่มีวิธีแก้ปัญหาที่รู้จักกันในชื่อพหุนามใน k ผมแค่หมายความว่ามันเป็นข้อสรุปที่ค่อนข้างอ่อนแอถ้าเราสนใจเพียงปัญหาเล็ก ๆ k
kaya3

จำนวนร้านอาหาร "สูงสุด" สามารถเปลี่ยนแปลงได้ การทำซ้ำนี้อาจเป็น 10 และถัดไปอาจเป็น 5
AK47

@ AK47 ไม่ว่าอัลกอริทึมที่ฉันร่างไว้ข้างต้นควรจะค่อนข้างเรียบร้อย ขนาดของอาเรย์หลายมิตินั้นกำหนดโดยงบประมาณของคุณจำนวนร้านอาหารและจำนวนการเข้าชมและใช้เวลา O (1) ในการกรอกข้อมูลในหนึ่งอาเรย์ดังนั้นอัลโกจึงทำงานในเวลา O (R B V)
Lagerbaer

2

ใช้แนวคิดเดียวกันกับคำตอบของฉันที่นี่ :

ในชุดของตัวเลขบวก n ที่รวมกันเป็น S อย่างน้อยหนึ่งในนั้นจะน้อยกว่า S หารด้วย n (S / n)

คุณสามารถสร้างรายการเริ่มต้นจากการที่มีศักยภาพ "ถูกที่สุด" ร้านอาหาร

ขั้นตอนของอัลกอริทึม:

  • ค้นหา 5 ร้านอาหารที่มีค่าใช้จ่าย <500/10 แต่ละคนมีดาวที่แตกต่างกันและค่าใช้จ่ายที่ต่ำที่สุดสำหรับแต่ละดาว เช่น r1, r2, r3, r4, r5
  • สำหรับแต่ละค่าข้างต้นพบอีก 5 ร้านอาหารที่มีค่าใช้จ่าย <(500 - ค่าใช้จ่าย (x)) / 9 และดาวที่แตกต่างกัน เลือกต้นทุนที่ต่ำที่สุดสำหรับแต่ละดาวอีกครั้ง
  • ทำเช่นนี้จนกว่าคุณจะไปถึง 10 ร้านอาหารและไม่เกินงบประมาณ
  • รัน 3 ขั้นตอนข้างต้นอีกครั้งสำหรับร้านอาหาร 1 - 9
  • ใช้วิธีแก้ปัญหาที่สร้างดาวมากที่สุด

แน่นอนคุณไม่สามารถเลือกร้านอาหารได้อีกครั้ง

ฉันคิดว่ากรณีที่เลวร้ายที่สุดคุณจะต้องคำนวณ 5x5x5 ... = 5 ^ 10 + 5 ^ 9 + ... + 5 ^ 2 + 5 (= ประมาณ 12 ล้าน) โซลูชั่น

ในจาวาสคริปต์

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 ใช้เวลา 27 วินาทีกับร้านอาหารกว่า 100,000 แห่ง: repl.it/repls/StripedMoralOptimizationคุณคิดว่าเป็นไปได้หรือไม่ที่จะเพิ่มประสิทธิภาพในการทำงานกับบันทึก 1 ล้านรายการ?
AK47

คอขวดคือ. ฟิลเตอร์ () ฟังก์ชั่นภายใน findCheapestRestaurant () คุณสามารถเรียงลำดับ () ร้านอาหารที่มีราคาถูกหลังจากที่สร้างและใช้. find () แทนที่จะเป็นตัวกรอง () เนื่องจากตัวที่ 1 เท่านั้นที่พบว่าถูกที่สุด ฉันทำการเปลี่ยนแปลงในลิงค์ แต่ฉันคิดว่าทางออกที่ดีที่สุดคือการใช้ฐานข้อมูล (เช่น mysql) สำหรับร้านอาหารที่มีดัชนีราคาเพื่อให้คุณสามารถแทนที่. ฟิลเตอร์ () ด้วยการเลือกแบบมีเงื่อนไข
Jannes Botis
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.