【基础算法】基础算法【转载】

原文:

基础算法


注意:所有的程序默认是用java写的,个人全部转为PHP

1. 最大公约数


问题:求两个自然数的最大公约数。
 
分析:这个是基础的数学问题,最大公约数指两个数字公共的约数中最大的,例如数字6的约数有1、2、3、6,数字9的约数有1、3、9,则数字6和数字9的公共约数有1和3,其中3是最大的公约数。
 
第一种思路:从1开始循环,每次把符合要求(即同时是两个数字的约数)的值都存储起来,那么最后一个存储起来的就是最大的约数。
E:博客-基础算法-代码1_a_max_divisor.php:
<?php

/**
 * 求两个自然数的最大公约数(暴力枚举法/穷举法)
 * @param int $a
 * @param int $b
 * @return bool|int
 */
function get_max_divisor($a, $b)
{
	if (!is_numeric($a) || !is_numeric($b)) {
		return false;
	}

	$min = $a > $b ? $b : $a;
	$max = $a > $b ? $a : $b;

	if ($max % $min === 0) {
		return $min;
	}

	$result = 1;
	for ($i = 1; $i <= $min / sqrt(2); $i++) {
		if ($min % $i === 0 && $max % $i === 0) {
			$result = $i;
		}
	}
	return $result;
}

$a = 36;
$b = 16;
$c = get_max_divisor($a, $b);
var_dump($c);


使用该思路,每次都存储得到的公共约数,那么最后一个存储的就是两个数字的最大公约数。

第二种思路:从两个数字中最小的数字开始循环,每次减1,那么第一次得到的公共约数就是所求的最大公约数。
则实现的代码如下:  
E:博客-基础算法-代码1_b_max_divisor.php:

<?php

/**
 * 求两个自然数的最大公约数(暴力枚举法/穷举法——优化)
 * 时间复杂度是O(min(a, b)))
 * @param int $a
 * @param int $b
 * @return bool|int
 */
function get_max_divisor($a, $b)
{
    if (!is_numeric($a) || !is_numeric($b)) {
        return false;
    }

    $min = $a > $b ? $b : $a;
    $result = 1;
    for ($i = $min; $i >= 1; $i--) {
        if ($a % $i === 0 && $b % $i === 0) {
            $result = $i;
            break;
        }
    }
    return $result;
}

$a = 55;
$b = 10;
$c = get_max_divisor($a, $b);
var_dump($c);


两者相比之下,第二种的时间复杂度要低,因为一旦找到了之后就会跳出循环了,而第一种则需要完整的遍历一遍循环。
但是,两种方法都是采用了暴力穷举法,时间复杂度都是比较高的。可以采用其他的方法。时间复杂度是O(min(a, b)))

第三种思路:辗转相除法/欧几里德算法
辗转相除法, 又名欧几里德算法(Euclidean algorithm)乃求两个正整数之最大公因子的算法。它是已知最古老的算法, 其可追溯至公元前300年前。
原理:两个整数的最大公约数是能够同时整除它们的最大的正整数。
辗转相除法基于如下原理:两个整数的最大公约数等于其中较小的数和两数的相除余数的最大公约数。
设两数为a、b(a>b),求a和b最大公约数(a,b)的步骤如下:用a除以b,得a÷b=q......r1(0≤r1)。若r1=0,则(a,b)=b;若r1≠0,则再用b除以r1,得b÷r1=q......r2 (0≤r2).若r2=0,则(a,b)=r1,若r2≠0,则继续用r1除以r2,……如此下去,直到能整除为止。其最后一个为被除数的余数的除数即为(a, b)。
例如:a=25,b=15,a/b=1......10,b/10=1......5,10/5=2.......0,最后一个为被除数余数的除数就是5,5就是所求最大公约数。

E:博客-基础算法-代码1_c_max_divisor.php:
<?php

/**
 * 求两个自然数的最大公约数(辗转相除法/欧几里德算法)
 * 时间复杂度不太好计算,可以近似为O(log(min(a, b))),但是取模运算性能较差。
 * @param int $a
 * @param int $b
 * @return bool|int
 */

