《程式設計原本 》一2 3 碰撞點

2021-09-23 09:39:18 字數 3246 閱讀 7477

如果不知道定義而只能觀察其行為,我們無法確定乙個變換的一條特定軌道是否無窮,因為它完全可能在任意一點結束或者迴圈.如果知道一條軌道是有窮的,那就可以用乙個演算法來確定該軌道的形狀了.因此,本章針對軌道的所有演算法都有乙個有關軌道有窮性的隱含前條件.

顯然可以寫乙個簡單而平凡的演算法,讓它儲存已訪問的每個元素,在每一步檢查新遇到的元素是否曾經訪問過.雖然可以用雜湊技術來加快搜尋,但這個演算法至少需要線性的儲存空間,而這種要求對於許多實際應用是不能接受的.還好,存在著只需要常量儲存空間的演算法.

下面的模擬有助於理解這一演算法.如果有一輛速度快的汽車和一輛速度慢的汽車在一條道路上行駛,當且僅當存在環路時快車將能追上慢車.如果沒有環路,快車將比慢車先到達道路的終點.如果有環路,在慢車進入環路之後,快車一定會在環路中追上它.下面我們把這種直觀想法從連續域轉到離散域,還要小心地避免快車超過慢車.1

這一演算法的離散版本基於找到快車遭遇慢車的點.變換f和起始點x確定的碰撞點(collisionpoint)就是那個唯一的y,它使

其中的n.0是滿足這一條件的最小整數.這一定義給出了乙個用於確定軌道結構的演算法,它只需比較較快的迭代和較慢的迭代.如果要處理的是部分變換,那就必須給演算法送乙個定義空間謂詞:

templaterequires(transformation(f) && unarypredicate(p) && 

domain(f) == domain(p))

domain(f) collision point(const domain(f)& x, f f, p p)

return fast; //slow=fn(x)∧fast=f2n+1(x)

//後條件:返回終止點或碰撞點的值

}

我們將通過三個步驟來確立collisionpoint演算法的正確性:(1)驗證f不會被應用於定義空間之外的引數;(2)驗證如果它終止,後條件一定滿足;以及(3)驗證它必定終止.

即使f是部分函式,它在這一過程中的使用也總是良定義的,因為fast的移動受到p呼叫的保護.而slow的移動不需要保護,這是由於f的規範性:slow和fast在同一軌道上,所以將f應用於slow時總有定義.

演算法裡的注釋說明,如果經過n.0次迭代後fast變得等於slow,那就一定有fast=f2n+1(x)和slow=fn(x).進一步說,n就是滿足這一條件的最小整數,因為對每個i如果沒有環路,由於有窮性,p將最終返回假.如果有環路,slow最終將到達連線點(環路的第乙個點).在程式裡的迴圈語句開始處考慮當slow剛進入環路時從fast到slow的距離d,有0.d下面過程確定一條軌道是否終止:

templaterequires(transformation(f) && unarypredicate(p) && 

domain(f) == domain(p)) bool terminating(const domain(f)& x, f f, p p)

有時我們知道乙個變換或者是全的,或者從某個特定開始點出發的軌道是不終止的.對於這種情況,下面特殊版本的collisionpoint會很有用:

templaterequires(transformation(f)) domain(f) collision point nonterminating orbit(const domain(f)& x, f f) 

return fast; //slow=fn(x)∧fast=f2n+1(x)

//後條件:返回碰撞點的值

}

要想確定環路的結構,即確定軌道的柄規模、連線點和環規模,就需要分析碰撞點的位置.當過程返回碰撞點時

其中n是slow走過的步數,2n+1是fast走過的步數.而且n=h+d這裡的h是柄規模,0.d0是fast碰到slow時完成整個環的次數.由於n=h+d,所以2(h+d)+1=h+d+qc

通過化簡得到

qc=h+d+1用下式表示h對c取模:h=mc+r其中0.r也就是

d=(q.m)c.r.10.d所以

d=c.r.1而且需要r+1步去完成整個環.這樣,從碰撞點到連線點的距離就是e=r+1對環路軌道有h=0且r=0,從碰撞點到軌道開始點的距離是e=1由此可知,環路性質可以用下面過程檢查:

templaterequires(transformation(f)) bool circular nonterminating orbit(const domain(f)& x, f f) 

templaterequires(transformation(f) && unarypredicate(p) && domain(f) == domain(p)) bool circular(const domain(f)& x, f f, p p)

至此仍不可能知道柄規模h和環規模c.在知道碰撞點之後很容易確定後者,為此只需遍歷環路一次並記錄步數.要想確定h,先看看碰撞點的位置:

fh+d(x)=fh+c.r.1(x)=fmc+r+c.r.1(x)=f(m+1)c.1(x)

從衝突點走h+1步就到達了點f(m+1)c+h(x),它等於fh(x),因為(m+1)c對應於繞環m+1次.如果同時從x走h步並從碰撞點走h+1步,兩者就會在連線點相遇.換句話說,x的軌道和過碰撞點1步的點在恰好h步後匯合,這一情況揭示了下面幾個演算法:

templaterequires(transformation(f)) domain(f) convergent point(domain(f) x0, domain(f) x1, f f) 

return x0;

} templaterequires(transformation(f)) domain(f) connection point nonterminating orbit(const domain(f)& x, f f)

templaterequires(transformation(f) && unarypredicate(p) && domain(f) == domain(p)) domain(f) connection point(const domain(f)& x, f f, p p)

引理2.8如果兩個元素的軌道相交,它們將有同樣的環路元素.

練習2.2請設計乙個演算法,對於給定的變換和定義空間謂詞,它能確定兩個元素的軌道是否相交.

練習2.3convergentpoint的前條件保證它終止.請針對沒有該條件,但已知在兩點x0和x1的軌道上有共同元素的情況實現演算法convergentpointguarded.

《程式設計原本 》一導讀

本書將演繹方法應用於程式設計,討論程式與保證它們能正確工作的抽象數學理論之間的聯絡.書中把反映這些理論的規程 speci.cation 基於這些理論寫出的演算法,以及描述演算法性質的引理和定理一起呈現給讀者.這些演算法在一種實際程式語言裡的實現是本書的中心.雖然規程主要是供人閱讀,但它們也應該 或者...

《程式設計原本 》一1 4 過程

乙個過程 procedure 是乙個指令序列,它修改某些物件的狀態,也可能構造或 者銷毀一些物件.根據程式設計師的意圖,與乙個過程互動的物件分為四類.1.輸入輸出 input output 指該過程通過其引數或返回值直接或間接傳遞的那些物件.2.區域性狀態 localstate 該過程在其一次呼叫期...

《程式設計原本 》一1 5 規範型別

存在這樣一組過程,如果把它們包含到乙個型別的計算基中,就能方便地把物件放入各種資料結構,或者通過演算法把物件從乙個資料結構複製到另一資料結構.我們稱具有這樣的基的型別為規範的 regular 因為使用這樣的型別可以保證程式行為的規範性,進而獲得型別之間的互操作性.1 可以從內部型別,如bool in...