Задача о рюкзаке

Из заданного набора вещей надо выбрать, что положить в рюкзак, так чтобы суммарная цена была максимальна и вес не превысил заданной величины.

наименование часы картина радио ваза книга компьютер
цена 175 90 20 50 10 200
вес 10 9 4 2 1 20

Создадим список объектов


function buildItems()
{
	const names = ['часы', 'картина', 'радио', 'ваза', 'книга', 'компьютер']
	const values = [175, 90, 20, 50, 10, 200]
	const weights = [10, 9, 4, 2, 1, 20]
	const items = []
	for (let i = 0; i < values.length; i++)
	{
		items.push({
			name:names[i], 
			value: values[i], 
			weight: weights[i]
		})
	} 
	return items
}
const items = buildItems()

Подмножество вещей мы будем обозначать массивом из 0, 1. 1 если мы берём вешь и ноль, если не берём. Создадим генератор таких последовательностей.


function* genBin(n)//на каждоме шаке получеам 
//увеличенное на 1 двоичное чилсо
{
	let b = []
	for(let i = 0; i < n; i++)
		b.push(0)
	yield b
	let i = n - 1
	while(true)
	{
		if(i == -1)
		{
			return
		}
		else if( b[i] == 1)
		{
			b[i] = 0
			i -= 1
		}
		else
		{
			b[i] = 1
			i = n - 1
			yield b
		}
	}
}
for(let x of genBin(6))//проверим как он рабтает
	console.log(x)

Используя этот генератор, создадим следующий, который будет генерировать всевозможные подмножества из данного множества


function* genPowerSet(items)
{
	for(let x of genBin(items.length))
	{
		let taken = []
		for (let i = 0; i < x.length; i++)
		{
			if (x[i] == 1) taken.push(items[i])
		}
		yield taken
	}
}

Создадим фильтр генерирующий только подходящие по весу наборы

function weightTest(taken, maxWeight)
{
	totalWeight = 0
	for(let t of taken)
	{
		totalWeight += t.weight 
		if(totalWeight > maxWeight)
		{
			return false
		}
	}
	return true
}



function* weightFilter(maxWeight, genSet)
{
	for(let taken of genSet)
	{
		if (weightTest(taken, maxWeight))
			yield taken
	}
	return
}

//Проверим
let setF = weightFilter(20, genPowerSet(items))
for(let taken of setF) console.log(taken) 

Выбреме набор имещий максимальную цену

function genSolution(xFGen)
{
	let totalValue = 0
	let taken = []
	for (let x of xFGen)
	{
		if (value(x) > totalValue)
		{
			totalValue = value(x)
			taken = x.slice()
		}
	}
	return {totalValue: totalValue, taken: taken}
}

function value(taken)
{
	let totalValue = 0
	for(let item of taken)
	{
		totalValue += item.value 
	}
	return totalValue
}
//Проверим
let  sol = genSolution(setF)
console.log(sol.taken)
console.log(sol.totalValue)

Жадный алгоритм

Приближённый метод решения, мы сортируем вещи в наборе по убыванию, по какому-то свойству, например по цене. И затем выбираем те из них, которые подходят по весу.


function greedy(items, maxWeight, cmpFunction)
{
	items.sort(cmpFunction)
	items.reverse()
	let totalWeight = 0
	let totalValue = 0
	const taken = []
	for (let i = 0; i < items.length; i++)
	{
		if( totalWeight + items[i].weight <= maxWeight)
		{
			taken.push(items[i].name)
			totalWeight += items[i].weight
			totalValue += items[i].value
		}
	}
	return {totalValue: totalValue, taken: taken}
}

Приведём примеры функций сортировки по цене и обратному весу.

function cmpValue(itemA, itemB)
{
	let r = itemA.value - itemB.value
	return r
}

function cmpWeightInverse(itemA, itemB)
{
	let r = 1/itemA.weight - 1/itemB.weight
	return r
}

Создадим функцию, которая будет выводить результат

function testGreedy(items, maxWeight, cmpFunction)
{
	const {totalValue, taken} = greedy(items, maxWeight, cmpFunction)
	console.log("Полная цена = ", totalValue)
	console.log("Взяли:", taken)
}

Проверим как все работает

console.log("Сортировка по цене даёт:")
testGreedy(items, 20, cmpValue)
console.log("Сортировка по обратному весу даёт:")
testGreedy(items, 20, cmpWeightInverse)

Решение для сортировки по удельной цене (цена/вес) найдите самостоятельно.