$a = 550;
$b = 700;
$c = gcd($a, $b);
var_dump($c);

/**
 * 递归计算最大的公约数
 * @param int $a
 * @param int $b
 * @return mixed
 */
function gcd($a, $b)
{
    if (!is_numeric($a) || !is_numeric($b)) {
        return false;
    }

    $min = $a > $b ? $b : $a;
    $max = $a > $b ? $a : $b;

    if ($max % $min === 0) {
        return $min;
    } else {
        return gcd($min, $max % $min);
    }
}

辗转相除法相比起穷举法效率快很多,不用遍历每个数字。
时间复杂度不太好计算,可以近似为O(log(min(a, b))),但是取模运算性能较差。

第四种思路:另一种方法是采用中国国人发明的更相减损法,更相减损术是出自《九章算术》的一种求最大公约数的算法,它原本是为约分而设计的,但它适用于任何需要求最大公约数的场合。(如果需要对分数进行约分,那么)可以折半的话,就折半(也就是用2来约分)。如果不可以折半的话,那么就比较分母和分子的大小,用大数减去小数,互相减来减去,一直到减数与差相等为止,用这个相等的数字来约分。
步骤:
第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。
第二步:以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。
则第一步中约掉的若干个2与第二步中等数的乘积就是所求的最大公约数。
其中所说的“等数”,就是最大公约数。求“等数”的办法是“更相减损”法。
比如:用更相减损术求260和104的最大公约数。由于260和104均为偶数,首先用2约简得到130和52,再用2约简得到65和26。
此时65是奇数而26不是奇数,故把65和26辗转相减:
65-26=39
39-26=13
26-13=13
所以,260与104的最大公约数等于13乘以第一步中约掉的两个2,即13*2*2=52。
E:博客-基础算法-代码1_d_max_divisor.php:
<?php

/**
 * 求两个自然数的最大公约数(更相减损法)
 * 避免了取模运算,但是算法性能不稳定,最坏时间复杂度为O(max(a, b)))
 * @param int $a
 * @param int $b
 * @return bool|int
 */

$a = 44;
$b = 88;
$c = gcd($a, $b);
var_dump($c);

/**
 * 递归计算最大的公约数
 * @param int $a
 * @param int $b
 * @return mixed
 */
function gcd($a, $b)
{
    if (!is_numeric($a) || !is_numeric($b)) {
        return false;
    }

    if ($a === $b) {
        return $a;
    }

    $min = $a > $b ? $b : $a;
    $max = $a > $b ? $a : $b;

    return gcd($max - $min, $min);

}

更相减损术和辗转相除法的主要区别在于前者所使用的运算是“减”,后者是“除”。从算法思想上看,两者并没有本质上的区别,但是在计算过程中,如果遇到一个数很大,另一个数比较小的情况,可能要进行很多次减法才能达到一次除法的效果,从而使得算法的时间复杂度退化为O(N),其中N是原先的两个数中较大的一个。相比之下,辗转相除法的时间复杂度稳定于O(logN)。但是缺点也很明显,如果两个数相差很大,比如10000和59,做$a % $b取模运算的性能会比较低。

第五种思路:结合辗转相除法和更相减损法,在更相减损法的基础上使用移位运算
<?php

/**
 * 求两个自然数的最大公约数(结合辗转相除法和更相减损法,在更相减损法的基础上使用移位运算)
 * 不但避免了取模运算,而且算法性能稳定,时间复杂度是a和b中较大数的二进制位数,即O(log(max(a, b)))
 * @param int $a
 * @param int $b
 * @return bool|int
 */

$a = 50;
$b = 88;
$c = gcd($a, $b);
var_dump($c);

/**
 * 递归计算最大的公约数
 * @param int $a
 * @param int $b
 * @return mixed
 */
