
Originally Posted by JMBattista
I may have misused the term, but my reference to exhaustive is simply that *all* possibilities must be accounted for to reach an optimal solution. Optimizations that allow you to trim out some set of possibilities as a "well this *definitely* is where we want to go!" can reduce the search space dramatically but you are still 'exhausting' those possibilities.
ie - mastery as our lowest stat (unholy live) you can safely assume that we will *never* choose to reforge into mastery, so a rather large number of possibilities fall by the way side right there. Similarly on gear with hit/haste it is clear we will never reforge the amount of haste (unholy live) since we can't improve on the haste and it is uncappable, ect, ect. Or that on a haste/crit piece we would consider the crit for reforge not the haste, and then the crit only to hit/expertise (depending on personal stat weights).
These are optimizations that allow you to prune the tree, but they don't change that fact that it is NP.
|
Ignoring some obviously wrong reforging options just decreases the branching factor -- those tree nodes aren't generated at all. This is not what I'm talking about when I say we can prune the tree with bounding.
I've synthesized my previous posts here into a proposed algorithm for finding the optimal reforging of a item set:
1. Create the root node, consisting of the item set with no reforges made.
2. Calculate the upper and lower bounds of the root node - the upper bound being the value of the current node plus the most we could possibly gain by reforging every item perfectly.The lower bound is just the value of the current item set as-is, because we don't want to consider any reforged item sets where the best we can do is worse than its parent!
3. Branch by creating a child node for every possible reforging option for a single item slot. (remember to include leaving the item un-reforged as a child node!).
4. Calculate the upper and lower bounds for each child node. The upper bound is, again, the value of the current node plus the most we could possibly gain by reforging every item perfectly (except this time we don't include the item we set in this child node). Lower bound is always the value of the current node as-is.
5. Eliminate all nodes on this level where the upper bound is less than the global maximum lower bound -- that is, we prune any subtrees where the best we could do is lower than the worst some other subtree we have seen could do.
6. For any remaining nodes at this level, branch on the next item slot.
7. Again calculate upper and lower bounds for each new child node, not including the item slot of the current node or any parent nodes in the calculation because they are now fixed (because you picked a specific reforging option for them already).
8. If you have no items left to reforge, you're done. Else: GOTO 5
So, the hard question is, how do you find the upper bound for a subtree?
Here's how:
Before you start searching the tree you need to find the range of possible item values for every item in your input:
lower bound: [ItemScore(item) when ALL hit and exp is over the cap]
upper bound:[ItemScore(item) when NO hit and exp is over the cap]
Now when we are tasked with finding the upper bound of a subtree, we can say that the upper bound on that subtree is:
(The value of the current node taken as a whole, not including any items that have already been reforged at this level or in a parent node) + (the sum of the highest upper bound for each of the lower-level item slots)
Note that because we are looking at individual item slots, we can't take into account hit/exp caps for this bounds calculation, but that actually makes the pruning LESS restrictive, so we're still guaranteed to find the optimal solution.
Expect an implementation and benchmarks vs naive enumeration of all sets from me within the next few days.