See also principal variation search.
We’ll notate the successors of a node in a game tree or search tree with
node·
0
count(node·
− 1
node·
node·
node·
node·
Note that
node·
node·
node·
node·
node·
node·
node·
node·
The return value of the search of
node
is
node·
node·
0
node·
node·ret
node·best [ s−1 ]
max(
−node·succ [ u ] ·ret
0
−1)
where
s
is the number of successors searched and
s−1
is the index of the last successor that was searched. Note that
s
may be less than
count(node·
The score of
node
is
node·
. We will always use the term “score” to refer to an evaluation (for terminal nodes) or the theoretical result of a minimax search without α/β pruning. We will not use it as a synonym for the return value of a search
with
pruning.
Using this terminology, minimax with α/β pruning is
def search! ( node ) if count ( node .succ ) = 0 node .ret = evaluate ( node .position ) return end mutable retval ← unknown for k from 0 to count ( node .succ ) − 1 node .succ [ k ] .alpha [ 0 ] = − node .beta node .succ [ k ] .beta = − node .alpha [ 0 ] search! ( node .succ [ k ] ) if k = 0 node .best [ 0 ] = − node .succ [ 0 ] .ret else node .best [ k ] = max ( node .best [ k − 1 ] ,− node .succ [ k ] .ret ) end retval ← node .best [ k ] break if retval * node .beta node .alpha [ k + 1 ] = max ( node .alpha [ k ] ,− node .succ [ k ] .ret ) end node .ret = retval end
where
is either
or
(this is the
cutoff criterion). When the flow of execution breaks out of the loop, we say that a
cutoff
has occurred and the
k-th successor caused the cutoff.
is not used in any case.
In more typical presentation, where there is not an explicit object for each node, this is
def search ( position ,alpha ,beta ) iterator = moves ( position ) mutable move ← next! ( iterator ) return evaluate ( position ) if move = none mutable alpha ← alpha mutable retval ← unknown while move ≠ none x = − search ( apply ( position ,move ) ,− beta ,− alpha ) retval ← x if retval = unknown or x > retval break if retval * beta alpha ← max ( alpha ,x ) move ← next! ( iterator ) end return retval end
where
is either
or
again.
Every node is classified as being one of three kinds:
A node is a
≥
>
A
╭──┴──╮ │ w │ ╰──┬──╯ ┌─────────────────┬────────┴────────┬──╶╶╶ w.beta =+7 │ w.beta =+7 │ w.beta =+7 │ w.alpha [ 0 ] =−3 │ w.alpha [ 1 ] =−3 │ w.alpha [ 2 ] =+5 │ │ w.best [ 0 ] =−4 │ w.best [ 1 ] =+5 │ ╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴ │ │x.ret =−5 │ │ x.beta =+3│ │ │ x.alpha [ 0 ] =−7│ │ ╭╴╴┴╶╶╮ ╭──┴──╮ ╭╴╴┴╶╶╮ ╷ ╷ │ x │ ╷ ╷ ╵ ╵ │p-v │ ╵ ╵ ╰╴╴┬╶╶╯ ╰──┬──╯ ╰╴╴┬╶╶╯ │ ┌────────┴────────┐ x.beta =+3 │ x.beta =+3 │ x.beta =+3 x.alpha [ 0 ] =−7 │ x.alpha [ 1 ] =−5 │ x.alpha [ 2 ] =−5 │ x.best [ 0 ] =−5 │ x.best [ 1 ] =−5 ╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴ │y.ret =+5 │z.ret =+9 y.beta =+7│ z.beta =+5│ y.alpha [ 0 ] =−3│ z.alpha [ 0 ] =−3│ ╭──┴──╮ ╭──┴──╮ │ y │ │ z │ ╰──┬──╯ ╰──┬──╯
A node is a
A
≥
>
The score of a
≥
>
╭──┴──╮ │ w │ ╰──┬──╯ ┌─────────────────┬────────┴────────┬──╶╶╶ w.beta =+7 │ w.beta =+7 │ w.beta =+7 │ w.alpha [ 0 ] =−3 │ w.alpha [ 1 ] =−3 │ w.alpha [ 2 ] =−3 │ │ w.best [ 0 ] =−4 │ w.best [ 1 ] =−4 │ ╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴ │ │x.ret =+5 │ │ x.beta =+3│ │ │ x.alpha [ 0 ] =−7│ │ ╭╴╴┴╶╶╮ ╭──┴──╮ ╭╴╴┴╶╶╮ ╷ ╷ │ x │ ╷ ╷ ╵ ╵ │cut │ ╵ ╵ ╰╴╴┬╶╶╯ ╰──┬──╯ ╰╴╴┬╶╶╯ │ ┌────────┴────────┐ x.beta =+3 │ │ x.alpha [ 0 ] =−7 │ │ │ x.best [ 0 ] =+5 │ ╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴┴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴ │y.ret =−5 y.beta =+7│ y.alpha [ 0 ] =−3│ ╭──┴──╮ │ y │ ╰──┬──╯
A node is an
An
The score of an
╭──┴──╮ │ w │ ╰──┬──╯ ┌─────────────────┬────────┴────────┐ w.beta =+7 │ w.beta =+7 │ │ w.alpha [ 0 ] =−3 │ w.alpha [ 1 ] =−3 │ │ │ w.best [ 0 ] =−4 │ w.best [ 1 ] =+8 │ ╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴┴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴ │ │x.ret =−8 │ x.beta =+3│ │ x.alpha [ 0 ] =−7│ ╭╴╴┴╶╶╮ ╭──┴──╮ ╷ ╷ │ x │ ╵ ╵ │all │ ╰╴╴┬╶╶╯ ╰──┬──╯ │ ┌────────┴────────┐ x.beta =+3 │ x.beta =+3 │ x.beta =+2 x.alpha [ 0 ] =−7 │ x.alpha [ 1 ] =−7 │ x.alpha [ 2 ] =−7 │ x.best [ 0 ] =−8 │ x.best [ 1 ] =−8 ╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴ │y.ret =+8 │z.ret =+11 y.beta =+7│ z.beta =+7│ y.alpha [ 0 ] =−3│ z.alpha [ 0 ] =−3│ ╭──┴──╮ ╭──┴──╮ │ y │ │ z │ ╰──┬──╯ ╰──┬──╯
alpha [ 0 ] < ret < beta
When the value returned by a search is strictly between the initial value of alpha and beta, the value is exact (the score is the return value).
ret > beta
When the value returned by a search is strictly above beta, the value is a lower bound of the score (but the score is unknown). In other words, the score is the return value or greater.
The example below shows a search with pruning on the left and a search without it on the right.
x.beta =+3 │ x.ret =+5 │ x.score =+9 x.alpha [ 0 ] =−7 │ │ ╭──┴──╮ ╭──┴──╮ │ x │ │ x │ │cut │ ╰──┬──╯ ╰──┬──╯ │ ┌───────┴───────┐ ┌───────┴───────┐ x.beta =+3 │ │ │ │ x.alpha [ 0 ] =−7 │ x.best [ 0 ] =+5 │ │ x.best [ 0 ] =+5 │ x.best [ 1 ] =+9 ╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴┴╴╴╴╴╴╴ ╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴ y.beta =+7 │ y.ret =−5 │ y.score =−5 │ z.score =−9 y.alpha [ 0 ] =−3 │ │ │ ╭──┴──╮ ╭──┴──╮ ╭──┴──╮ │ y │ │ y │ │ z │ ╰─────╯ ╰─────╯ ╰─────╯
Here the search with pruning return +5, but the score is actually +9.
ret < alpha [ 0 ]
When the value returned by a search is strictly below the initial value of alpha, the value is an upper bound of the score (and the score is unknown). In other words, the score is the return value or less.
The example below shows a search with pruning on the left and a search without it on the right.
x.beta =+7 │ x.ret =−5 │ x.score =−9 x.alpha [ 0 ] =−3 │ │ ╭──┴──╮ ╭──┴──╮ │ x │ │ x │ │all │ ╰──┬──╯ ╰──┬──╯ │ x.beta =+7 │ │ x.alpha [ 0 ] =−3 │ x.best [ 0 ] =−5 │ x.best [ 0 ] =−9 ╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴ ╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴ y.beta =+3 │ y.ret =+5 │ y.score =+9 y.alpha [ 0 ] =−7 │ │ ╭──┴──╮ ╭──┴──╮ │ y │ │ y │ ╰──┬──╯ ╰──┬──╯ ┌───────┴───────┐ ┌───────┴───────┐ y.beta =+3 │ │ │ │ y.alpha [ 0 ] =−7 │ y.best [ 0 ] =+5 │ │ y.best [ 0 ] =+5 │ &.best [ 1 ] =+9 ╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴┴╴╴╴╴╴╴ ╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴╴│╴╴╴╴╴╴╴╴╴╴╴╴╴╴ z.beta =+7 │ z.ret =−5 │ y.score =−5 │ &.score =−9 z.alpha [ 0 ] =−3 │ │ │ ╭──┴──╮ ╭──┴──╮ ╭──┴──╮ │ z │ │ z │ │ & │ ╰─────╯ ╰─────╯ ╰─────╯
Here the search with pruning return −5, but the score is actually −9.
ret = alpha [ 0 ]
ret = beta
When the value returned by a search is equal to alpha or beta, whether the value is exact or a bound of the score depends on the criterion for cutoff.
When the criterion for cutoff is >
or
is exact.
>
=
≤
≤
=
=
=
≥
=
≤
When the criterion for cutoff is ≥
or
is exact or a bound is unknown.
>
=
However, when the cutoff criterion is
≥
def invert ( d ) ※ if upper, exact, and lower are represented with ※ −1, 0, and +1 respectively, this is simply −d return upper if d = lower return lower if d = upper return exact end def join ( d0 ,d1 ) ※ if upper, exact, and lower are represented with ※ −1, 0, and +1 respectively, this is simply max(d0, d1) return lower if d0 = lower or d1 = lower return exact if d0 = exact or d1 = exact return upper end def search ( position ,alpha ,beta ) iterator = moves ( position ) mutable move ← next! ( iterator ) return ⟨ evaluate ( position ) ,exact ⟩ if move = none mutable alpha ← alpha mutable retval ← unknown mutable bound ← unknown while move ≠ none x' ,d' = search ( apply ( position ,move ) ,− beta ,− alpha ) x ,d = − x' ,invert ( d' ) if retval = unknown or x ≥ retval when x = retval then bound ← join ( bound ,d ) when x > retval then bound ← d retval ← x end move ← next! ( iterator ) break if retval ≥ beta alpha ← max ( alpha ,x ) end ※ move ≠ none means not all successors were searched bound ← lower if retval ≥ beta and move ≠ none return ⟨ retval ,bound ⟩ end
With perfect move ordering, search subtrees have this structure:
p-v cut all ┌────┼────┐ │ ┌──┴──┐p-v cut ...all cut ...
where nodes are searched from left to right and an ellipsis indicates zero or more nodes of the preceding type.
With imperfect move ordering, they have this structure:
p-v cut all ┌───────┬────┼────┬────┐ ┌──────┼────┐ ┌──┴──┐p-v /cut ...p-v cut ...p-v /cut ...all cut ...
A tree with perfect move ordering then looks like:
p-v ┌───────────┬────────┼────────┐p-v cut cut cut ┌──────┬────┼────┐ │ │ │p-v cut cut cut all all all ┌───┬──┼──┐ │ │ │ ┌──┼──┐ ┌──┼──┐ ┌──┼──┐p-v c c c all all all c c c c c c c c c ╎ ╎ ╎ ╎ ╎ ╎ ╎ ╎ ╎ ╎ ╎ ╎ ╎ ╎ ╎ ╎
Actual search trees are quite good: 90+% of
If you believe a node is an
If you believe a node is a
If you believe a node is a
{注意}