function gcd($a, $b)
{
    if (!is_numeric($a) || !is_numeric($b)) {
        return false;
    }

    if ($a == $b) {
        return $a;
    }

    //保证参数$a永远大于等于参数$b,为减少代码量
    if ($a < $b) {
        return gcd($b, $a);
    } else {
        //和1做按位与运算,判断奇偶
        if (is_even($a) && is_even($b)) {
            return (gcd($a >> 1, $b >> 1) << 1);
        } elseif (is_even($a) && !is_even($b)) {
            return gcd($a >> 1, $b);
        } elseif (!is_even($a) && is_even($b)) {
            return gcd($a, $b >> 1);
        } else {
            return gcd($b, $a - $b);
        }
    }
}

/**
 * 判断奇偶数,减少迭代次数,奇数为false,偶数为true
 * @param int $a
 * @return bool
 */
function is_even($a) {
    return !($a & 1);
}
众所周知,移位运算的性能非常快。对于给定的正整数a和b,不难得到如下的结论。其中gcb(a,b)的意思是a,b的最大公约数函数:
当a和b均为偶数,gcb(a,b) = 2*gcb(a/2, b/2) = 2*gcb(a>>1, b>>1)
当a为偶数,b为奇数,gcb(a,b) = gcb(a/2, b) = gcb(a>>1, b)
当a为奇数,b为偶数,gcb(a,b) = gcb(a, b/2) = gcb(a, b>>1)
当a和b均为奇数,利用更相减损术运算一次,gcb(a,b) = gcb(b, a-b), 此时a-b必然是偶数,又可以继续进行移位运算。
 
比如计算10和25的最大公约数的步骤如下:
 
    整数10通过移位,可以转换成求5和25的最大公约数
    利用更相减损法,计算出25-5=20,转换成求5和20的最大公约数
    整数20通过移位,可以转换成求5和10的最大公约数
    整数10通过移位,可以转换成求5和5的最大公约数
    利用更相减损法,因为两数相等,所以最大公约数是5
 
在两数比较小的时候,暂时看不出计算次数的优势,当两数越大,计算次数的节省就越明显。

这种方法:不但避免了取模运算,而且算法性能稳定,时间复杂度为O(log(max(a, b)))


2. 百元百鸡问题


问题:每只母鸡3元,每只公鸡4元,每只小鸡0.5元,如果花100元钱买100只鸡,请问有哪些可能?说明:每种鸡的数量都可以为零。

分析:其实这个问题是数学上的组合问题,只需要把所有的情况列举出来,然后来判断是否符合要求即可。这样的重复列举的问题,在程序上可以使用循环进行解决。

第一种思路:当母鸡的数量为0时,公鸡的数量从0-100,当公鸡的数量每变化一次,小鸡的数量就从0变化到100,使用如下数值组合来描述这个思路:
               母鸡数量                                   公鸡数量                                 小鸡数量
                      0                                               0                                   从0变化到100
                      0                                               1                                   从0变化到100
                      0                                               2                                   从0变化到100
                   ……
                      1                                               0                                   从0变化到100
                      1                                               1                                   从0变化到100
                   ……
                     100                                          100                                         100
上面列举出了所有公鸡、母鸡和小鸡的数量都是0-100时的所有组合,总计是101的三次方种,这样的穷举结构直接存在嵌套,在程序实际实现时,通过循环之间的嵌套就可以实现,则实现的代码如下: 
E:博客-基础算法-代码2_a_hundred_hen.php:
  1. <?php
  2. for ($i = 0; $i <= 100; $i++) {
  3. for ($j = 0; $j <= 100; $j++) {
  4. for ($k = 0; $k <= 100; $k++) {
  5. if (3 * $i + 4 * $j + 0.5 * $k == 100 && $i + $j + $k == 100) {
  6. echo $i . '<br>' . $j . '<br>' . $k;
  7. echo '<hr>';
  8. }
  9. }
  10. }
  11. }
输出:
 按照for语句的执行流程,循环变量变化1,则内部的循环执行一次,而在循环嵌套时,循环体又是一个新的循环,则该循环执行完成的一组循环。这里通过循环的嵌套实现了所有数值的穷举。在循环的内部,只需要按照题目要求判断一下数量和金额是否符合要求即可。
