贪心算法:保证每一步都是最优,最终结果肯定是最优的(简化了很多问题,不需要你有大局观)
场景:求集合的极限值(最多,最短,最便宜等等等...)
(1)会议安排
说明:在有限的时间内召开更多的会议,任何两个会议不能有时间冲突。下图是会议详情:
思路:下一个会议要取结束时间最早的(更优的会议持续时间最短,相当于动态的改变开始时间)
代码:
1 <?php 2 $meetings = [ 3 '1' => [3, 6], '2' => [1, 4], '3' => [5, 7], '4' => [2, 5], '5' => [5, 9], 4 '6' => [3, 8], '7' => [8, 11], '8' => [6, 10], '9' => [8, 12], '10' => [12, 14], 5 ]; 6 7 //根据结束时间排序 8 uasort($meetings, function($p1, $p2){ 9 if ($p1[1] == $p2[1]) return 0; 10 return $p1[1] > $p2[1] ? 1 : -1; 11 }); 12 13 //取不冲突 14 $last = null; 15 foreach ($meetings as $k => $meet) { 16 if (($last === null) || ($last !== null && $meet[0] >= $last)) 17 list($res[], $last) = [$k, $meet[1]]; 18 }
(2)最短路径
说明:景区有五个点,求第1个点到其他所有点的最短距离。地图如下:
思路:依次找最近的点。大致过程如下:
当前在1:得知1-2路程为2;1-3路程为5 。下面去2和3(因为1的两个出度分别是2和3)
然后去2:得知2-4路程为6;2-3路程为1; 结合上面新得出的路程:1-3路程为4(1-2-3,要比上面的5小,这里要刷新一下),1-4路程为6(这个分支下面要去3和4)
然后去3:得知 3-4路程为7;3-5路程为1; 结合上面新得出的路程:1-4路程还是4,1-5路程为5(1-2-3-5)(这个分支下面要去4和5)
依次类推...
代码:
1 <?php 2 $map = [ //这里用矩阵表示地图 3 [null, 2, 5, null, null], 4 [null, null, 2, 6, null], 5 [null, null, null, 7, 1], 6 [null, null, 2, null, 4], 7 [null, null, null, null, null], 8 ]; 9 $position = $start = 0; 10 $been = []; //去过的点 11 12 function minPath($position) 13 { 14 global $map, $start, $been; 15 static $res = []; //结果 16 $been[] = $position; //记录走过的点 17 $next = []; 18 foreach ($map[$position] as $k => $v) { 19 if ($v !== null) { 20 if (! isset($res[$start . $k]) || ($res[$start . $k] > $res[$start . $position] + $v)) { 21 $res[$start . $k] = $res[$start . $position] + $v; //记录最短的距离 22 if (in_array($k, $been)) $next[] = $k; //这里放在后面解释 23 } 24 ! in_array($k, $been) && $next[] = $k; //记录接下来要走的点 25 } 26 } 27 28 foreach ($next as $v) { //接着找下个点 29 minPath($v); 30 } 31 return $res; 32 } 33 $res = minPath($position);
补充:代码第22行注释:
刚开始也没发现这个问题,执行上面的案例也没啥问题。但执行下面这个案例就有问题了(假如没有第22行)。走到第2个点的时候,还判定1-2的距离为8,所以计算1-4为9。等后来走到第3个点,把1-2的距离精确到3,但已经晚了。第2个点已经走过一次,不会再走了。也就是说不会再更新1-4的距离。所以当检测出距离更精确的时候(1-3-2比1-2要近),把那个点(第2个点)从记录走过点的数组中移除,重新在走一次。
或者可以手动遍历一下结果,将需要精确的精确一下,比如当发现1-3-2要比1-2要近,则更新1-2-4(第2个点的所有出度)。但这里要记录路径(只记录1-4=9不行,要记录1-2-4=9),性能应该要比上一个好很多。
(4)最小生成树
说明:把所有点都连接起来(从一点可以到任意一点),并且所有路径的和最小。地图如下:
思路:找到与初始点(管它叫a)最近的点(管它叫b),连接ab;
找到与a或b最近的点(管它叫c),连接bc(假如c和b最近)
找到与a或b或c最近的点(管它叫d),连接ad(假如d和a最近)
一直找完所有的点.....
代码:
1 <?php 2 $map = [ //要保证其是个连通图 3 [null, 23, null, null, null, 28, 36], 4 [23, null, 20, null, null, null, 1], 5 [null, 20, null, 15, null, null, 4], 6 [null, null, 15, null, 3, null, 9], 7 [null, null, null, 3, null, 17, 16], 8 [28, null, null, null, 17, null, 25], 9 [36, 1, 4, 9, 16, 25, null], 10 ]; 11 12 function minTree($position) 13 { 14 global $map; 15 $distance = 0; $paths = []; $res[] = $position; 16 while(true) { 17 $minLink = ['from' => null, 'name' => null, 'value' => INF]; 18 foreach ($res as $p) { 19 foreach ($map[$p] as $k => $v) { 20 if ($v && $v < $minLink['value'] && ! in_array($k, $res)) { 21 $minLink = ['from' => $p, 'name' => $k, 'value' => $v]; //所有已知点遍历一遍,找到离已知点最近的点 22 } 23 } 24 } 25 if ($minLink['name'] !== null) { //收录新的已知点,并记录路径和长度 26 $res[] = $minLink['name']; 27 $paths[] = $minLink['from'] . $minLink['name']; 28 $distance += $minLink['value']; 29 } else { 30 break; //全部走完可以结束了 31 } 32 } 33 34 return [$paths, $distance]; 35 } 36 37 $position = 0; //初始点随便选,路径方法可能答案不唯一,但总长度是唯一的 38 list($paths, $distance) = minTree($position); //57
(5)哈夫曼编码
说明:根据字母出现的频率,为其分配相应长度的编码(频率越高,编码越短),以下几个字母出现的频率如图:
思路:把这些字母放到二叉树上,然后用01表示,频率较高的字母靠上。过程如下,不停的取出数组中最小的两个元素加和(和是父节点,这两个元素分别为左右叶)之后再放回数组中,直到剩下最后一个元素:
代码:
1 <?php 2 $arr = [5, 32, 18, 7, 25, 13]; 3 4 function createTree($arr) 5 { 6 sort($arr); 7 $arr[] = INF; //如果加和之后值在数组中最大,就不会插入了 8 while (count($arr) > 2) { 9 $left = gettype($arr[0]) == 'object' ? $arr[0] : newNode($arr[0]); 10 $right = gettype($arr[1]) == 'object' ? $arr[1] : newNode($arr[1]); //左右叶 11 $set = newNode($left->name + $right->name, $left, $right); //父节点 12 unset($arr[0], $arr[1]); //去除前两个元素 13 foreach ($arr as $k => $v) 14 { 15 if ($set->name <= $v) { 16 array_splice($arr, $k - 2, 0, [$set]); //插入加和的元素(每次会重置键值) 17 break; 18 } 19 } 20 } 21 return $arr[0]; 22 } 23 24 function newNode($name, & $left = null, & $right = null) 25 { 26 $n = new stdClass(); 27 list($n->name, $n->left, $n->right, $n->parent) = [$name, & $left, & $right, null]; 28 $left && $left->parent = & $n; 29 $right && $right->parent = & $n; 30 return $n; 31 } 32 createTree($arr);