算法笔记-回溯法

  (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 <?php
 2 $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 = new stdClass();
93     $obj->val = $val;
94     $obj->nodes = array();
95     return $obj;
96 }

 

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

 1 <?php
 2 $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 = new stdClass();
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 <?php
  2 //每个居民的关系
  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 = new stdClass();
106     $obj->val = $val;
107     $obj->nodes = array();
108     return $obj;
109 }

 

 

  (3)地图着色问题

   说明

  

 

 

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

 1 <?php
 2 //每个国家是否相邻
 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 = new stdClass();
95     $obj->val = $val;
96     $obj->nodes = array();
97     return $obj;
98 }

 

  (4)8皇后问题

   说明

     

 

    

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

  1 <?php
  2 
  3 //4个皇后
  4 $queens = 4;
  5 
  6 //所有坐标
  7 $coordinates = array();
  8 for($i=0; $i<$queens; $i++) {
  9     for($j=0; $j<$queens; $j++) {
 10         $coordinates[] = [$i, $j];
 11     }
 12 }
 13 
 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 = new stdClass();
103     $obj->val = $val;
104     $obj->nodes = array();
105     return $obj;
106 }
107 

 

  (5)零件加工问题

   说明

              

 

 

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

  

  1 <?php
  2 $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 = new stdClass();
 98     $obj->val = $val;
 99     $obj->nodes = array();
100     return $obj;
101 }

 

     

posted @ 2019-11-14 23:30  Dahouzi  阅读(385)  评论(0编辑  收藏  举报