但是这样的代码效率比较差,可以通过简单的优化来提高程序的执行效率。

第二种思路:由于母鸡每只的金额是3元,所以100元最多购买的母鸡数量是100/3=33只,同理100元最多购买的公鸡数量是25只,而按照100元100只的要求,小鸡的数量应该为100减去公鸡和母鸡的数量,这样代码就可以简化为如下的结构:
E:博客-基础算法-代码2_b_hundred_hen.php:
  1. <?php
  2. for ($i = 0; $i <= 33; $i++) {
  3. for ($j = 0; $j <= 25; $j++) {
  4. $k = 100 - $i - $j;
  5. if (3 * $i + 4 * $j + 0.5 * $k == 100) {
  6. echo $i . '<br>' . $j . '<br>' . $k;
  7. echo '<hr>';
  8. }
  9. }
  10. }
输出也是一样,但是时间复杂度比第一种好得多。

3. 喝汽水问题

问题:共有1000瓶汽水,每喝完后一瓶得到的一个空瓶子,每3个空瓶子又能换1瓶汽水,喝掉以后又得到一个空瓶子,问总共能喝多少瓶汽水,最后还剩余多少个空瓶子?

分析:这个问题其实是个比较典型的递推问题,每3个空瓶都可以再换1瓶新的汽水,这样一直递推下去,直到最后不能换到汽水为止。

第一种思路:每次喝一瓶,每有三个空瓶子就去换一瓶新的汽水,直到最后没有汽水可以喝为止。在程序中记忆汽水的数量和空瓶子的数量即可。
则实现的代码如下:
E:博客-基础算法-代码3_a_drink_bottle.php:
  1. <?php
  2. $num = 1000; //汽水数量
  3. $drink_num = 0; //喝掉的汽水数量
  4. $empty_num = 0; //空瓶子的数量
  5. while ($num > 0) { //有汽水可以喝
  6. $num--; //喝掉一瓶
  7. $empty_num++; //空瓶子数量加1
  8. $drink_num++; //喝掉的汽水数量加1
  9. if ($empty_num === 3) { //有3个空瓶子,就去换一瓶汽水
  10. $num++; //汽水数量加1
  11. $empty_num = 0; //空瓶子数量清0
  12. }
  13. }
  14. echo "total drink: " . $drink_num;
  15. echo "left empty bottle: " . $empty_num;
输出:
total drink: 1499left empty bottle: 2

在该代码中,每次循环喝掉一瓶汽水,则汽水数量减少1,空瓶子数增加1,喝掉的总汽水瓶数增加1,每次判断空瓶子的数量是否达到3,如果达到3则换1瓶汽水,同时空瓶子的数量变为零。这种思路比较直观,但是循环的次数比较多,所以就有了下面的逻辑实现。

第二种思路:一次把所有的汽水喝完,获得所有的空瓶子,再全部换成汽水,然后再一次全部喝完,再获得所有的空瓶子,依次类推,直到没有汽水可喝为止。

则实现的代码如下:
E:博客-基础算法-代码3_b_drink_bottle.php:
  1. <?php
  2. $num = 1000; //汽水数量
  3. $drink_num = 0; //喝掉的汽水数量
  4. $empty_num = 0; //空瓶子的数量
  5. while ($num > 0) { //有汽水可以喝
  6. $drink_num += $num; //喝掉所有的汽水
  7. $empty_num += $num; //空瓶子数量等于上次剩余的加上这次喝掉的数量
  8. $num = intval(floor($empty_num / 3)); //兑换的汽水数量
  9. $empty_num -= $num * 3; //本次兑换剩余的空瓶子数量
  10. }
  11. echo "total drink: " . $drink_num;
  12. echo "left empty bottle: " . $empty_num;
 在该代码中,每次喝掉所有的汽水,也就是num瓶,则喝掉的总瓶数每次增加num,因为每次都可能剩余空瓶子(不足3个的),则总的空瓶子数量是上次空瓶子数量加上本次喝掉的num瓶。接着是兑换汽水,则每次可以兑换的汽水数量是空瓶子的数量的1/3,注意这里是整数除法,而本次兑换剩余的空瓶子数量是原来的空瓶子数量减去兑换得到汽水数量的3倍,这就是一次循环所完成的功能,依次类推即可解决该问题。

