SCIP讀書筆記(1)

這書也算是必修吧,尤爲是我這種非科班人員,仍是應該抽時間儘可能學習一下。大體翻過一遍,習題很是多,盡力吧。
##構造過程抽象
* 爲了表述認知,每種語言都提供了三種機制:基本元素;組合方式;抽象方法。
* 前綴表示法,haskell中有學過,scheme中基本都是前綴表達,這使得表達式的可讀性略微下降。
* 聲明變量:`(#define name value)`,和C中的宏聲明一致,聲明函數:`(#define (func ( para1,para2...) ())`;
* 通常性求職規則是**樹形累積**,其餘的被稱爲**特殊形式**(如`define`語句)。
* Scheme中多項式求值聽從**代換模型**,即逐步將過程調用代換爲對應的過程和變量。先展開後規約的成爲**正則序求值**,不然被稱爲**應用序求值**。在Scala中,分別對應*Call By Name* 和 *Call By Value*。和Scala同樣,Lisp也是默認聽從後者。
* 條件表達式:`cond`關鍵字,對應現代語言中的`switch`,`if`關鍵字,只能在非此即彼的狀況下使用(換言之,沒有`else if`)。
* 邏輯運算符:`and`, `or` 和 `not`.
###練習部分:
1.3:
```lisp
(define (sumMax2 x y z)
         (if (= x (larger x y))
             (+ x (larger y z))
             (+ y (larger x z))))
(define (larger a b)
                  (if (> a b) a b))

(sumMax2 1 2 3)
```

1.5 因爲第二個參數是一個endless loop,若是對其作value展開,就會死掉,換句話說:若是死機就是應用序,不然是正則序。

* 牛頓迭代法求平方根
```lisp
(define (sqrt x)
  (define (isGoodEnough guess)
    (< (abs (- (* guess guess) x)) 0.001))
  (define (improve guess)
    (average guess (/ x guess)))
  (define (sqrtIter guess)
    (if (isGoodEnough guess)
        guess
        (improve guess)))
  (sqrtIter 1.0))

(define (average x y)
  (/ (+ x y) 2.0))

(define (abs x)
  ((if (> x 0) + -) x))
```
1.6 因爲lisp是應用序優先的,所以
```lisp
new-if ( good-enough? guess x) 
            guess  
            (sqrt-iter (improve guess x) x)
```

的計算過程是先計算 `(good-enough? guess x)`,而下一步是計算`(sqrt-iter (improve guess x))`,換句話說,遞歸被無條件執行了,所以程序會陷入死循環。

* 過程的形式參數被稱爲「約束變量」,與之對應的成爲「自由變量」,所謂**約束**,過程約束了形式參數。若是像上文同樣把一些過程定義在某個過程內部,這個過程的形參就在全部的過程當中共享,沒必要再次聲明,這被稱爲「詞法做用域」。有點相似類內部的私有函數。
* 尾遞歸與普通遞歸的區別:簡單來講,尾遞歸能夠被優化成迭代,對於lisp這種沒有循環語句的語言,掌握尾遞歸是一項必備技能。非尾遞歸可能形成堆棧溢出,尾遞歸無此後患(現代C/C++的編譯器也會自動優化尾遞歸)。

1.9 `a+b`展開爲:` ++(--a+b)`,`+`運算須要再次展開,直到a=0,所以是遞歸計算過程;然後者展開爲`(--a)+(++b)`,不須要伸展長度,所以是尾遞歸|迭代。

1.10 (A 1 10)=A(0 (A 1 9))=A(0 (A 0 A(1 8)))..A(...A(1 1))=$2^{10}$

(A 2 4)=A(1 (A 2 3))=A(1 (A 1 A(2 2)))...A(...A(2 1))=$2^{16}$

(A 3 3)=A(2 A(3 2))=A(2 A(2 A(3 1)))...=$2^{16}$

根據上面的展開規律:

(f n)=2n;

(g n)=$2^n$

(h n)=$2^{2^n}=4^n$

* 樹形展開,即遞歸的展開是一棵樹,這個是比較常見的狀況。書中舉了著名的坑爹例子」斐波那契「,這個例子是有名的不適合使用遞歸計算的遞歸式。
$$f(n)=\begin{equation}
    \begin{cases}
        0, &n = 0\\
        1, &n=1\\
        f(n-1)+f(n-2), &n > 1\\
    \end{cases}
    \end{equation}
$$
[$\LaTeX$公式的命令都快不記得了,去查了半天ORZ]

