回溯 皇后 算法笔记_算法笔记-回溯法

(1)0-1背包问题

说明:有4个商品,重量分别为2, 5, 4, 2;价值分别为6, 3, 5, 4,背包只能装10重量的物品,怎么装可以获取最大价值的物品?

思路:构造一个二叉树,每个商品都有两种状态,要或者不要。如果要就在这个节点的左枝挂子节点,如果不要就在右节点挂子节点。如果全部商品都分配完状态之后就回溯,回溯到一个还有其他选择的节点,接着往这个选择发展节点,然后再回溯,然后再往下。。。。  直到无路可走,就结束了。

假如限制重量是10,总共有四个商品,重量分别是2, 5, 4, 2 价格分别是6, 3, 5, 4。第一轮的路程如5-11图,第1个商品要,第2个商品要,第3个商品发现装不下了,所以到第3个节点只能走右节点,并且第3个节点的左节点成为死节点,没有发展下去的可能了。第4个商品要,此时已经给所有的商品赋予状态了(要或者不要),记录下此时所有商品的价值和,记为最优价格。接着就开始回溯,如5-13,从节点5先是回溯到节点4(此时购物车有1,2,3选择不要,4肯定是要的,所以没必要再发展4节点的右节点),再到节点3(节点三的左节点是死节点),再到节点2,节点2的右节点是可选的,然后接着按照刚开始的逻辑接着往下走就可以了,等继续走完这一轮,计算最优值,更新下最优值,然后再回溯。。。

剪枝:如果按照上面的逻辑,其实几乎相当于遍历了所有的可能性。如果有4个商品,就会有2的4次方种可能,有些不可能是最优结果的分支直接就剪掉就可以了,比如,如果按照上面的逻辑是会有:1不要2不要3不要4不要这个分支。所以如果发现背包可以把剩下的商品都装入的情况,就直接给剩余的商品赋值为要就可以了。当1不要2不要的时候,3和4可以都装入背包,直接都要就可以了。没必要再走3不要的分支(直接设置成死节点)。或者也可以判断就算把剩余的都加进包里,总价值也小于当前最优值,当前这条路也没必要走了。

        

代码:这个模板几乎可以解决所有这个类型的问题,buildTree用于构建树,这个微调就可以;thenData用于返回当前选择(这是个抽象概念,该题中表示这个商品要或者不要,部落护卫队问题表示这个人要不要,地图标色问题表示当前国家涂什么颜色)所对应的下一层数据;isContinue用于判断当前路径是否要继续(比如当前路径发展为第一个商品选择装入,第二个商品选择装入,第三个商品选择装入,isContinue将返回false,因为第三个已经装不下了。即使可以装下,有时候我们也可以从其他角度判断返回false,越早的堵死,越少走弯路);travel根据tree返回所有路径,这个方法几乎不用动,根据路径的长度就可以判断是否为完整路径(此题应该与货物数量相等);

1 <?php2 $w = [2, 5, 4, 2];3 $v = [6, 3, 5, 4];4 $limit = 10;5

6 $tree = buildTree($w);7 print_r(resolve($tree));8

9 function resolve($obj) {10 global $w, $v;11 $paths = travel($obj);12 $ret = array();13 foreach ($paths as $path) {14 $valCount = 0;15 foreach ($path as $k => $isChoose) {16 $valCount += $v[$k] * $isChoose;17 }18 $ret[$valCount][] = $path;19 }20 return $ret;21

22 }23 function travel($obj) {24 $paths = array();25 if (!$obj->nodes) {26 return [$paths];27 }28

29 foreach ($obj->nodes as $isChoose => $node) {30 $tmpRes = travel($node);31 foreach ($tmpRes as & $path) {32 $path[$obj->val] = $isChoose;33 }34 $paths = array_merge($paths, $tmpRes);35 }36 return $paths;37 }38

39 function buildTree($data, $path =[]) {40 if (empty($data)) {41 return nodeFactory(null);42 }43 $key = key($data);44 $obj = nodeFactory($key);45 $nextData = thenData($data);46

47 foreach ($nextData as $isChoose => $ndata) {48 $path[$key] = $isChoose;49 if (isContinue($path)) {50 unset($ndata[$key]);51 $obj->nodes[$isChoose] = buildTree($ndata, $path);52 }53 }54 return $obj;55 }56

57 function thenData($data) {58 return[59 1 => $data,

60 0 => $data,

61 ];62 }63