第三种思路:3个空瓶子=1瓶饮料=>3个空瓶子=1瓶饮料(不带瓶)+1个空瓶子=>2个空瓶子=1瓶饮料(不带瓶),那么这样1000个空瓶可以兑换500瓶饮料(不带瓶),但是最后1瓶饮料你是喝不到的,因为你最后剩下2个空瓶,喝的饮料数=1000+500-1。
E:博客-基础算法-代码3_c_drink_bottle.php:
  1. <?php
  2. $num = 1000; //汽水数量
  3. $drink_num = 0; //喝掉的汽水数量
  4. $empty_num = 2; //空瓶子的数量
  5. $drink_num = $num + $num / 2 - 1;
  6. echo "total drink: " . $drink_num;
  7. echo "left empty bottle: " . $empty_num;

4. 水仙花数

问题:水仙花数指三位数中,每个数字的立方和和自身相等的数字,例如370,3 × 3 × 3 + 7 × 7 × 7 + 0 × 0 × 0 =370,请输出所有的水仙花数。

分析:该问题中体现了一个基本的算法——数字拆分,需要把一个数中每位的数字拆分出来,然后才可以实现该逻辑。

实现思路:循环所有的三位数,拆分出三位数字的个位、十位和百位数字,判断3个数字的立方和是否等于自身。
则实现的代码如下所示:   
E:博客-基础算法-代码4_narcissus_number.php:
  1. <?php
  2. for ($i = 100; $i < 1000; $i++) {
  3. $a = $i % 10; //个位数字
  4. $b = intval(floor($i / 10)) % 10; //十位数字
  5. $c = intval(floor($i / 100)); //百位数字
  6. if ($a * $a * $a + $b * $b * $b + $c * $c * $c === $i) {
  7. echo '<br>' . $i . '<br>';
  8. }
  9. }
在该代码中,拆分个位数字使用i和10取余即可,拆分十位数字时首先用i除以十,去掉个位数字,并使原来的十位数字变成个位,然后和10取余即可,因为i是一个三位数,所以i除以100即可得百位数字,因为这里都是整数除法,不存在小数的问题。然后只需要判断立方和是否等于自身即可。
注意:因为i是循环变量,这里不能改变i的值,不然可能造成死循环。

5. 求素数问题 

 问题:求出1000以内的所有素数,素数即质数,只能被1和本身整除的数,最小的质数是2。

实现思路:通过嵌套循环找出2到1000内所有的符合条件的数。
则实现的代码如下所示:    
E:博客-基础算法-代码5_a_prime_number.php:
  1. <?php
  2. for ($i = 2; $i <= 997; $i++) {
  3. $is_prime = true;
  4. for ($j = 2; $j < $i; $j++) {
  5. if ($i !== $j && $i % $j === 0) {
  6. $is_prime = false;
  7. break;
  8. }
  9. }
  10. if ($is_prime === true) {
  11. echo '<br>' . $i . '<br>';
  12. }
  13. }
这样算是想到的最直接的方式,当然也是最笨的方式,因为每次判断的时候都会从2检查到i,聪明一点的方式是把i变成i/2,因为2/i以上的数肯定不会被i整除。
 则实现的代码如下所示:
E:博客-基础算法-代码5_b_prime_number.php:
  1. <?php
  2. for ($i = 2; $i <= 1000; $i++) {
  3. $is_prime = true;
  4. for ($j = 2; $j <= $i / 2; $j++) {
  5. if ($i !== $j && $i % $j === 0) {
  6. $is_prime = false;
  7. break;
  8. }
  9. }
  10. if ($is_prime === true) {
  11. echo '<br>' . $i . '<br>';
  12. }
  13. }
