javascript演算法基礎之01揹包,完全揹包,多重揹包實現

小腦fu發表於2019-02-16

01揹包

給定一組物品,每種物品都有自己的重量和價格,在限定的總重量內,我們如何選擇,才能使得物品的總價格最高。

const tList = [1, 2, 3, 4, 5]  // 物品體積
const vList = [3, 4, 10, 7, 4] // 物品價值
const map = {}
function getbag (i, v) {
  if (i === 0) {
    if (tList[0] > v) {
      return 0
    } else {
      return vList[0]
    }
  }
  if (v >= tList[i]) {
    // 放與不放 取最大值
    return Math.max(getbag(i - 1, v), vList[i] + getbag(i - 1, v - tList[i]))    
  } else {
    return getbag(i - 1, v)
  }
}

console.log(getbag(5, 3))

完全揹包

  • 商品不限重複次數
  • 狀態轉移方程從01揹包的f[i][j] = max(f[i-1][j], f[i-1][j-w[i] + v[i])變成f[i][j] = max(f[i-1][j], f[i-1][j-k*w[i]]+k*v[i])且0<k<j/w[i]
const bag = 61

const goodList = [{
  name: `apple`,
  w: 2,
  v: 2
}, {
  name: `book`,
  w: 3,
  v: 7
}, {
  name: `iphone`,
  w: 5,
  v: 40
}, {
  name: `mac`,
  w: 20,
  v: 70
}]

const goodLen = goodList.length

const wList = goodList.map(item => {
  return item.w
})

const vList = goodList.map(item => {
  return item.v
})

const nList = goodList.map(item => {
  return item.name
})

const map = {}

function getMax(i, j) {
  let count = Math.floor(j/wList[i])
  if (i === 0) {
    map[i] = map[i] || {}
    map[i][j] = count
    return count * vList[i]
  }

  if (count === 0) {
    map[i] = map[i] || {}
    map[i][j] = 0
    return getMax(i-1, j)
  } else {
    let maxIdx = 0
    let maxVal = 0
    for (let k = 1; k < count + 1; k++) {
      let kr = getMax(i - 1, j - wList[i] * k) + vList[i] * k
      if (kr > maxVal) {
        maxVal = kr
        maxIdx = k
      }
    }
    map[i] = map[i] || {}
    map[i][j] = maxIdx
    return maxVal
  }
}

const val = getMax(goodLen - 1, bag)

let bagCost = 0
function getSelect(i, j) {
  if (i < 0) {
    return
  }
  let count = 0
  if (map[i] && map[i][j]) {
    count = map[i][j]
  }
  bagCost += wList[i] * count
  console.log(`物品${nList[i]} x ${count}`)
  getSelect(i - 1, j - count * wList[i])
}

getSelect(goodLen - 1, bag)

console.log(`總價值${val}`)
console.log(`揹包負載${bagCost}/${bag}`)
// 物品mac x 1
// 物品iphone x 8
// 物品book x 0
// 物品apple x 0
// 總價值390
// 揹包負載60/61

多重揹包

  • 商品限定重複次數
  • 只需要把重複的物品都看作一個不同的物品,然後轉化成01揹包即可

01揹包帶路徑

const bag = 12
const goodList = [{
  name: `apple`,
  w: 1,
  v: 2
}, {
  name: `book`,
  w: 2,
  v: 10
}, {
  name: `iphone`,
  w: 5,
  v: 40
}, {
  name: `mac`,
  w: 10,
  v: 100
}]

const goodLen = goodList.length

const wList = goodList.map(item => {
  return item.w
})

const vList = goodList.map(item => {
  return item.v
})

const nList = goodList.map(item => {
  return item.name
})

const map = {}
const path = {}

function setMap(i, w, v) {
  map[i] = map[i] || {}
  map[i][w] = v
}

function setPath(i, w) {
  path[i] = path[i] || {}
  // 在重量為w的包裡,是否放置第i個物品以達到最大價值
  path[i][w] = true
}

function getMax(i, w) {
  if (i === 0) {
    if (wList[i] > w) {
      setMap(i, w, 0)
      return 0
    } else {
      setMap(i, w, vList[i])
      setPath(i, w)
      return vList[i]
    }
  }

  if (wList[i] > w) {
    let plan = getMax(i-1,w)
    setMap(i, w, plan)
    return plan
  } else {
    let planA = getMax(i-1, w)
    let planB = getMax(i-1, w-wList[i]) + vList[i]
    if (planB > planA) {
      setMap(i, w, planB)
      setPath(i, w)
      return planB
    } else {
      setMap(i, w, planA)
      return planA
    }
  }
}

const val = getMax(goodLen - 1, bag)
let bagCost = 0

function getSelect(i, j) {
  if (i < 0) {
    return []
  }
  let arr = []
  if (path[i] && path[i][j]) {
    arr.push(i)
    bagCost += wList[i]
    console.log(`物品${nList[i]} x 1`)
    return arr.concat(getSelect(i-1, j-wList[i]))
  } else {
    console.log(`物品${nList[i]} x 0`)
    return arr.concat(getSelect(i-1, j))
  }
}

getSelect(goodLen - 1, bag)
console.log(`物品總價值${val}`)
console.log(`揹包負重${bagCost}/${bag}`)

// 物品mac x 1
// 物品iphone x 0
// 物品book x 1
// 物品apple x 0
// 物品總價值110
// 揹包負重12/12

相關文章