64 function isContinue($path) {65 global $limit, $w, $v;66 static $maxVal = 0;67 $weightCount = 0;68 $valueCount = 0;69 foreach ($path as $k => $value) {70 $weightCount += $w[$k] * $value;71 $valueCount += $v[$k] * $value;72 }73 //重量限制

74 if ($weightCount > $limit) {75 return false;76 }77

78 //就算剩下的货物全要, 价值也比不上最大价值79 //就没有继续往下的必要了

80 $curreny = max(array_keys($path));81 $remainSumVal = array_sum(array_slice($v, $curreny + 1));82 if ($valueCount + $remainSumVal < $maxVal) {83 return false;84 }85

86 //更新下最优值

87 $maxVal = max($maxVal, $valueCount);88 return true;89 }90

91 function nodeFactory($val) {92 $obj = newstdClass();93 $obj->val = $val;94 $obj->nodes = array();95 return $obj;96 }

ps:这版写的很乱,不要看了 只为记录用

1 <?php2 $w = [2, 5, 4, 2];3 $v = [6, 3, 5, 4];4 $current = getNode(0);5 $count = count($w);6 list($limit, $best, $cw, $cp, $bestMap, $map) = [10, 0, 0, 0, array(), array()];7 $noBack = true;8

9 while (1) {10 $node = getNode($current->level + 1, $current);11 if ($current->level < $count && $noBack) {12 if ($best >= array_sum(array_slice($v, $current->level)) + $cp) {13 $current->l = false; //剪枝

14 $current->r = false;15 $noBack = false;16 } elseif (is_object($current->l)|| $current->l === false) {17 $node->dir = 0; //这种情况是回溯回来的,直接发展右节点就可以了

18 $current->r = & $node;19 } elseif ($cw + $w[$current->level] <= $limit) {20 $cw += $w[$current->level]; $cp += $v[$current->level];21 $node->dir = 1; //1代表左枝,0代表右枝

22 $current->l = & $node; //这种情况代表背包可以装下,所以挂在左节点

23 $map[$current->level] = 1;24 } else{25 $node->dir = 0;26 $current->r = & $node;27 $current->l = false; //这种情况代表装不下,左节点是死节点,发展右节点

28 }29 $current = & $node;30 } else { //走完一轮,开始回溯

31 if ($cp > $best) { //记录最优值

32 $best = $cp; $bestMap = $map;33 }34 while (1) { //开始回溯

35 $deal = isset($current->dir) ? $current->dir : 0;36 $current = & $current->p;37 if ($current === null) {38 break 2; //到头了,结束

39 }40 if (isset($map[$current->level])) {41 unset($map[$current->level]);42 $cw -= $w[$current->level] * $deal; //怎么加的,怎么减回去

43 $cp -= $v[$current->level] * $deal;44 }45 if ($current->l === null || $current->r === null) { //存在活结点

46 $noBack = true;47 break;48 }49 }50 }51 unset($node);52 }53

54 function getNode($level, & $p = null) {55 $node = newstdClass();56 $node->level = $level; $node->p = & $p;57 $node->l = null; $node->r = null;58 return $node;59 }60

61 print_r(['map' => $bestMap, 'val' => $best]);

View Code

(2)最大团问题

说明

思路:和上一个问题几乎是一样的, 根据场景不同, 我们需要修改isContinue,thenData这两个函数其他的几乎没变

1 <?php2 //每个居民的关系

3 $map =[4 [false, true, true, true, true],

5 [true, false, true, false, false],

6 [true, true, false, true, true],

7 [true, false, true, false, true],

8 [true, false, true, true, false],

9 ];10

11 //五个居民

12 $residents = [1, 2, 3, 4, 5];13 $tree = buildTree($residents);14

15 print_r(resolve($tree));16

17

18 function resolve($obj) {19 global $residents;20 $count = count($residents);21

22 //过滤掉不完成的路径

23 $paths = array_filter(travel($obj), function ($path) use ($count) {24 return count($path) === $count;25 });26

27 //从里面选择一个最大就可以了

28 $ret = array();29 foreach ($paths as $path) {30 $count = array_sum($path);31 $ret[$count][] = $path;32 }33 $max = max(array_keys($ret));34 return $ret[$max];35

36 }37 function travel($obj) {38 $paths = array();39 if (!$obj->nodes) {40 return [$paths];41 }42

43 foreach ($obj->nodes as $isChoose => $node) {44 $tmpRes = travel($node);45 foreach ($tmpRes as & $path) {46 $path[$obj->val] = $isChoose;47 }48 $paths = array_merge($paths, $tmpRes);49 }50 return $paths;51 }52

53 function buildTree($residents, $path =[]) {54 if (empty($residents)) {55 return nodeFactory(null);56 }57 $cresident = array_shift($residents);58 $obj = nodeFactory($cresident);59 $nextData = thenData($cresident, $residents);60

61 foreach ($nextData as $isChoose => $ndata) {62 $path[$cresident] = $isChoose;63 if (isContinue($path)) {64 $obj->nodes[$isChoose] = buildTree($ndata, $path);65 }66 }67 return $obj;68 }69