那么再聪明的方式就是把i/2变成根号i,因为根号i以上的数肯定不会被i整除。
则实现的代码如下所示:
E:博客-基础算法-代码5_c_prime_number.php:
  1. <?php
  2. for ($i = 2; $i <= 15; $i++) {
  3. $is_prime = true;
  4. for ($j = 2; $j <= sqrt($i); $j++) {
  5. if ($i !== $j && $i % $j === 0) {
  6. $is_prime = false;
  7. break;
  8. }
  9. }
  10. if ($is_prime === true) {
  11. echo '<br>' . $i . '<br>';
  12. }
  13. }

6. 输出数列

问题:输出1 1 2 3 5 8 13……这样的数列,输出该数列的前20个数字。

分析:该题是一个基本的数字逻辑,在实际解决该问题时,首先要发现该数字的规律,然后按照该规律来设计数组即可。

实现思路:数字的规律是除了数列里的前两个数字以外,其它的数字都满足该数字等于前两个数字的和,由于题目要求输出前20个数字,所以需要一个长度为20的数组,第一个和第二个数字直接赋值,后续的数字通过前两个数字元素得到。
则实现的代码如下:
E:博客-基础算法-代码6_get_series.php:
  1. <?php
  2. $res[0] = 1;
  3. $res[1] = 1;
  4. for ($i = 2; $i < 20; $i++) {
  5. $res[$i] = $res[$i - 1] + $res[$i - 2];
  6. }
  7. var_dump($res);
在该代码中,初始化一个长度为20的数组,首先将数组中的前两个元素赋值成1,然后循环对后续的元素的赋值,如果当前元素的下标是i,则它前一个元素的下标是i-1,再前面一个元素的下标是i-2,只需要将这2个元素的值相加,然后赋值给当前元素即可。

7. 歌手打分

问题:在歌唱比赛中,共有10位评委进行打分,在计算歌手得分时,去掉一个最高分,去掉一个最低分,然后剩余的8位评委的分数进行平均,就是该选手的最终得分。如果已知每个评委的评分,求该选手的得分。

分析:该题实际上涉及到求数组的最大值、最小值,以及求数组中所有元素的和,也是数组方便统计的用途体现。

实现思路:求出数组元素的最大值、最小值以及和,然后使用和减去最大值和最小值,然后除以8获得得分。
则实现的代码如下:
E:博客-基础算法-代码7_get_avt_score.php:
  1. <?php
  2. $score = array(90,78,90,96,67,86,78,92,79,85);
  3. $avg = (array_sum($score) - max($score) - min($score)) / 8;
  4. echo $avg;

8. 冒泡排序

原理:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。至此第一趟结束,将最大的数放到了最后。在第二趟:仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到倒数第二个数(倒数第一的位置上已经是最大的),第二趟结束,在倒数第二的位置上得到一个新的最大数(其实在整个数列中是第二大的数)。如此下去,重复以上过程,直至最终完成排序。

由于在排序过程中总是小数往前放,大数往后放,相当于气泡往上升,所以称作冒泡排序。

用二重循环实现,外循环变量设为i,内循环变量设为j。外循环重复9次,内循环依次重复9,8,...,1次。每次进行比较的两个元素都是与内循环j有关的,它们可以分别用a[j]和a[j+1]标识,i的值依次为1,2,...,9,对于每一个i,j的值依次为1,2,...10-i。
 
实现的代码如下:
E:博客-基础算法-代码8_maopao.php:
  1. <?php
  2. /**
  3. * 冒泡排序
  4. * @param $array
  5. * @return mixed
  6. */
  7. function bubble_sort($array)
  8. {
  9. for ($i = 1; $i < count($array); $i++) {
  10. for ($j = 0; $j < count($array) - $i; $j++) {
  11. if ($array[$j] > $array[$j + 1]) {
  12. $temp = $array[$j + 1];
  13. $array[$j + 1] = $array[$j];
  14. $array[$j] = $temp;
  15. }
  16. }
  17. }
  18. return $array;
  19. }
  20. $array = array(5, 8, 9, 3, 4, 7, 2, 4, 1);
  21. var_dump(bubble_sort($array));

9. 选择排序