若是想要轉爲迭代,用常規命令語言的思路:
```c
if(n==0)return 0;
if(n==1)return 1;
int pre=0;
int result=1;
int temp;
while(--n>0)
{
    temp=result;
    result=result+pre;
    pre=temp;
}
return result;
```
顯然,須要兩個中間變量,循環結束的條件是次數。使用尾遞歸實現,由於不須要中間變量交換數據,因此所需變量少一個。

```lisp
(define (f n)
    (f-iter 0 1 n)

(define (f-iter  pre result count)
    (if (= count 0) 
        b
        (f-iter result (+pre result) (- count 1)))
)
```
你看,其實並不難,只要你會寫循環…固然,尾遞歸要簡潔不少。若是可以跳出命令式思惟,直接想到尾遞歸寫法,這函數式編程才能算入了門(好難= =)。

* 換零錢的例子。其實這是一個組合算法,總的組合次數=必定用了某個部件的次數+必定沒用某個部件的次數。按着這個規律進行規約,最終退化到邊界狀況,問題解決。使用這種典型的分治式思惟,寫出的代碼很是簡潔。著名的快速排序也是一句話:快排的結果應該是把比某元素小的快排結果放在其前面,把比該元素大的結果放在後面,這個haskell只要兩行,常被用來作廣告,哈哈。

```haskell
qsort [] = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)
```

Lisp版:
```lisp
define (quicksort x)
  (cond
   ((null? x) ‘())
   (else (append
   (quicksort (filter (lambda (y) (< y (car x))) (cdr x)))
   (list (car x))
   (quicksort (filter (lambda (y) (>= y (car x))) (cdr x)))))))
```
注:這個是從網上找的,個人lisp水平和這篇筆記的進度一致。

1.11 
$$ f(n)=\begin{equation}
        \begin{cases}
        n,&n<3\\
        f(n-1)+f(n-2)+f(n-3),&n \ge 3\\
        \end{cases}
    \end{equation}
$$

```lisp
(define (f n)
  (if (<= n 3)
      n
  (f-iter 1 2 3 n)))

(define (f-iter ppre pre result count)
  (if (<= count 3)
      result
      (f-iter pre
              result
              (+ (* 3 ppre) (* 2 pre) result) 
              (- count 1))))
```
1.12 帕斯卡三角形,把它當成一個下三角,從二維的角度來說:
$$f(x,y)=\begin{equation}
\begin{cases}
 1,& y=1 \text{ or } x=y\\
f(x-1,y-1)+f(x-1,y),&x>y  \text{ and } y!=1\\
\end{cases}
\end{equation}
$$

```lisp
(define (f x y)
  (cond ((< x y) null)
        ((= y 1) 1)
        ((= x y) 1)
        (else (+ (f (- x 1) (- y 1))
                 (f (- x 1) y)))))
```
1.13 純數學題,這裏就略過吧ORZ,我印象中上學的時候彷佛證實過。

下面一部分是講複雜度的,CLRS這裏講的很明白,略過。

* 求冪的題目。這題在面試題中卻是很常見,一個技巧
$$a^n=a^{n/2}*a^{n/2}$$
因此若是n是偶數,須要$\log{n}$次,是奇數就是$\log{n}+1$次。對應的代碼段:

```lisp
(define (exp a n)
  (define (even? n)
    (= (remainder n 2) 0))
  (cond (= (n 0) 1)
        ((even? n) (square (exp (/ n 2))))
        (else (* a (exp a n-1)))))
```
顯然,這裏遞歸運算,下面描述對應的迭代,主要利用了公式:
$(a^{n/2})^2=(a^2)^{n/2}$,反向分解的思路:
$5^7=5^3*5^3*5=5^2*5*5^2*5*5$,顯然,n是偶數,除以2,遞歸;n是奇數,減1,遞歸

```lisp
(define (exp a n)
  (define (square a) (* a a))
  (define (even? n)
    (= (remainder n 2) 0))
  (define (exp-iter a n result)
    (cond ((= n 0) result)
          ((even? n) (exp-iter (square a) (/ n 2) result))
          (else (exp-iter a (- n 1) (* a result)))))
  (exp-iter a n 1))
```

1.17 好吧,這種題很適合作面試題,定義了乘法,倍乘和減半,實際上把上面的代碼中相關的代換掉就能夠了。其中`even?`須要用一些技巧,不過也不困難。

先到這裏,這本書讀的很慢…習題太多,每週抽一天時間學習吧。
相關文章
相關標籤/搜索