70

71 function isContinue($path) {72 global $residents;73 static $maxVal = 0;74

75 //无法超过最大值

76 $remaind = array_diff($residents, array_keys($path));77 if (array_sum($path) + count($remaind) < $maxVal) {78 return false;79 }80

81 //更新下最优值

82 $maxVal = max($maxVal, array_sum($path));83 return true;84 }85

86

87 function thenData($cresident, $data) {88 $choise = array_filter($data, function ($p) use ($cresident){89 return isLink($p, $cresident);90 });91

92 //这个把1放在上面可以少走很多弯路1

93 return[94 1 => $choise,

95 0 => $data,

96 ];97 }98

99 function isLink($p1, $p2) {100 global $map;101 return $map[$p1 - 1][$p2 - 1];102 }103

104 function nodeFactory($val) {105 $obj = newstdClass();106 $obj->val = $val;107 $obj->nodes = array();108 return $obj;109 }

(3)地图着色问题

说明

思路:之前的是二叉树,而这次有多个分支。但总体思路和之前一样

1 <?php2 //每个国家是否相邻

3 $map =[4 [false, true, true, true, false, false, false],

5 [true, false, true, false, true, false, false],

6 [true, true, false, true, true, false, false],

7 [true, false, true, false, true, false, true],

8 [false, true, true, true, false, true, true],

9 [false, false, false, false, true, false, true],

10 [false, false, false, true, true, true, false],

11 ];12

13

14 //3个颜色

15 $allColors = [1, 2, 3];16

17 //7个国家

18 $countries = [1, 2, 3, 4, 5, 6, 7];19

20 $tree = buildTree($countries, $allColors);21 print_r(resolve($tree));22

23 function resolve($obj) {24 global $countries;25 $count = count($countries);26

27 //过滤掉不完成的路径

28 $paths = array_filter(travel($obj), function ($path) use ($count) {29 return count($path) === $count;30 });31

32 return $paths;33

34 }35 function travel($obj) {36 $paths = array();37 if (!$obj->nodes) {38 return [$paths];39 }40

41 foreach ($obj->nodes as $isChoose => $node) {42 $tmpRes = travel($node);43 foreach ($tmpRes as & $path) {44 $path[$obj->val] = $isChoose;45 }46 $paths = array_merge($paths, $tmpRes);47 }48 return $paths;49 }50

51 function buildTree($countries, $colors, $path =[]) {52 global $allColors;53 if (empty($countries)) {54 return nodeFactory(null);55 }56 $country = array_shift($countries);57 $obj = nodeFactory($country);58

59 $nextData = thenData($colors, $countries);60 foreach ($nextData as $color => $ndata) {61 $path[$country] = $color;62 if (isContinue($path)) {63 $obj->nodes[$color] = buildTree($ndata, array_diff($allColors, [$color]), $path);64 }65 }66 return $obj;67 }68

69 //这里只要判断所有相邻国家是否有相同着色就可以了

70 function isContinue($path) {71 foreach ($path as $country1 => $color1) {72 foreach ($path as $country2 => $color2) {73 if (isLink($country1, $country2) && $color1 == $color2) {74 return false;75 }76 }77 }78 return true;79 }80

81

82 function thenData($colors, $data) {83 $ret = array();84 $datas = array_fill(0, count($colors), $data);85 return array_combine($colors, $datas);86 }87

88 function isLink($p1, $p2) {89 global $map;90 return $map[$p1 - 1][$p2 - 1];91 }92

93 function nodeFactory($val) {94 $obj = newstdClass();95 $obj->val = $val;96 $obj->nodes = array();97 return $obj;98 }

(4)8皇后问题

说明

思路:分支在于判断每个格子是否放置皇后

1 <?php2

3 //4个皇后

4 $queens = 4;5

6 //所有坐标

