吴军的硅谷来信-队列-堆-递归

318 阅读8分钟

队列queue

例子:银行排队,先来先服务

  • 特点: 先进先出 first in first out/first in first serve
  • 优点:有序,公平
  • 缺点:
  1. 如果最前面卡顿,后面那个就要等很久。
  2. 队列里的箱子不像人一样移动,每次挪动都要整个数组一起往前移动。效率低。
  3. 留下的空位子会占用空间。要不断回收

例子:坐电梯,先进后出。大公司裁员,先裁掉刚入职的,所有为什么那么多老跳槽的人会总是被裁员的怪象。

特点: 先进后出 first in last out/first in last serve

  • 优点:高效,不占用多余的空间,不用回收
  • 缺点:不公平,

运行策略

  1. 先来先服务
  2. 执行之间最少先服务
  3. 占用资源最少先服务
  4. 释放资源最多的先服务
  5. 优先级最高的

程序都是相互依赖的,A等待B,B等待C,C等待D, 当出现 A->B,B->C,C->A,则会出现死机.

window系统-资源调度和使用策略处理的不好,mac系统更优。

递归 recursive

递推 iterative

人的正向思维被称为“递推”,

  1. 学习数字是从1,2,3到100,1000
  2. 解方程是从 一个未知数方程,到二元方程X,Y,三元方程X,Y,Z,到线性方程组.
  3. 学习语文,先从字,词,普通句子,到既要xxxx又要xxxx
  4. 整数阶乘,5的阶乘:12345,从小到大相乘。

八皇后问题 (回溯算法)

在国际棋盘8*8上,放置8个皇后,请问有多少种摆法?(任何两个皇后都不能处于同一条横行、纵行或斜线上)

这也是一个递归的过程,首先假定前面7个皇后已经确定,只需要在第8行找到满足条件的就ok了,但是前7行7个皇后是如何确定呢,一样假定前6行6个皇后已经确定,这样一直递归的第一行 image.png

为了简化问题,把8*8改成 4*4理解

image.png

整体思路:每次都是从上往下,从左往右,依次与下一行比较,满足跳下一行,不满足回退到上一行,每当放满8个皇后就记录下来。 流程步骤:

  1. 从第一行a行开始,最左边开始如a1,依次放一个皇后,a1发现ok无冲突请求,行标往下移动,到b行
  2. b行也是从左往右开始:放b1, b1与a1冲突,继续放b2,依然冲突,继续放b3发现ok,行标往下移动,到c行
  3. c行也是从左往右开始:放c1, c2,c3,c4都失败,这时候回溯到上一行b行,
  4. b行继续往右移动,到b4,发现ok,行标往下移又到c行。
  5. c行从c1开始往右移动,c1冲突,c2发现ok。行标往下移到d行
  6. d行发现都不ok,c3,c4也不满足,再往上,b行b4已经都没有了,继续往上,到a行
  7. a行继续往右边a2,然后是b4,c1,d3,最终找到第一种方案。

image.png 这是1-3的步骤,c行发现走不通,回溯到b4

image.png 这是4的步骤,c行发现走不通,回溯到b行,b行已经没有满足的,继续往上找。

image.png 回到a行,从a1到a2 继续比较。

最终可解的是下面两种:

image.png image.png

注意判断 当前皇后 是否满足通过 左斜,右边斜和竖向是否冲突?

代码实现

class Queen {
    constructor(num) {
      this.num = num;
      this.arr = [];
      this.result = [];
      this.initList();
      this.buildList(this.arr, 0);//1. 从0行开始
    }
  
    initList() {
      let num = this.num;
      for (let i = 0; i < num; i++) {
        this.arr[i] = [];
        for (let j = 0; j < num; j++) {
          this.arr[i][j] = 0;
        }
      }
      console.log(this.arr);
    }
  
    buildList(list, row) {
      // 递归中止条件,找到一个解缓存起来
      if (row === list.length) {
        this.result.push(JSON.parse(JSON.stringify(list)));
        return;
      }
      for (let col = 0, len = list.length; col < len; col++) {//列循环8次
        if (this.isSafe(list, row, col)) { // 第一次 [0,0]
          list[row][col] = 1;
          this.buildList(list, row + 1);//每次都递归往下查找,行号+1 往下执行 //把[0,0] = 1 传入下一行。
          // 走到这里,说明该次递归已经结束,不管找没找到,都需要重置,相当于 [0,0]下面的所有穷举都遍历完一遍
          list[row][col] = 0;
        }
      }
    }
    //验证当前皇后 是否满足左斜 右边斜 竖向都不冲突
    isSafe(list, row, col) {
      const len = list.length;
      // 同一列
      for (let i = 0; i < len; i++) {
        if (list[i][col] === 1) return false;
      }
      // 斜右上方
      for (let i = row - 1, j = col + 1; i >= 0 && j < len; i--, j++) {
        if (list[i][j] === 1) return false;
      }
      // 斜左上方
      for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
        if (list[i][j] === 1) return false;
      }
      return true;
    }
  }
  const queen = new Queen(8);
  console.log(queen.result);

