算法

作者: tangyefei | 来源:发表于2019-01-21 18:03 被阅读12次

译者序

译者认为这是一本非常好的入门书籍。因为内容通俗易懂、编排用心,没有太多数学和编程基础的人也能看懂。没有介绍像动态规划这样的重要思想算是一个遗憾。

前言

本书的配套网站 algs4.cs.princeton.edu 提供了丰富的关于数据结构和算法的资料。

TODO 进行探索,看是否能将网站和图书结合起来使用。

第1章 基础

本章介绍学习算法和数据结构所需要的基本工具:

  • 首先介绍的是基础编程模型,即Java语言的基本使用
  • 接下来是数据抽象并定义抽象数据类型以进行模块化编程
  • 之后学习三种基础的抽象数据类型:背包、队列、栈
  • 最后描述了分析算法性能的方法,并且给出了一个案例分析

我们关注的大多数算法都需要适当地组织数据,由此产生了数据结构。数据结构与算法的关系十分密切,作者的观点是它是算法的副产品或结果。理解算法必须学习数据结构,因此本书中会讨论多数数据结构的性质。

学习算法的原因是它们能够节约非常多的资源,甚至能让我们完成一些本不可能完成的任务,精心设计的算法在任何领域都是解决大型问题的最有效的方法。本书的焦点就在那些复杂算法分解后的子问题的算法,他们是被证实为在很多领域内是解决困难问题的有效方法。

1.1 基础编程模型

1.1.1-1.1.10

略,因为自己有过使用Java的经验,并且预备使用JavaScript实现算法因此略过该部分

基础练习

13. 编写一段代码,打印出一个M行N列的二维数组的转制(交换行和列)。

/* Analyze
    
    先遍历行,会先把每一行的所有列号的下标暴露
    反过来,先遍历列,会先把每一列的所有行号暴露
    但是取值仍旧要将行号放在前面,列号放在后面,这是由2维数组的存储结构决定的
*/

// JavaScript Impl
function reversePrint(array, M, N) {
  for (let i = 0; i < N; i++) {
    let line = '';
    for (let j = 0; j < M; j++) {
      line += (array[j][i] + ' ');
    }
    console.log(line);
  }
}

// Test 
var array = [
  [11, 12, 13],
  [21, 22, 23]
];
var M = 2,  N = 3;
reversePrint(array, M, N)

// Test Output
// 11 21 
// 12 22 
// 13 23 

14编写一个静态方法lg(), 接受一个整形参数N,返回不大于log2N的最大整数。


/* Analyze

数值初始为2,操作次数为1
连续做乘以2的操作,直到大于N(<=N要继续做乘法)
返回 `操作次数-1` 作为返回结果

*/

function f(){}

// JavaScript Impl
function lg(N) {

  if(N == 1) return 0;
  
  var value = 2, result = 1;

  while(value <= N) {
    result += 1;
    value *= 2;
  }
  return result - 1;
}

// Test
lg(1) // 0
lg(2) // 1
lg(4) // 2
lg(1025) // 10

16. 给出exR1(6)的返回值:

//Java Define
public static String exR1(int n) {
    if(n <= 0) return "";
    return exR1(n-3) + n + exR1(n-2) + n;
}

// JavaScript
function exR1(int n) {
    if(n <= 0) return "";
    return exR1(n-3) + n + exR1(n-2) + n;
}

// Calculate
        e6
    e3 + 6 + e4 + 6
    (e0 + 3 + e1 + 3) + 6 + (e1 + 4 + e2 + 4) + 6
    (e0 + 3 + (e-2 + 1 + e-1 + 1) + 3) + ((e-2 + 1 + e-1 + 1) + 4 + (e-1 + 2 + e0 + 2) + 4 ) + 6  
    3+1+1+3+1+1+4+2+2+4+6
    result: 31131142246

// Execute Result

    311361142246

    Miss a 6 in third step

17. 找出以下函数的问题

//Java Define
public static String exR1(int n) {
    String s =  exR1(n-3) + n + exR1(n-2) + n;
    if(n <= 0) return "";
    return s;
}

exR2会永远循环调用,退出代码 n <= 0 永远不会被执行

18.