7 $coordinates = array();8 for($i=0; $i

14 $tree = buildTree($coordinates);15 print_r(resolve($tree));16

17 function resolve($obj) {18 global $coordinates;19 $count = count($coordinates);20

21 //过滤掉不完成的路径

22 $paths = array_filter(travel($obj), function ($path) use ($count) {23 return count($path) === $count;24 });25

26 return $paths;27 }28 function travel($obj) {29 $paths = array();30 if (!$obj->nodes) {31 return [$paths];32 }33

34 foreach ($obj->nodes as $isChoose => $node) {35 $tmpRes = travel($node);36 foreach ($tmpRes as & $path) {37 $valToKey = join('-', $obj->val);38 $path[$valToKey] = $isChoose;39 }40 $paths = array_merge($paths, $tmpRes);41 }42 return $paths;43 }44

45 function buildTree($coordinates, $map =[]) {46 if (empty($coordinates)) {47 return nodeFactory(null);48 }49 $coord = array_shift($coordinates);50 $obj = nodeFactory($coord);51

52 $nextData = thenData($coordinates);53 foreach ($nextData as $isLay => $ndata) {54 $tmpMap = $map;55 $isLay && $tmpMap[] = $coord;56 if (isContinue($tmpMap, $ndata)) {57 $obj->nodes[$isLay] = buildTree($ndata, $tmpMap);58 }59 }60

61 return $obj;62 }63

64

65 function isContinue($map, $ndata) {66 global $queens;67

68 //判断剩余的行数能否放下剩余的皇后

69 $remaindQ = $queens - count($map);70 $lines = ceil(count($ndata) / $queens);71 if ($lines < $remaindQ) {72 return false;73 }74

75 //判断是否有两个皇后处于一条直线

76 foreach ($map as $k1 => $m1) {77 foreach ($map as $k2 => $m2) {78 if ($k1 != $k2) {79 if ($m1[0] == $m2[0] || $m1[1] == $m2[1] || abs($m1[0] - $m2[0]) == abs($m1[1] - $m2[1])) {80 return false;81 }82 }83 }84 }85 return true;86 }87

88

89 function thenData($coordinates) {90 return[91 1 => $coordinates,

92 0 => $coordinates,

93 ];94 }95

96 function isLink($p1, $p2) {97 global $map;98 return $map[$p1 - 1][$p2 - 1];99 }100

101 function nodeFactory($val) {102 $obj = newstdClass();103 $obj->val = $val;104 $obj->nodes = array();105 return $obj;106 }107

(5)零件加工问题

说明

代码:节点为第几个加工顺序(第1次,第2次。。),分支为加工哪个零件

1 <?php2 $m1 = [5, 1, 8, 5, 3, 4];3 $m2 = [7, 2, 2, 4, 7, 4];4

5 $tree = buildTree(array_keys($m1));6 print_r(resolve($tree));7

8 function resolve($obj) {9 global $m1;10 $count = count($m1);11

12 //过滤掉不完成的路径

13 $ret = array();14 $paths = travel($obj);15 foreach ($paths as $path) {16 if (count($path) !== $count) {17 continue;18 }19 $time = celTime(array_reverse($path));20 $ret[$time][] = $path;21 }22 return $ret;23 }24 function travel($obj) {25 $paths = array();26 if (!$obj->nodes) {27 return [$paths];28 }29

30 foreach ($obj->nodes as $isChoose => $node) {31 $tmpRes = travel($node);32 foreach ($tmpRes as & $path) {33 $path[$obj->val] = $isChoose;34 }35 $paths = array_merge($paths, $tmpRes);36 }37 return $paths;38 }39

40 function buildTree($keys, $path =[]) {41 global $m1;42 if (empty($keys)) {43 return nodeFactory(null);44 }45

46 $order = count($m1) - count($keys) + 1;47 $obj = nodeFactory($order);48

49 $nextData = thenData($keys);50 foreach ($nextData as $m => $ndata) {51 $path[] = $m;52 if (isContinue($path)) {53 $obj->nodes[$m] = buildTree($ndata, $path);54 }55 array_pop($path);56 }57 return $obj;58 }59

60

61 function isContinue($path) {62 global $m1, $m2;63 static $bestVal =PHP_INT_MAX;64

65 //计算一下时间

66 $time = celTime($path);67 //更新最优值

68 count($path) == count($m1) && $bestVal = min($time, $bestVal);69 return $time <= $bestVal;70 }71

72 function celTime($path) {73 global $m1, $m2;74 $mTime1 = $mTime2 =[];75 foreach ($path as $v) {76 $mTime1[$v] = (int)end($mTime1) + $m1[$v];77 $begin = empty($mTime2) ? $mTime1[$v] : max(end($mTime2), $mTime1[$v]);78 $mTime2[$v] = $begin + $m2[$v];79 }80 return end($mTime2);81 }82

83 function thenData($keys) {84 $ret =[];85 foreach ($keys as $v) {86 $ret[$v] = array_diff($keys, [$v]);87 }88 return $ret;89 }90

91 function isLink($p1, $p2) {92 global $map;93 return $map[$p1 - 1][$p2 - 1];94 }95

96 function nodeFactory($val) {97 $obj = newstdClass();98 $obj->val = $val;99 $obj->nodes = array();100 return $obj;101 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值