目录
1.穷举法(枚举法) 【鸡兔同笼问题】
对于已知范围结果进行一一列举进行验证
《孙子算经》今有鸡兔同笼,上有三十五头,下有九十四足,问鸡兔各几何
/*
* 获取兔子数量
* @param int $header 头
* @param int $foot 脚
* @return int
*/
function chicken_num(int $header, int $foot)
{
for ($i = 0; $i < $header; $i++) {
if (($i * 4 + ($header - $i) * 2) == $foot) {
return $i;
}
}
return 0;
}
2.递推算法思想【兔子产仔问题】
根据已知结果和关系,求解中间结果
斐波那契《算盘书》
如果一对两个月大的兔子以后每一个月都可以生一对小兔子, 而一对新生的兔子出生两个月后才可以生小兔子。 *也就是说, 1 月份出生,3 月份才可产仔。那么假定一年内没有产生兔子死亡事件,那么1年后共有多少对兔子呢?
2.1关系分析
忽略死亡
第一个月出生1对,即f(1) = 1;
第二个月出生1对,即f(2) = 1;
第三个月出生2对,即f(3) = 2;
第四个月出生3对,即f(4) = 3;
第五个月出生5对,即f(5) = 5;
……(斐波那契数列)
第N个月出生f(n) = f(n-1)+ f(n-2)
/*
* @param int $n 月份
* @return int
*/
function fibonacci(int $n)
{
if ($n == 1 || $n == 2) {
return 1;
} else {
return fibonacci(($n - 1)) + fibonacci(($n - 2));
}
}
3.递归算法思想【阶乘】
递归算法就是在程序中不断反复调用自身来达到求解问题的方法。这里的重点是调用自身,这就要求待求解的问题能够分解为相同问题的一个子问题。这样 ,通过多次递归调用,便可以完成求解。
/*
* 阶乘
* @param int $n
*/
function fact(int $n)
{
if ($n <= 1) {
return 1;
}
return $n * fact(($n - 1));
}
4.分治算法思想【寻找假币问题】
分治算法的基本思想是将一个计算复杂的问题分为规模较小,计算简单的小问题求解,然后综合各个小问题,得到最终问题的答案。
一个袋子里有30个硬币,其中一枚是假币,并且假币和真币一模- 样,肉眼很难分辨,目前只知道假币比真币重量轻一点。 请问如何区分出假币?
/**
* @param $list 假币数组
* @return int
*/
function isFalse(array $list)
{
if (count($list) <= 3) {
$keys = array_keys($list);
switch (count($list)) {
case 1:
return $keys[0];
case 2:
return $list[$keys[0]] > $list[$keys[1]] ? $keys[1] : $keys[0];
case 3:
if (($list[$keys[0]] + $list[$keys[1]]) / 2 > $list[$keys[2]]) {
return $keys[2];
} else {
$list[$keys[0]] > $list[$keys[1]] ? $keys[1] : $keys[0];
}
}
}
$qian = intval(count($list) / 2) + 1;
$list_new = array_chunk($list, $qian, true);
if (array_sum($list_new[0]) / count($list_new[0]) > array_sum($list_new[1]) / count($list_new[1])) {
return isFalse($list_new[1]);
} else {
return isFalse($list_new[0]);
}
}
$list = array_fill(0,30,2);
$list[10] = 1;
echo isFalse($list);
5.概率算法思想【蒙特卡罗PI概率算法问题】
概率算法依照概率统计的思路来求解问题,往往不能得到问题的精确解,但却在数值计算领域得到了广泛的应用。 因为很多数学问题,往往没有或者很难计算解析解,这时便需要通过数值计算来求解近似值。
在边长为1的正方形内,以1为半径画一个1/4圆。落入院内的概率为PI/4?
/**
* @param $n 落叶次数
* @return int
*/
function getPi(int $n)
{
$p = 0;
for ($i = 0; $i < $n; $i++) {
$x = lcg_value();
$y = lcg_value();
if (($x * $x + $y * $y) <= 1) {
$p++;
}
}
return ($p / $n * 4);
}
6.回溯法【八皇后问题】
所谓回溯法,名字高大上,思想很朴素。
设想把你放在一个迷宫里,想要走出迷宫,最直接的办法是什么呢? 没错,试。先选一条路走起,走不通就往回退尝试别的路,走不通继续往回退,直到找到出口或所有路都试过走不出去为止。
/**
*
*/
class Queen
{
/**
* 记录当前列是否存在皇后
* @var array
*/
public $column = [];
/**
* 记录/ (45°线)是否存在皇后
* @var array
*/
public $rup = [];
/**
* 记录\ (135°线)是否存在皇后
* @var array
*/
public $lup = [];
/**
* 记录皇后方案
* @var array
*/
public $num = [];
public static $f = 0;//方案序号
/**
* 输出八皇后方案
* @param int|null $n
*/
public static function board(?string $n)
{
self::$f ++;
echo "<h5>方案".self::$f.":{$n}</h5>";
echo "<style>
td{
width: 50px;
height: 50px;
color: beige;
}
.b{
background:black;
}
.g{
background:green;
}
</style>";
echo "<table>";
for ($i = 0; $i < 8; $i++) {
$cop = intval($n[$i]);
echo "<tr>";
for ($j = 1; $j <= 8; $j++) {
$con = $cop == $j ? 'W' : '';
echo ($i + $j) % 2 == 1 ? "<td class='b' align='center'>{$con}</td>" : "<td class='g' align='center'>{$con}</td>";
$con = '';
}
echo "</tr>";
}
echo "</table>";
}
/**
* 执行运算方案
* @param int $num
*/
public function main(int $num = 0)
{
if($num >= 8){
self::board(implode('',$this->num));
}
for ($i = 1; $i <= 8; $i++) {
if ((empty($this->column[$i]) || $this->column[$i] == false)
&& (empty($this->rup[$i + $num]) || $this->rup[$i + $num] == false)
&& (empty($this->lup[$i - $num]) || $this->lup[$i - $num] == false)) {
$this->num[$num] = $i;
$this->column[$i] = $this->rup[$i + $num] = $this->lup[$i - $num] = true;
$this->main(($num + 1));
$this->column[$i] = $this->rup[$i + $num] = $this->lup[$i - $num] = false;
}
}
}
}
(new Queen())->main();
7.动态规划【最长公共子序列】
参考 https://blog.csdn.net/hrn1216/article/details/51534607
动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。
与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。
若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次
S1 = [1,3,4,5,6,7,7,8];
S2 = [3,5,7,4,8,6,7,8,2];
求最长公共子序列?
7.1分析
具体请参考 https://blog.csdn.net/hrn1216/article/details/51534607
用i表示S1的个数对应Xi
用j表示S2的个数对应Yj
用C(i,j)表示结果 则得:
(1)i = j = 0;结果长度为0
(2)Xi = Yj =C(i-1,j-1)
(3)Xi != Yj max(C(i-1,j),C(i,j-1))
7.2向上查找
下标j 下标i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
S2 | 3 | 5 | 7 | 4 | 8 | 6 | 7 | 8 | 2 | ||
0 | S1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 3 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
3 | 4 | 0 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 2 | 2 |
4 | 5 | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
5 | 6 | 0 | 1 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 |
6 | 7 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 4 | 4 | 4 |
7 | 7 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 4 | 4 | 4 |
8 | 8 | 0 | 1 | 2 | 3 | 3 | 4 | 4 | 4 | 5 | 5 |
7.3向左查找
下标j 下标i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
S2 | 3 | 5 | 7 | 4 | 8 | 6 | 7 | 8 | 2 | ||
0 | S1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 3 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
3 | 4 | 0 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 2 | 2 |
4 | 5 | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
5 | 6 | 0 | 1 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 |
6 | 7 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 4 | 4 | 4 |
7 | 7 | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 4 | 4 | 4 |
8 | 8 | 0 | 1 | 2 | 3 | 3 | 4 | 4 | 4 | 5 | 5 |
class Dp
{
private $c = array();
private $s1;
private $s2;
public function outPrint(array $s1, array $s2)
{
$this->c = [];
$this->s1 = $s1;
$this->s2 = $s2;
for ($i = 0; $i < count($s1); $i++) {
for ($j = 0; $j < count($s2); $j++) {
$this->c[$i][$j] = $this->dp_f($i, $j);
}
}
$lastI = $i - 1;
$lastJ = $j - 1;
$last = $this->c[$lastI][$lastJ];
//向上找
$upList = $this->getUp($last, $lastI, $lastJ);
//优先向左
$leftList = $this->getLeft($last, $lastI, $lastJ);
return array(implode(',', $upList), implode(',', $leftList));
}
/**
* 动态规划
* @param int $i
* @param int $j
* @return int|mixed
*/
private function dp_f(int $i, int $j)
{
if (isset($this->c[$i]) && isset($this->c[$i][$j])) {
return $this->c[$i][$j];
}
if ($i == -1 || $j == -1) {
return 0;
}
if ($this->s1[$i] == $this->s2[$j]) {
return ($this->dp_f(($i - 1), ($j - 1)) + 1);
}
if ($this->s1[$i] != $this->s2[$j]) {
return max($this->dp_f(($i - 1), $j), $this->dp_f($i, ($j - 1)));
}
return 0;
}
/**
* 向上查找
* @param int $lastUp
* @param int $upI
* @param int $upJ
* @return array
*/
private function getUp(int $lastUp, int $upI, int $upJ)
{
$upList = [];
while ($lastUp > 0 && $upI > -1 && $upJ > -1) {
if ($this->s1[$upI] == $this->s2[$upJ]) {
array_unshift($upList, $this->s1[$upI]);
$upI--;
$upJ--;
$lastUp--;
} else {
//优先向上
if (!empty($this->c[($upI - 1)]) && $lastUp == $this->c[($upI - 1)][$upJ]) {
$upI--;
} else {
$upJ--;
}
}
}
return $upList;
}
/**
* 向上查找
* @param int $lastUp
* @param int $upI
* @param int $upJ
* @return array
*/
private function getLeft(int $lastLeft, int $liftI, int $liftJ)
{
$leftList = [];
while ($lastLeft > 0 && $liftI > -1 && $liftJ > -1) {
if ($this->s1[$liftI] == $this->s2[$liftJ]) {
array_unshift($leftList, $this->s1[$liftI]);
$liftI--;
$liftJ--;
$lastLeft--;
} else {
//优先向左
if (!empty($this->c[$liftI][($liftJ - 1)]) && $lastLeft == $this->c[$liftI][($liftJ - 1)]) {
$liftJ--;
} else {
$liftI--;
}
}
}
return $leftList;
}
}
$s1 = [1, 3, 4, 5, 6, 7, 7, 8];
$s2 = [3, 5, 7, 4, 8, 6, 7, 8, 2];
$c = (new Dp())->outPrint($s1, $s2);
var_dump($c);
8.贪心算法【纸币找零问题】
所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。 也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
假设1元、2元、5元、10元、20元、50元、100元的纸币,张数不限制,现在要用来支付K元,至少要多少张纸币?
/**
* @param $money 需要找零金额
* @return array
*/
function greedy_money(int $money){
$rData = [];
$moneyLevel = [1,2,5,10,20,50,100];
for ($i = (count($moneyLevel)-1); $i >= 0 ; $i--){
$num = intval($money / $moneyLevel[$i]);
if($num > 0){
$rData[$moneyLevel[$i]] = $num;
}
$money = $money % $moneyLevel[$i];
}
return $rData;
}