package com.pan.test.domain;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.assertj.core.util.Lists;
import java.io.Serializable;
import java.util.*;
public class AlgorithmTest {
public static void main(String[] args) {
//eg1:揹包問題,限定重量 5kg .
dpAlgorithm();
//eg1:多集合交集覆蓋範圍問題.
}
/**
* 動態規劃:揹包問題,限定重量 5kg ,選取價值最大的商品組合.(可以重複選取)
* <p>
* 問題分析:該類問題,選取個數無限制,唯一限制的是重量(權重).
* 對於該類問題,使用動態規劃的原理是,每個因子,都是可以替換的.
* 故而可以採用遞歸選優的方式,層級遞歸.(這裏可以用循環代替,但思想是層級遞歸)
* 也就是說,第一層與第二層在限定條件下,選取最優解,選取出來的最優解與第三層比較,選取更優解.
* 直到所有因子選完,則該解爲最優解.
* <p>
* ps:此類算法無法解決球隊選球員問題.及多集合選取最優解,每個集合只能選取一個.不能解決此問題的原因是,一個球隊是一個整體,
* 這個整體的最優解,來源依賴每一個位置的集合.而每個位置只能有一個人.他的權重比值是分佈在整個球員身上.
* 也無法採用迪克斯特拉算法,因爲這裏存在限制條件,而這個限制條件是整個球隊的.所以球員的限制條件其實是不確定的.如果採用迪克斯特拉算法,
* 那麼,選取出來的路徑,可能走不到終點.
*/
public static void dpAlgorithm() {
//待選商品
Product banana = new Product("香蕉", 7, 1);
Product apple = new Product("蘋果", 15, 2);
Product orange = new Product("桔子", 24, 3);
Product durian = new Product("榴蓮", 31, 4);
List<Product> products = Lists.newArrayList(apple, banana, durian, orange);
//100個揹包
for (int i = 0; i < 100; i++) {
Bag bag = new Bag("小揹包" + i, 3 + i, new ArrayList<>(), 0, 0);
for (int j = 0; j < products.size(); j++) {
Product product = products.get(j);
bag = fillBag(bag, product, products);
}
System.out.println("揹包:" + bag.getBagName() + "\t 總價:" + bag.getSumPrice() + "\t 限重:" + bag.getMaxWeight() + "\t 總重量:" + bag.getSumWeight());
//輸出選取商品名
String pickName = null;
List<Product> bagProducts = bag.getProducts();
for (int j = 0; j < bagProducts.size(); j++) {
Product product = bagProducts.get(j);
if (pickName == null) {
pickName = product.getName();
continue;
}
pickName += "-" + product.getName();
}
System.out.println("揹包:" + bag.getBagName() + "\t 挑選的商品:" + pickName);
}
}
/**
* 將商品放入揹包
*
* @param bag
* @param product
* @param products
* @return
*/
public static Bag fillBag(Bag bag, Product product, List<Product> products) {
Map<Float, Product> productMap = setupProductMap(products);
float minWeight = products.stream().min(Comparator.comparing(Product::getWeight)).get().getWeight();
float maxWeight = bag.getMaxWeight();
List<Product> pickList = bag.getProducts();
float sumPrice = bag.getSumPrice();
float sumWeight = bag.getSumWeight();
//第一次塞入
if (CollectionUtils.isEmpty(pickList)) {
while (sumWeight <= maxWeight - product.getWeight()) {
pickList.add(product);
sumWeight += product.getWeight();
sumPrice += product.getPrice();
}
} else {
//替換
List<Product> needAdd = new ArrayList<>();
List<Product> needRemove = new ArrayList<>();
List<Product> subRemove = new ArrayList<>();
float removeWeight = 0;
float removePrice = 0;
for (int i = 0; i < pickList.size(); i++) {
Product tmp = pickList.get(i);
//單個替換
if (tmp.getPrice() / tmp.getWeight() < product.getPrice() / product.getWeight() && tmp.getWeight() + (maxWeight - sumWeight) >= product.getWeight()) {
sumWeight += product.getWeight() - tmp.getWeight();
sumPrice += product.getPrice() - tmp.getPrice();
needAdd.add(product);
needRemove.add(tmp);
}
//組合替換
subRemove.add(tmp);
removeWeight += tmp.getWeight();
removePrice += tmp.getPrice();
if (removePrice / removeWeight < product.getPrice() / product.getWeight() && removeWeight + (maxWeight - sumWeight) >= product.getWeight()) {
sumWeight += product.getWeight() - removeWeight;
sumPrice += product.getPrice() - removePrice;
needAdd.add(product);
needRemove.addAll(subRemove);
subRemove.clear();
removeWeight = 0;
removePrice = 0;
}
//剩餘空間塞值
float surplus = maxWeight - sumWeight;
Product surPlus = getSurPlus(products, productMap, surplus);
while (surplus >= minWeight && surPlus != null) {
sumWeight += surPlus.getWeight();
sumPrice += surPlus.getPrice();
needAdd.add(surPlus);
surplus = maxWeight - sumWeight;
surPlus = getSurPlus(products, productMap, surplus);
}
}
//刪除替換掉的
Iterator<Product> removes = needRemove.iterator();
Iterator<Product> pickIt = pickList.iterator();
while (removes.hasNext()) {
Product remove = removes.next();
while (pickIt.hasNext()) {
Product pick = pickIt.next();
if (pick.equals(remove)) {
pickIt.remove();
removes.remove();
break;
}
}
}
pickList.addAll(needAdd);
}
bag.setMaxWeight(maxWeight);
bag.setSumPrice(sumPrice);
bag.setSumWeight(sumWeight);
bag.setProducts(pickList);
return bag;
}
/**
* 獲取指定可選重量下,最佳選擇的商品
*
* @param products
* @param setupProductMap
* @param surplus
* @return
*/
private static Product getSurPlus(List<Product> products, Map<Float, Product> setupProductMap, float surplus) {
Product product = setupProductMap.get(surplus);
if (product != null) {
return product;
}
//重新查找
for (int i = 0; i < products.size(); i++) {
Product tmp = products.get(i);
if (tmp.getWeight() <= surplus) {
if (product == null) {
product = tmp;
} else if (product.getPrice() < tmp.getPrice()) {
product = tmp;
}
}
}
setupProductMap.put(surplus, product);
return product;
}
/**
* 相同商品重量的最高價值商品map
*
* @param products
* @return
*/
public static Map<Float, Product> setupProductMap(List<Product> products) {
Map<Float, Product> productMap = new HashMap<>();
for (int i = 0; i < products.size(); i++) {
Product tmp = products.get(i);
Product old = productMap.get(tmp.getWeight());
if (old == null) {
productMap.put(tmp.getWeight(), tmp);
continue;
}
if (old.getPrice() < tmp.getPrice()) {
productMap.put(tmp.getWeight(), tmp);
continue;
}
}
return productMap;
}
}
/**
* 揹包實體類
*/
class Bag implements Serializable {
private static final long serialVersionUID = 3554907573665360761L;
public Bag(String bagName, float maxWeight, List<Product> products, float sumPrice, float sumWeight) {
this.bagName = bagName;
this.maxWeight = maxWeight;
this.products = products;
this.sumPrice = sumPrice;
this.sumWeight = sumWeight;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Bag)) return false;
Bag bag = (Bag) o;
return new EqualsBuilder()
.append(getMaxWeight(), bag.getMaxWeight())
.append(getSumPrice(), bag.getSumPrice())
.append(getSumWeight(), bag.getSumWeight())
.append(getBagName(), bag.getBagName())
.append(getProducts(), bag.getProducts())
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(getBagName())
.append(getMaxWeight())
.append(getProducts())
.append(getSumPrice())
.append(getSumWeight())
.toHashCode();
}
private String bagName;
private float maxWeight;
private List<Product> products;
private float sumPrice;
private float sumWeight;
public String getBagName() {
return bagName;
}
public void setBagName(String bagName) {
this.bagName = bagName;
}
public float getMaxWeight() {
return maxWeight;
}
public void setMaxWeight(float maxWeight) {
this.maxWeight = maxWeight;
}
public List<Product> getProducts() {
return products;
}
public void setProducts(List<Product> products) {
this.products = products;
}
public float getSumPrice() {
return sumPrice;
}
public void setSumPrice(float sumPrice) {
this.sumPrice = sumPrice;
}
public float getSumWeight() {
return sumWeight;
}
public void setSumWeight(float sumWeight) {
this.sumWeight = sumWeight;
}
}
/**
* 商品實體類
*
*/
class Product implements Serializable {
private static final long serialVersionUID = 5594269264643217406L;
public Product(String name, float price, float weight) {
this.name = name;
this.price = price;
this.weight = weight;
}
private String name;
private float price;
private float weight;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
}