本题执行结果会有6次左右的递归运算,但是连续的乘法使结果变得非常大。作者尝试用一道练习题来告诉我们,递归可能导致最终的运算结果非常大,使用时候需要评估运算量。

19. 在计算机上运行以下程序

// JavaScript Define
function fibonacci(n) {
  if(n === 0 || n === 1) return n;
  return fibonacci(n-1) + fibonacci(n - 2);
}
for (let i = 0; i < 100; i++) {
  console.log(i + ' ' + fibonacci(i));
}

计算机上运行这段程序1小时所能得到的N的最大值是多少?开发一个更好的实现,用数组保存已经计算的值。

// JavaScript Update One
for (let i =0; i < 100; i++) {
  var start = new Date();
  let result = fibonacci(i);
  var end = new Date();
  console.log(i + ' ' + (end.getTime() - start.getTime())/1000 + ' ' + result)
}

答:尝试记录每次调用 fibonacci 的执行时间,发现从45开始,执行时间已经长达16s,并且N每加1,执行时间近乎是成倍的执行时间,必须减少重复运算来提高执行效率,目测很可能1小时下只能执行到54左右。优化后的代码如下(执行时间不到1秒):

// JavaScript Update Two
var caches = new Array(100);
function fibonacci(n) {
  if(chaches[n]) return caches[n];
  if(n === 0 || n === 1) return n;
  return fibonacci(n-1) + fibonacci(n - 2);
}
for (let i = 0; i < 100; i++) {
  var start = new Date();
  let result = caches[i] = fibonacci(i);
  var end = new Date();
  console.log(i + ' ' + (end.getTime() - start.getTime())/1000 + ' ' + result)
}

20. 编写一个递归的静态方法计算ln(N!)的值

基础的数学知识(百度百科):

  • a的x次方等于N(a>0,且a不等于1),那么数x叫做以a为底N的对数,记作x=logaN。其中,a叫做对数的底数,N叫做真数。
  • 自然对数 以无理数e为底 记为ln。
  • 常用对数 以10为底的对数 记为lg。
对数基本公式
//JavaScript 
function ln(N) {
    if(N == 1) return 0;
    return Math.log(N) + ln(N-1);
}

24. 给出使用欧几里得算法计算102

// JavaScript 
function euclid(p, q) {
    if(q > p) {var t = p; p = q; q = t}
    console.log(p + ','+ q);
    if(q == 0) return p;
    return euclid(q,p-q);
}


euclid(102,24)
// 102,24
// 78,24
// 54,24
// 30,24
// 24,6
// 18,6
// 12,6
// 6,6
// 6,0

euclid(1 111 111, 1 234 567);
//...
//Uncaught RangeError: Maximum call stack size exceeded


对应地将 `p-q` 改为  `p%q`,因为p和q差值巨大的时候,会大量的时间用于递归。

25. 数学归纳法证明欧几里得算法那能够对任意一非负整数的 p 和 q 的最大公约数。

假设:p, q的最大公约是是r,假定p >= q

证明:

p/r = p1(整数)
q/r = q1(整数)

(p-q)/r=p1-q1 同样为整数
那么 p 和 p-q 的最大公约数也是r

如果其中p-q为0,那么r 的值即等于此时的p的值

提高题

27. 二项分布。估计用以下代码计算 binomial(100, 50, 0.25)将会产生的递归调用次数,将已经计算过的值保存在数组中并给出一个更好的实现。

// JavaScript Slow
function binomial(N, k, p) {
    if(N == 0 && k == 0) return 1.0;
    if(N < 0 || K < 0) return 0.0;
    return (1.0-p) * binomial(N-1, k, p) + p * binomial(N-1, k-1, p);
}

binomial(5,  2,  .25); // 0.263671875
binomial(5, 3, .25); // 0.087890625
binomial(5, 0, .25); // 0.2373046875
binomial(5, 5, .25); // 9.765625E-4

