501 lines
12 KiB
C++
501 lines
12 KiB
C++
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
#include "stdpch.h"
|
|
#include "knapsack_solver.h"
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CKnapsackSolver //
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
std::string CKnapsackSolver::toString(Algorithm a)
|
|
{
|
|
switch (a)
|
|
{
|
|
case Optimal: return "Optimal";
|
|
case FullAddCheck: return "FullAddCheck";
|
|
case AddCheck: return "AddCheck";
|
|
case FastAddCheck: return "FastAddCheck";
|
|
case FullSingleReplace: return "FullSingleReplace";
|
|
case SingleReplace: return "SingleReplace";
|
|
case FastSingleReplace: return "FastSingleReplace";
|
|
case VeryFastSingleReplace: return "VeryFastSingleReplace";
|
|
case TakeAll: return "TakeAll";
|
|
default: return "undefined";
|
|
}
|
|
}
|
|
|
|
CKnapsackSolver::Algorithm CKnapsackSolver::fromString(std::string const& a)
|
|
{
|
|
if (a=="Optimal") return Optimal;
|
|
if (a=="FullAddCheck") return FullAddCheck;
|
|
if (a=="AddCheck") return AddCheck;
|
|
if (a=="FastAddCheck") return FastAddCheck;
|
|
if (a=="FullSingleReplace") return FullSingleReplace;
|
|
if (a=="SingleReplace") return SingleReplace;
|
|
if (a=="FastSingleReplace") return FastSingleReplace;
|
|
if (a=="VeryFastSingleReplace") return VeryFastSingleReplace;
|
|
if (a=="TakeAll") return TakeAll;
|
|
return UndefinedAlgorithm;
|
|
}
|
|
|
|
/// Find the set that fit the specified maximum weight which have the maximal
|
|
/// value total, using an already defined good solution.
|
|
void CKnapsackSolver::optimize(float wMax, Algorithm algorithm)
|
|
{
|
|
H_AUTO(CKnapsackSolver_optimize);
|
|
_WMax = wMax;
|
|
// Solve the problem
|
|
switch (algorithm)
|
|
{
|
|
case FullAddCheck: optimizeFullAddCheck(); break;
|
|
case AddCheck: optimizeAddCheck(); break;
|
|
case FastAddCheck: optimizeFastAddCheck(); break;
|
|
case FullSingleReplace: optimizeFullSingleReplace(); break;
|
|
case SingleReplace: optimizeSingleReplace(); break;
|
|
default:
|
|
case FastSingleReplace: optimizeFastSingleReplace(); break;
|
|
case VeryFastSingleReplace: optimizeVeryFastSingleReplace(); break;
|
|
case Optimal: optimizeOptimal(); break;
|
|
case TakeAll: optimizeTakeAll(); break;
|
|
}
|
|
}
|
|
|
|
/// Algorithm is taken from http://eleves.ensmp.fr/P00/00rouaul/sacados/sacados_swp.html
|
|
/// This algorithm complexity is O(N^2)
|
|
void CKnapsackSolver::optimizeOptimal()
|
|
{
|
|
H_AUTO(CKnapsackSolver_optimizeOptimal);
|
|
// :FIXME: Not thread safe
|
|
// Allocate temporary solution
|
|
bool* take = new bool[size()];
|
|
for (size_t i=0; i<size(); ++i)
|
|
take[i] = false;
|
|
// Run the optimization recursion
|
|
optimizeOptimalRec((int)size()-1, _WMax, 0, take);
|
|
// Delete temporary solution
|
|
delete [] take;
|
|
}
|
|
|
|
/// @param i take[j] for j>i are already determined by the recursion
|
|
/// @param w Free weight to fill
|
|
/// @param v Sum of the taken values (ie all value(j) where take[j] is true and j > i)
|
|
/// @param take Current examined partial solution
|
|
void CKnapsackSolver::optimizeOptimalRec(int i, float w, float v, bool* take)
|
|
{
|
|
nlassert(i>=-1);
|
|
if (i==-1)
|
|
{
|
|
if (v > _VBest)
|
|
{
|
|
_WBest = _WMax - w;
|
|
_VBest = v;
|
|
std::copy(take, take+size(), _Take);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
take[i] = false;
|
|
optimizeOptimalRec(i-1, w, v, take);
|
|
if (weight(i) <= w)
|
|
{
|
|
take[i] = true;
|
|
optimizeOptimalRec(i - 1, w - weight(i), v + value(i), take);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Here we consider already defined solution has lots of take[i] that are
|
|
/// true. We just find the first i for which take[i] is false and that don't
|
|
/// exceed maximal weight. If we find one we take it which gives a better
|
|
/// solution.
|
|
/// Note: We start at the end since CTargetable puts candidate at end.
|
|
/// This algorithm complexity is O(N)
|
|
void CKnapsackSolver::optimizeAddCheck()
|
|
{
|
|
H_AUTO(CKnapsackSolver_optimizeAddCheck);
|
|
int i = (int)size()-1;
|
|
float w = _WMax - _WBest;
|
|
while (i>=0)
|
|
{
|
|
if (!_Take[i] && weight(i) <= w)
|
|
{
|
|
_Take[i] = true;
|
|
_WBest += weight(i);
|
|
_VBest += value(i);
|
|
break;
|
|
}
|
|
--i;
|
|
}
|
|
}
|
|
|
|
/// Same as AddCheck, except that we consider all false take[i], even if we already took some.
|
|
/// This algorithm complexity is Theta(N)
|
|
void CKnapsackSolver::optimizeFullAddCheck()
|
|
{
|
|
H_AUTO(CKnapsackSolver_optimizeFullAddCheck);
|
|
int i = (int)size()-1;
|
|
float w = _WMax - _WBest;
|
|
while (i>=0)
|
|
{
|
|
if (!_Take[i] && weight(i) <= w)
|
|
{
|
|
_Take[i] = true;
|
|
_WBest += weight(i);
|
|
_VBest += value(i);
|
|
}
|
|
--i;
|
|
}
|
|
}
|
|
|
|
/// Same as AddCheck, except that we examine only a single false take[i], event if it cannot be taken.
|
|
/// This algorithm complexity is O(N), but O(1) when used by CTargetable
|
|
void CKnapsackSolver::optimizeFastAddCheck()
|
|
{
|
|
H_AUTO(CKnapsackSolver_optimizeFastAddCheck);
|
|
int i = (int)size()-1;
|
|
float w = _WMax - _WBest;
|
|
while (i>=0)
|
|
{
|
|
if (!_Take[i])
|
|
{
|
|
if (weight(i) <= w)
|
|
{
|
|
_Take[i] = true;
|
|
_WBest += weight(i);
|
|
_VBest += value(i);
|
|
}
|
|
break;
|
|
}
|
|
--i;
|
|
}
|
|
}
|
|
|
|
/// First try FullAddCheck, then try to replace the already taken elements with not taken ones.
|
|
/// This algorithm complexity is Theta(N^2)
|
|
void CKnapsackSolver::optimizeFullSingleReplace()
|
|
{
|
|
optimizeFullAddCheck();
|
|
H_AUTO(CKnapsackSolver_optimizeFullSingleReplace);
|
|
int i = (int)size()-1;
|
|
while (i>=0)
|
|
{
|
|
// For each not taken ith element
|
|
if (!_Take[i])
|
|
{
|
|
float w = weight(i);
|
|
float v = value(i);
|
|
int worst = i;
|
|
// Find the worst element that ith element can replace
|
|
int j = (int)size()-1;
|
|
while (j>=0)
|
|
{
|
|
if (i!=j && _Take[j] && w<=weight(j) && v>value(j))
|
|
{
|
|
worst = j;
|
|
w = weight(j);
|
|
v = value(j);
|
|
}
|
|
--j;
|
|
}
|
|
// If we find one untake it and take ith.
|
|
if (worst!=i)
|
|
{
|
|
_Take[worst] = false;
|
|
_WBest -= weight(worst);
|
|
_VBest -= value(worst);
|
|
_Take[i] = true;
|
|
_WBest += weight(i);
|
|
_VBest += value(i);
|
|
}
|
|
}
|
|
--i;
|
|
}
|
|
}
|
|
|
|
/// First try FastAddCheck, and if it fails optimizing try to replace a not
|
|
/// taken one with an already taken element (the worst one) until a
|
|
/// replacement occurs.
|
|
/// This algorithm complexity is Theta(N^2) and O(N^2) for CTargetable
|
|
void CKnapsackSolver::optimizeSingleReplace()
|
|
{
|
|
float vBest = _VBest;
|
|
optimizeAddCheck();
|
|
if (_VBest > vBest)
|
|
return;
|
|
H_AUTO(CKnapsackSolver_optimizeSingleReplace);
|
|
int i = (int)size()-1;
|
|
while (i>=0)
|
|
{
|
|
// For each not taken ith element
|
|
if (!_Take[i])
|
|
{
|
|
float w = weight(i);
|
|
float v = value(i);
|
|
int worst = i;
|
|
// Find the worst element that ith element can replace
|
|
int j = (int)size()-1;
|
|
while (j>=0)
|
|
{
|
|
if (i!=j && _Take[j] && w<=weight(j) && v>value(j))
|
|
{
|
|
worst = j;
|
|
w = weight(j);
|
|
v = value(j);
|
|
}
|
|
--j;
|
|
}
|
|
// If we find one untake it and take ith.
|
|
if (worst!=i)
|
|
{
|
|
_Take[worst] = false;
|
|
_WBest -= weight(worst);
|
|
_VBest -= value(worst);
|
|
_Take[i] = true;
|
|
_WBest += weight(i);
|
|
_VBest += value(i);
|
|
break;
|
|
}
|
|
}
|
|
--i;
|
|
}
|
|
}
|
|
|
|
/// First try FastAddCheck, and if it fails optimizing try to replace the
|
|
/// first not taken element with an already taken element (the worst one).
|
|
/// This algorithm complexity is O(N^2) and Theta(N) for CTargetable
|
|
void CKnapsackSolver::optimizeFastSingleReplace()
|
|
{
|
|
float vBest = _VBest;
|
|
optimizeFastAddCheck();
|
|
if (_VBest > vBest)
|
|
return;
|
|
H_AUTO(CKnapsackSolver_optimizeFastSingleReplace);
|
|
int i = (int)size()-1;
|
|
while (i>=0)
|
|
{
|
|
// For each not taken ith element
|
|
if (!_Take[i])
|
|
{
|
|
float w = weight(i);
|
|
float v = value(i);
|
|
int worst = i;
|
|
// Find the worst element that ith element can replace
|
|
int j = (int)size()-1;
|
|
while (j>=0)
|
|
{
|
|
if (i!=j && _Take[j] && w<=weight(j) && v>value(j))
|
|
{
|
|
worst = j;
|
|
w = weight(j);
|
|
v = value(j);
|
|
}
|
|
--j;
|
|
}
|
|
// If we find one untake it and take ith.
|
|
if (worst!=i)
|
|
{
|
|
_Take[worst] = false;
|
|
_WBest -= weight(worst);
|
|
_VBest -= value(worst);
|
|
_Take[i] = true;
|
|
_WBest += weight(i);
|
|
_VBest += value(i);
|
|
}
|
|
break;
|
|
}
|
|
--i;
|
|
}
|
|
}
|
|
|
|
/// First try FastAddCheck, and if it fails optimizing try to replace the
|
|
/// first not taken element with an already taken one (the first worst that
|
|
/// the not taken one).
|
|
/// This algorithm complexity is O(N^2) and O(N) for CTargetable
|
|
void CKnapsackSolver::optimizeVeryFastSingleReplace()
|
|
{
|
|
float vBest = _VBest;
|
|
optimizeFastAddCheck();
|
|
if (_VBest > vBest)
|
|
return;
|
|
H_AUTO(CKnapsackSolver_optimizeVeryFastSingleReplace);
|
|
int i = (int)size()-1;
|
|
while (i>=0)
|
|
{
|
|
// For each not taken ith element
|
|
if (!_Take[i])
|
|
{
|
|
float w = weight(i);
|
|
float v = value(i);
|
|
int worst = i;
|
|
// Find the worst element that ith element can replace
|
|
int j = (int)size()-1;
|
|
while (j>=0)
|
|
{
|
|
if (i!=j && _Take[j] && w<=weight(j) && v>value(j))
|
|
{
|
|
worst = j;
|
|
w = weight(j);
|
|
v = value(j);
|
|
break;
|
|
}
|
|
--j;
|
|
}
|
|
// If we find one untake it and take ith.
|
|
if (worst!=i)
|
|
{
|
|
_Take[worst] = false;
|
|
_WBest -= weight(worst);
|
|
_VBest -= value(worst);
|
|
_Take[i] = true;
|
|
_WBest += weight(i);
|
|
_VBest += value(i);
|
|
}
|
|
break;
|
|
}
|
|
--i;
|
|
}
|
|
}
|
|
|
|
void CKnapsackSolver::optimizeTakeAll()
|
|
{
|
|
H_AUTO(CKnapsackSolver_optimizeTakeAll);
|
|
_WBest = 0;
|
|
_VBest = 0;
|
|
int i = (int)size()-1;
|
|
while (i>=0)
|
|
{
|
|
_Take[i] = true;
|
|
_WBest += weight(i);
|
|
_VBest += value(i);
|
|
--i;
|
|
}
|
|
}
|
|
|
|
CKnapsackSolver::CKnapsackSolver(IKnapsackContext* context, bool* _take)
|
|
: _Context(context)
|
|
, _Take(_take)
|
|
, _AllocatedTake(_take==NULL)
|
|
, _WBest(0.f)
|
|
, _VBest(0.f)
|
|
, _WMax(0.f)
|
|
{
|
|
if (_take==NULL && size()!=0)
|
|
{
|
|
_Take = new bool[size()];
|
|
for (size_t i=0; i<size(); ++i)
|
|
_Take[i] = false;
|
|
}
|
|
else
|
|
{
|
|
for (size_t i=0; i<size(); ++i)
|
|
{
|
|
if (take(i))
|
|
{
|
|
_WBest += weight(i);
|
|
_VBest += value(i);
|
|
}
|
|
}
|
|
_WMax = _WBest;
|
|
}
|
|
}
|
|
|
|
CKnapsackSolver::~CKnapsackSolver()
|
|
{
|
|
if (_AllocatedTake)
|
|
delete [] _Take;
|
|
}
|
|
|
|
float CKnapsackSolver::weight(size_t i)
|
|
{
|
|
if (_Context!=0)
|
|
return _Context->weight(i);
|
|
else
|
|
return 0.f;
|
|
}
|
|
|
|
float CKnapsackSolver::value(size_t i)
|
|
{
|
|
if (_Context!=0)
|
|
return _Context->value(i);
|
|
else
|
|
return 0.f;
|
|
}
|
|
|
|
size_t CKnapsackSolver::size()
|
|
{
|
|
if (_Context!=0)
|
|
return _Context->size();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
bool CKnapsackSolver::take(size_t i)
|
|
{
|
|
if (_Take!=0 && i>=0 && i<size())
|
|
return _Take[i];
|
|
else
|
|
return false;
|
|
}
|
|
|
|
float CKnapsackSolver::totalWeight()
|
|
{
|
|
return _WBest;
|
|
}
|
|
|
|
float CKnapsackSolver::totalValue()
|
|
{
|
|
return _VBest;
|
|
}
|
|
|
|
float CKnapsackSolver::totalFreeWeight()
|
|
{
|
|
return _WMax - _WBest;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CKnapsackContext //
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
CKnapsackContext::CKnapsackContext(size_t size, float* weights, float* values)
|
|
: _Size(size)
|
|
, _Weights(weights)
|
|
, _Values(values)
|
|
{
|
|
}
|
|
|
|
float CKnapsackContext::weight(size_t i)
|
|
{
|
|
if (i>=0 && i<_Size)
|
|
return _Weights[i];
|
|
else
|
|
return 0.f;
|
|
}
|
|
|
|
float CKnapsackContext::value(size_t i)
|
|
{
|
|
if (i>=0 && i<_Size)
|
|
return _Values[i];
|
|
else
|
|
return 0.f;
|
|
}
|
|
|
|
size_t CKnapsackContext::size()
|
|
{
|
|
return _Size;
|
|
}
|