原理:n个数的直接选择排序可经过n-1趟直接选择排序得到有序结果:
        ①初始状态:无序区为R[1..n],有序区为空。
        ②第1趟排序,在无序区R[1..n]中选出关键字最小的记录R[k],将它与无序区的第1个记录R[1]交换,使R[1..1]和R[2..n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
        ……
        ③第i趟排序,第i趟排序开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中选出关键字最小的记录R[k],将它与无序区的第1个记录R交换,使R[1..i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
        这样,n个数的直接选择排序可经过n-1趟直接选择排序得到有序结果。
 
实现的代码如下:
E:博客-基础算法-代码9_a_xuanze.php:
  1. <?php
  2. /**
  3. * 选择排序
  4. * @param $array
  5. * @return mixed
  6. */
  7. function select_sort($array)
  8. {
  9. for ($i = 0; $i < count($array); $i++) {
  10. $min = $i;
  11. for ($j = $i + 1; $j < count($array); $j++) {
  12. if ($array[$j] < $array[$min]) {
  13. $min = $j;
  14. }
  15. }
  16. if ($min !== $i) {
  17. $temp = $array[$min];
  18. $array[$min] = $array[$i];
  19. $array[$i] = $temp;
  20. }
  21. }
  22. return $array;
  23. }
  24. $array = array(5, 8, 9, 3, 4, 7, 2, 4, 1);
  25. var_dump(select_sort($array));

10. 寻找孤立数字


问题:给定一个数组,数组内的数两两相同,只有一个数是孤立的,用最快的方式找出这个数。
 
分析:循环数组,判断第i个元素的值和其它位置的值是否相等,如果不存在相等的,那么这个数就是孤立数据。
 
实现的代码如下:  
E:博客-基础算法-代码10_a_find_single_num.php:
  1. <?php
  2. /**
  3. * 从数组(数组内的数两两相同,只有一个数是孤立的)中寻找孤立的数字
  4. * Created by PhpStorm.
  5. * User: Administrator
  6. * Date: 2016/11/16
  7. * Time: 10:43
  8. */
  9. $array = [1, 2, 3, 2, 3, 1, 4, 7, 6, 4, 7];
  10. $res = find_single_num($array);
  11. echo $res;
  12. /**
  13. * 从数组(数组内的数两两相同,只有一个数是孤立的)中寻找孤立的数字
  14. * @param array $array 数组
  15. * @return bool|int|mixed
  16. */
  17. function find_single_num($array)
  18. {
  19. if (!is_array($array)) {
  20. return false;
  21. }
  22. $single = 0;
  23. foreach ($array as $key1=>$value1) {
  24. $is_single = true;
  25. foreach ($array as $key2=>$value2) {
  26. if ($key2 !== $key1 && $value2 === $value1) {
  27. $is_single = false;
  28. break;
  29. }
  30. }
  31. if ($is_single === true) {
  32. $single = $value1;
  33. break;
  34. }
  35. }
  36. return $single;
  37. }
 显然这样的嵌套循环判断复杂度是很高的,达到n的平方,所以使用异或(^),则实现的代码如下:
  1. <?php
  2. /**
  3. * 从数组(数组内的数两两相同,只有一个数是孤立的)中寻找孤立的数字
  4. * Created by PhpStorm.
  5. * User: Administrator
  6. * Date: 2016/11/16
  7. * Time: 10:43
  8. */
  9. $array = [1, 2, 3, 2, 3, 1, 4, 7, 6, 4, 7];
  10. $res = find_single_num($array);
  11. echo $res;
  12. /**
  13. * 从数组(数组内的数两两相同,只有一个数是孤立的)中寻找孤立的数字
  14. * @param array $array 数组
  15. * @return bool|int|mixed
  16. */
  17. function find_single_num($array)
  18. {
  19. if (!is_array($array)) {
  20. return false;
  21. }
  22. $single = 0;
  23. foreach ($array as $value) {
  24. $single = $single ^ $value;
  25. }
  26. return $single;
  27. }






原文地址:https://www.cnblogs.com/linewman/p/9918121.html