// JavaScript Quick
function binomial(N, k, p) {
  var mArray = new Array(N + 1);
  for (let i = 0; i <= N; i++) {
    mArray[i] =  new Array(k + 1);
  }
  mArray[0][0] = 1;

  for (let i = 1; i <= N; i++) {
    mArray[i][0] = (1-p)*mArray[i-1][0];
  }
  for (let i = 1; i <= k; i++) {
    mArray[0][i] = 0;
  }

  for (let i = 1; i <= k; i++) {
    for (let j = 1; j <= N; j++) {
      mArray[j][i] = (1-p)*mArray[j-1][i] + p*mArray[j-1][i-1];
    }
  }
  console.log(mArray[N][k]);
}
  1. 等值键。为BinarySearch类添加一个静态方法rank(key, a),它接受一个键和一个整形有序数组(可能存在重复键)作为参数返回数组中小于该键的元素数量,以及一个类似的方法count(key, a)返回该数组中等于该键的元素的数量。
// JavaScript
function rank(key, a) {
  var lo = 0, ro = a.length - 1;
  var mo = -1;
  var li = 0;
  while(lo <= ro) {
    li ++;
    if(li == 1000) break;
    var mid = parseInt((lo + ro) / 2);
    if(a[mid] >= key) {
      ro = mid - 1;
    } else if(a[mid] < key) {
      mo = mid;
      lo = mid + 1;
    }
  }
  return mo;
}

console.log(rank(1, [1,1,2,2,2,3,3,3,3])); // -1
console.log(rank(2, [1,1,2,2,2,3,3,3,3])); // 1
console.log(rank(4, [1,1,2,2,2,3,3,3,3])); // 8
console.log(rank(3, [1,1,2,2,2,3,3,3,3])); // 4
console.log(rank(11, [1,3,5,7,9,11,11,15,19])); // 4 

function count(key, a) {
  var lo = 0, ro = a.length - 1;
  var mo1 = -1, mo2 = -1;
  while(lo <= ro) {
    var mid = parseInt((lo + ro) / 2);
    if(a[mid] >= key) {
      ro = mid - 1;
    } else  {
      mo1 = mid;
      lo = mid + 1;
    }
  }

  lo = 0, ro = a.length - 1;
  while(lo <= ro) {
    var mid = parseInt((lo + ro) / 2);
    if(a[mid] <= key) {
      lo = mid + 1;
    } else  {
      mo2 = mid;
      ro = mid - 1;
    }
  }
  if(mo1 == -1 && mo2 == -1) return 0;
  else if(mo1 == -1) return mo2 + 1;
  else if(mo2 == -1) return a.length - (mo1 + 1);
  else return mo2 - mo1 - 1;
}
console.log(count(5, [1,2,3,4,5,6])) // 1
console.log(count(5, [1,2,5,5,5,6])) // 3
console.log(count(8, [1,2,5,5,5,6])) // 0

相关文章

  • 匈牙利算法

    算法思想 算法流程 算法步骤 算法实现 python 算法应用

  • web开发需要知道的几个算法

    算法分类 快速排序算法 深度优先算法 广度优先算法 堆排序算法 归并排序算法

  • 机器学习算法

    机器学习的算法分监督算法和无监督 算法。监督算法包括回归算法,神经网络,SVM;无监督算法包括聚类算法,降维算法。...

  • 字符串匹配

    BF 算法和 RK 算法BM 算法和 KMP 算法

  • 垃圾回收算法有几种类型? 他们对应的优缺点又是什么?

    常见的垃圾回收算法有: 标记-清除算法、复制算法、标记-整理算法、分代收集算法 标记-清除算法 标记—清除算法包括...

  • 头条-手撕代码

    [toc] 图算法 以及最短路径算法 树算法 手写LRU 排序算法 链表算法

  • 关于一些算法

    我们平常说的算法按照使用方向加密算法,排序算法,搜索算法,优化算法,音视频处理算法,图片处理算法 1.加密解密算法...

  • 给我巨大影响的技术书籍

    算法《算法概论》《算法设计与分析基础》 Anany Levitin《算法引论》Udi Manber《算法导论》《什...

  • 缓存相关

    cache淘汰算法:LIRS 算法 缓存那些事 Redis缓存淘汰算法,LRU算法,LRU算法讲解

  • LZW压缩算法

    参考链接:超级简单的数据压缩算法—LZW算法压缩算法——lzw算法实现LZW算法 LZW 压缩算法正确图解

网友评论

      本文标题:算法

      本文链接:https://www.haomeiwen.com/subject/sunddqtx.html