斐波那契数列

抢20游戏

  • 用户A第一次可以出1,或2。
  • 用户B在A的基础上继续出1,或2,
  • 用户A在B的基础上继续出1,或2.
  • 直到最后一个人出的数字加起来刚好是20就算赢 用什么策略能保存最后一定能赢?

这是一个递归的倒推过程: 我们把问题简化成抢10的游戏

  1. 要抢10,其实只要抢到7
  2. 类似的抢7 就要抢到4
  3. 类似的抢4 就要抢到1

如果是 抢20

  1. 要抢20,其实只要抢到17
  2. 类似的抢17 就要抢到14
  3. 类似的抢14 就要抢到11
  4. 类似的抢11 就要抢到8
  5. 类似的抢8 就要抢到5
  6. 类似的抢5 就要抢到2

所以无论对方出的是1或者2,我们都可以把合计控制在3,所以是操作3的递减范围

延展

请问上面的游戏中有多少种可能?

用倒退的思想: f(20) = f(19) + f(18) , f(19) = f(18) + f(17) 递推公式 直到f(1) = 1 ,然后再反推回去,f(2) = f(1) + f(0) 上面的结果 得到下面的数列 1,1,2,3,5,8,13,21,24...... (这里第一个f(0) = 1)

也就是斐波那契数列

斐波那契数列

兔子数列:

假设一对初生兔子要一个月才到成熟期,而一对成熟兔子每月会生一对兔子,那么,由一对初生兔子开始,12 个月后会有多少对兔子呢?(注意这里的兔子都不会挂掉)

image.png

  1. 两个相邻的数比值是 接近于 黄金分割 f(n)/f(n-1) = 1.618...无理数
  2. 向日葵葵籽的生长和树枝每一段的生长都也是符合 斐波那契数列的规律 (这样可以以最高又均匀的分布)
  3. 可以用长方形面积来表达斐波那契数列 image.png

递归的思考

递归只关心下一层,不关心后面的所有细节

  1. 社会与公司的组织结构,从上往下,老板->总经理->经理->主管->员工。而老板是不用直接管员工的。老板->总经理的过程叫授权
  2. 系统论:如开始一个软件,应该从顶层开始设计,而不是从细节开始。这样才能构建稳定健壮的系统。而想从小模块慢慢扩展为整个大系统的思想是陷入一遍混乱和不规范。
  3. 倒推式工作:做计划比如年底要跳槽,就要从现在开始复习。如:12月面试,11月就要完成所有简历更新,10月就要复习基础等等。如:向死而生,就会倒逼自己懂得舍弃,什么才是最重要要做,做减法的道理。

面试题:

给你一个英语句:“London brigade is falling down”,把它完全倒装过来,改成 “down falling is brigade London”. 如何不使用而外的空间实现倒装?

方法1

把单词分割成

  • London
  • brigade
  • is
  • falling
  • down 存入数组,然后从后往前取出。或者存入堆栈,然后直接取出。

缺点:还是使用了数组的30个字符的存储空间

方法2

整个单词挪动

  1. 先找到所有单词最长的长度,创建空间是London 6个格空间的临时空间temp
  2. 先把Lodon单词放入temp变量,然后把down放入London的位置,然后把London放入down的位置
  3. 由于down原来的位置只有4位不够,继续往前借用falling的空间。
  4. 所以又要创建falling 7个格空间的临时空间temp2.
  5. 这时候就可以把London 放到最后面。
  6. 一直下去

缺点:如果临时空间不够都要一直创建

image.png

方法3

把整个句子所有字母首尾对调,然后再根据空格,对调每个打乱的小单词。 “London brigade is falling down” => "nwod gnillaf si egdirb nodnoL" 再把每个单词转换

  • nwod => down
  • gnillaf  => falling
  • si  => is
  • egdirb  => brigade
  • nodnoL  => London image.png

当然每次对调都用到临时变量 temp,会占用一个字符的空间。

音频与语音压缩原理

数据压缩的原理:是把原来看得懂得东西转化成看不懂的过渡产物(但是没有任何损失的中间信息),然后再转化成原来的东西。

音频压缩:

  • 第一步:先把语音或者图像从直观的信息,变成人根本看不懂的频率信号。
  • 第二步:根据压缩的比例,把高频的信号过滤掉,只保留低频的即可。当播放语音或者显示图片时,再从频率信息恢复为语音波形,或者图像即可。

图片压缩:原图是一个个像素存储,而压缩后的图片是一大片一大片存储。把雷同的处理一个颜色处理。图片的蓝天白云,大海。jepg压缩可以是原图压缩的10倍。