• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            Creative Commons License
            本Blog采用 知識共享署名-非商業性使用-禁止演繹 3.0 Unported許可協議 進行許可。 —— Fox <游戲人生>

            游戲人生

            游戲人生 != ( 人生 == 游戲 )
            站點遷移至:http://www.yulefox.com。請訂閱本博的朋友將RSS修改為http://feeds.feedburner.com/yulefox
            posts - 62, comments - 508, trackbacks - 0, articles - 7

            以前在學習非數值算法的時候,曾經了解過動態規劃算法(Dynamic programming),以下是對Wikipedia動態規劃的翻譯,圖也是Wikipedia上的,倉促行文,不到之處,請方家指正。

            這篇文章的術語實在是太多了,所以我在文中加入了少量注釋,一律以粗斜體注明。

            本文的不足之處將隨時修正,MIT的《Introduction to Algorithms》第15章是專門講動態規劃的。

            _____________________________________________________________

            動態規劃

            在數學與計算機科學領域,動態規劃用于解決那些可分解為重復子問題(overlapping subproblems,想想遞歸求階乘吧)并具有最優子結構(optimal substructure,想想最短路徑算法)(如下所述)的問題,動態規劃比通常算法花費更少時間。

            上世紀40年代,Richard Bellman最早使用動態規劃這一概念表述通過遍歷尋找最優決策解問題的求解過程。1953年,Richard Bellman將動態規劃賦予現代意義,該領域被IEEE納入系統分析和工程中。為紀念Bellman的貢獻,動態規劃的核心方程被命名為貝爾曼方程,該方程以遞歸形式重申了一個優化問題。

            在“動態規劃”(dynamic programming)一詞中,programming與“計算機編程”(computer programming)中的programming并無關聯,而是來自“數學規劃”(mathematical programming),也稱優化。因此,規劃是指對生成活動的優化策略。舉個例子,編制一場展覽的日程可稱為規劃。 在此意義上,規劃意味著找到一個可行的活動計劃。

            • 概述

             

            圖1 使用最優子結構尋找最短路徑:直線表示邊,波狀線表示兩頂點間的最短路徑(路徑中其他節點未顯示);粗線表示從起點到終點的最短路徑。

            不難看出,start到goal的最短路徑由start的相鄰節點到goal的最短路徑及start到其相鄰節點的成本決定。

             

             

            最優子結構即可用來尋找整個問題最優解的子問題的最優解。舉例來說,尋找上某頂點到終點的最短路徑,可先計算該頂點所有相鄰頂點至終點的最短路徑,然后以此來選擇最佳整體路徑,如圖1所示。

            一般而言,最優子結構通過如下三個步驟解決問題:

            a) 將問題分解成較小的子問題;

            b) 通過遞歸使用這三個步驟求出子問題的最優解;

            c) 使用這些最優解構造初始問題的最優解。

            子問題的求解是通過不斷劃分為更小的子問題實現的,直至我們可以在常數時間內求解。

             

             

            圖2 Fibonacci序列的子問題示意圖:使用有向無環圖(DAG, directed acyclic graph)而非表示重復子問題的分解。

            為什么是DAG而不是樹呢?答案就是,如果是樹的話,會有很多重復計算,下面有相關的解釋。

             

             

            一個問題可劃分為重復子問題是指通過相同的子問題可以解決不同的較大問題。例如,在Fibonacci序列中,F3 = F1 + F2和F4 = F2 + F3都包含計算F2。由于計算F5需要計算F3和F4,一個比較笨的計算F5的方法可能會重復計算F2兩次甚至兩次以上。這一點對所有重復子問題都適用:愚蠢的做法可能會為重復計算已經解決的最優子問題的解而浪費時間。

            為避免重復計算,可將已經得到的子問題的解保存起來,當我們要解決相同的子問題時,重用即可。該方法即所謂的緩存(memoization,而不是存儲memorization,雖然這個詞亦適合,姑且這么叫吧,這個單詞太難翻譯了,簡直就是可意會不可言傳,其意義是沒計算過則計算,計算過則保存)。當我們確信將不會再需要某一解時,可以將其拋棄,以節省空間。在某些情況下,我們甚至可以提前計算出那些將來會用到的子問題的解。

            總括而言,動態規劃利用:

            1) 重復子問題

            2) 最優子結構

            3) 緩存

            動態規劃通常采用以下兩種方式中的一種兩個辦法:

            自頂向下:將問題劃分為若干子問題,求解這些子問題并保存結果以免重復計算。該方法將遞歸和緩存結合在一起。

            自下而上:先行求解所有可能用到的子問題,然后用其構造更大問題的解。該方法在節省堆棧空間和減少函數調用數量上略有優勢,但有時想找出給定問題的所有子問題并不那么直觀。

            為了提高按名傳遞(call-by-name,這一機制與按需傳遞call-by-need相關,復習一下參數傳遞的各種規則吧,簡單說一下,按名傳遞允許改變實參值)的效率,一些編程語言將函數的返回值“自動”緩存在函數的特定參數集合中。一些語言將這一特性盡可能簡化(如SchemeCommon LispPerl),也有一些語言需要進行特殊擴展(如C++,C++中使用的是按值傳遞和按引用傳遞,因此C++中本無自動緩存機制,需自行實現,具體實現的一個例子是Automated Memoization in C++)。無論如何,只有指稱透明(referentially transparent,指稱透明是指在程序中使用表達式、函數本身或以其值替換對程序結果沒有任何影響)函數才具有這一特性。

            • 例子

            1. Fibonacci序列

            尋找Fibonacci序列中第n個數,基于其數學定義的直接實現:

               function fib(n)
                   if n = 0
                       return 0
                   else if n = 1
                       return 1
                   return fib(n-1) + fib(n-2)

            如果我們調用fib(5),將產生一棵對于同一值重復計算多次的調用樹:

            1. fib(5)
            2. fib(4) + fib(3)
            3. (fib(3) + fib(2)) + (fib(2) + fib(1))
            4. ((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))
            5. (((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))

            特別是,fib(2)計算了3次。在更大規模的例子中,還有更多fib的值被重復計算,將消耗指數級時間。

            現在,假設我們有一個簡單的映射(map)對象m,為每一個計算過的fib及其返回值建立映射,修改上面的函數fib,使用并不斷更新m。新的函數將只需O(n)的時間,而非指數時間:

               var m := map(0 → 1, 1 → 1)
               function fib(n)
                   if map m does not contain key n
                       m[n] := fib(n-1) + fib(n-2)
                   return m[n]

            這一保存已計算出的數值的技術即被稱為緩存,這兒使用的是自頂向下的方法:先將問題劃分為若干子問題,然后計算和存儲值。

            自下而上的方法中,我們先計算較小的fib,然后基于其計算更大的fib。這種方法也只花費線性(O(n))時間,因為它包含一個n-1次的循環。然而,這一方法只需要常數(O(1))的空間,相反,自頂向下的方法則需要O(n)的空間來儲存映射關系。

               function fib(n)
                   var previousFib := 0, currentFib := 1
                   if n = 0
                       return 0
                   else if n = 1
                       return 1
                   repeat n-1 times
                       var newFib := previousFib + currentFib
                       previousFib := currentFib
                       currentFib  := newFib
                   return currentFib

            在這兩個例子,我們都只計算fib(2)一次,然后用它來計算fib(3)和fib(4),而不是每次都重新計算。

            2. 一種平衡的0-1矩陣

            考慮n*n矩陣的賦值問題:只能賦0和1,n為偶數,使每一行和列均含n/2個0及n/2個1。例如,當n=4時,兩種可能的方案是:

            + - - - - +             + - - - - +
            | 0 1 0 1 |             | 0 0 1 1 |
            | 1 0 1 0 |             | 0 0 1 1 |
            | 0 1 0 1 |             | 1 1 0 0 |
            | 1 0 1 0 |             | 1 1 0 0 |
            + - - - - +             + - - - - +

            問:對于給定n,共有多少種不同的賦值方案。

            至少有三種可能的算法來解決這一問題:窮舉法(brute force)、回溯法(backtracking)及動態規劃(dynamic programming)。窮舉法列舉所有賦值方案,并逐一找出滿足平衡條件的方案。由于共有C(n, n/2)^n種方案(在一行中,含n/2個0及n/2個1的組合數為C(n,n/2),相當于從n個位置中選取n/2個位置置0,剩下的自然是1),當n=6時,窮舉法就已經幾乎不可行了。回溯法先將矩陣中部分元素置為0或1,然后檢查每一行和列中未被賦值的元素并賦值,使其滿足每一行和列中0和1的數量均為n/2。回溯法比窮舉法更加巧妙一些,但仍需遍歷所有解才能確定解的數目,可以看到,當n=8時,該題解的數目已經高達116963796250。動態規劃則無需遍歷所有解便可確定解的數目(意思是劃分子問題后,可有效避免若干子問題的重復計算)。

            通過動態規劃求解該問題出乎意料的簡單。考慮每一行恰含n/2個0和n/2個1的k*n(1<=k<=n)的子矩陣,函數f根據每一行的可能的賦值映射為一個向量,每個向量由n個整數對構成。向量每一列對應的一個整數對中的兩個整數分別表示該列上該行以下已經放置的0和1的數量。該問題即轉化為尋找f((n/2,n/2),(n/2,n/2),...,(n/2,n/2))(具有n個參數或者說是一個含n個元素的向量)的值。其子問題的構造過程如下:

            1) 最上面一行(第k行)具有C(n, n/2)種賦值;

            2) 根據最上面一行中每一列的賦值情況(為0或1),將其對應整數對中相應的元素值減1;

            3) 如果任一整數對中的任一元素為負,則該賦值非法,不能成為正確解;

            4) 否則,完成對k*n的子矩陣中最上面一行的賦值,取k=k-1,計算剩余的(k-1)*n的子矩陣的賦值;

            5) 基本情況是一個1*n的細小的子問題,此時,該子問題的解的數量為0或1,取決于其向量是否是n/2個(0, 1)和n/2個(1, 0)的排列。

            例如,在上面給出的兩種方案中,向量序列為:

            ((2, 2) (2, 2) (2, 2) (2, 2))       ((2, 2) (2, 2) (2, 2) (2, 2))     k = 4
              0      1      0      1              0       0       1      1

            ((1, 2) (2, 1) (1, 2) (2, 1))       ((1, 2) (1, 2) (2, 1) (2, 1))     k = 3
              1      0      1      0              0      0      1      1

            ((1, 1) (1, 1) (1, 1) (1, 1))       ((0, 2) (0, 2) (2, 0) (2, 0))     k = 2
              0      1      0      1              1      1      0      0

            ((0, 1) (1, 0) (0, 1) (1, 0))       ((0, 1) (0, 1) (1, 0) (1, 0))     k = 1
              1      0      1      0              1      1      0      0

            ((0, 0) (0, 0) (0, 0) (0, 0))       ((0, 0) (0, 0), (0, 0) (0, 0))

            動態規劃在此的意義在于避免了相同f的重復計算,更進一步的,上面著色的兩個f,雖然對應向量不同,但f的值是相同的,想想為什么吧:D

            該問題解的數量(序列a058527在OEIS)是1, 2, 90, 297200, 116963796250, 6736218287430460752, ...

            下面的外部鏈接中包含回溯法的Perl源代碼實現,以及動態規劃法的MAPLE和C語言的實現。

            3. 棋盤

            考慮n*n的棋盤及成本函數C(i,j),該函數返回方格(i,j)相關的成本。以5*5的棋盤為例:

            5 | 6 7 4 7 8
            4 | 7 6 1 1 4
            3 | 3 5 7 8 2
            2 | 2 6 7 0 2
            1 | 7 3 5 6 1
            - + - - - - -
              | 1 2 3 4 5

            可以看到:C(1,3)=5

            從棋盤的任一方格的第一階(即行)開始,尋找到達最后一階的最短路徑(使所有經過的方格的成本之和最小),假定只允許向左對角、右對角或垂直移動一格。

            5 |
            4 |
            3 |
            2 |   x x x
            1 |     o
            - + - - - - -
              | 1 2 3 4 5

            該問題展示了最優子結構。即整個問題的全局解依賴于子問題的解。定義函數q(i,j),令:q(i,j)表示到達方格(i,j)的最低成本。

            如果我們可以求出第n階所有方格的q(i,j)值,取其最小值并逆向該路徑即可得到最短路徑。

            記q(i,j)為方格(i,j)至其下三個方格((i-1,j-1)、(i-1,j)、(i-1,j+1))最低成本與c(i,j)之和,例如:

            5 |
            4 |     A
            3 |   B C D
            2 |
            1 |
            - + - - - - -
              | 1 2 3 4 5

            q(A) = min(q(B),q(C),q(D)) + c(A)

            定義q(i,j)的一般形式:

                        |-  inf.                                                  j<1 or j>n
            q(i,j) = -+-  c(i,j)                                                i=1
                        |-  min(q(i-1,j-1),q(i-1,j),q(i-1,j+1))+c(i,j)   otherwise.

            方程的第一行是為了保證遞歸可以退出(處理邊界時只需調用一次遞歸函數)。第二行是第一階的取值,作為計算的起點。第三行的遞歸是算法的重要組成部分,與例子ABCD類似。從該定義我們可以直接給出計算q(i,j)的簡單的遞歸代碼。在下面的偽代碼中,n表示棋盤的維數,C(i,j)是成本函數,min()返回一組數的最小值:

            function minCost(i, j)
                if j < 1 or j > n
                    return infinity
                else if i = 1
                    return c(i,j)
                else
                    return min(minCost(i-1,j-1),minCost(i-1,j),minCost(i-1,j+1))+c(i,j)

            需要指出的是,minCost只計算路徑成本,并不是最終的實際路徑,二者相去不遠。與Fibonacci數相似,由于花費大量時間重復計算相同的最短路徑,這一方式慢的恐怖。不過,如果采用自下而上法,使用二維數組q[i,j]代替函數minCost,將使計算過程快得多。我們為什么要這樣做呢?選擇保存值顯然比使用函數重復計算相同路徑要簡單的多。

            我們還需要知道實際路徑。路徑問題,我們可以通過另一個前任數組p[i,j]解決。這個數組用于描述路徑,代碼如下:

            function computeShortestPathArrays()
                 for x from 1 to n
                     q[1, x] := c(1, x)
                 for y from 1 to n
                     q[y, 0]     := infinity
                     q[y, n + 1] := infinity
                 for y from 2 to n
                     for x from 1 to n
                         m := min(q[y-1, x-1], q[y-1, x], q[y-1, x+1])
                         q[y, x] := m + c(y, x)
                         if m = q[y-1, x-1]
                             p[y, x] := -1
                         else if m = q[y-1, x]
                             p[y, x] :=  0
                         else
                             p[y, x] :=  1

            剩下的求最小值和輸出就比較簡單了:

            function computeShortestPath()
                 computeShortestPathArrays()
                 minIndex := 1
                 min := q[n, 1]
                 for i from 2 to n
                     if q[n, i] < min
                         minIndex := i
                         min := q[n, i]
                 printPath(n, minIndex)

            function printPath(y, x)
                 print(x)
                 print("<-")
                 if y = 2
                     print(x + p[y, x])
                 else
                     printPath(y-1, x + p[y, x])

            4. 序列比對

            序列比對是動態規劃的一個重要應用。序列比對問題通常是使用編輯操作(替換、插入、刪除一個要素等)進行序列轉換。每次操作對應不同成本,目標是找到編輯序列的最低成本。

            可以很自然地想到使用遞歸解決這個問題,序列AB的最優編輯通過以下措施之一實現:

            插入B的第一個字符,對AB的剩余序列進行最優比對;

            刪去A的第一個字符,對AB進行最優比對;

            B的第一個字符替換A的第一個字符,對A的剩余序列和B進行最優比對。

            局部比對可在矩陣中列表表示,單元(i,j)表示A[1..i]到b[1..j]最優比對的成本。單元(i,j)的成本計算可通過累加相鄰單元的操作成本并選擇最優解實現。至于序列比對的不同實現算法,參見Smith-WatermanNeedleman-Wunsch

            對序列比對的話題并不熟悉,更多的話也無從談起,有熟悉的朋友倒是可以介紹一下。

            • 應用動態規劃的算法

            1) 許多字符串操作算法如最長公共子列最長遞增子列最長公共字串

            2) 將動態規劃用于的樹分解,可以有效解決有界樹寬圖生成樹等許多與圖相關的算法問題;

            3) 決定是否及如何可以通過某一特定上下文無關文法產生給定字符串的Cocke-Younger-Kasami (CYK)算法;

            4) 計算機國際象棋轉換表駁斥表的使用;

            5) Viterbi算法(用于隱式馬爾可夫模型);

            6) Earley算法(一類圖表分析器);

            7) Needleman-Wunsch及其他生物信息學中使用的算法,包括序列比對結構比對RNA結構預測;

            8) Levenshtein距離(編輯距離);

            9) 弗洛伊德最短路徑算法;

            10) 連鎖矩陣乘法次序優化;

            11) 子集求和背包問題分治問題的偽多項式時間算法;

            12) 計算兩個時間序列全局距離的動態時間規整算法;

            13) 關系型數據庫的查詢優化的Selinger(又名System R)算法;

            14) 評價B樣條曲線的De Boor算法

            15) 用于解決板球運動中斷問題的Duckworth-Lewis方法;

            16) 價值迭代法求解馬爾可夫決策過程

            17) 一些圖形圖像邊緣以下的選擇方法,如“磁鐵”選擇工具在Photoshop

            18) 間隔調度

            19) 自動換行

            20) 巡回旅行商問題又稱郵差問題或貨擔郎問題);

            21) 分段最小二乘法

            22) 音樂信息檢索跟蹤。

            對于這些算法應用,大多未曾接觸,甚至術語翻譯的都有問題,鑒于本文主要在于介紹動態規劃,所以倉促之中,未及查證。

            • 相關

            1) 貝爾曼方程

            2) 馬爾可夫決策過程

            3) 貪心算法

            • 參考
          1. Adda, Jerome, and Cooper, Russell, 2003. Dynamic Economics. MIT Press. An accessible introduction to dynamic programming in economics. The link contains sample programs.
          2. Richard Bellman, 1957, Dynamic Programming, Princeton University Press. Dover paperback edition (2003), ISBN 0486428095.
          3. Bertsekas, D. P., 2000. Dynamic Programming and Optimal Control, Vols. 1 & 2, 2nd ed. Athena Scientific. ISBN 1-886529-09-4.
          4. Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein, 2001. Introduction to Algorithms, 2nd ed. MIT Press & McGraw-Hill. ISBN 0-262-03293-7. Especially pp. 323–69.
          5. Giegerich, R., Meyer, C., and Steffen, P., 2004, "A Discipline of Dynamic Programming over Sequence Data," Science of Computer Programming 51: 215-263.
          6. Nancy Stokey, and Robert E. Lucas, with Edward Prescott, 1989. Recursive Methods in Economic Dynamics. Harvard Univ. Press.
          7. S. P. Meyn, 2007. Control Techniques for Complex Networks, Cambridge University Press, 2007.
            • 外部鏈接

            _____________________________________________________________

            關于動態規劃,這只是一篇譯文,后面將根據實際問題具體寫點動態規劃的應用。

          8. posted @ 2008-05-07 20:43 Fox 閱讀(31112) | 評論 (9)編輯 收藏

            Author: Fox

            首先聲明:本人沒有解決掉這個問題。

            相比第一道讓CPU占用率曲線聽你指揮對Windows系統中CPU占有率概念的考察和對API的使用,以及第二道中國象棋將帥問題對抽象建模的考察。這道題目才算是一道算法題吧?之前那兩道尤其是中國象棋將帥問題總有點腦筋急轉彎的味道。

            題目:星期五的晚上,一幫同事在希格瑪大廈附近的“硬盤酒吧”多喝了幾杯。程序員多喝了幾杯之后談什么呢?自然是算法問題。有個同事說:

            “我以前在餐館打工,顧客經常點非常多的烙餅。店里的餅大小不一,我習慣在到達顧客飯桌前,把一摞餅按照大小次序擺好——小的在上面,大的在下面。由于我一只手托著盤子,只好用另一只手,一次抓住最上面的幾塊餅,把它們上下顛倒個個兒,反復幾次之后,這摞烙餅就排好序了。

            我后來想,這實際上是個有趣的排序問題:假設有n塊大小不一的烙餅,那最少要翻幾次,才能達到最后大小有序的結果呢?

            你能否寫出一個程序,對于n塊大小不一的烙餅,輸出最優化的翻餅過程呢?

            排序問題是數據結構和算法中比較重要的一個了,之前在一篇被同事成為標題黨的文章中因為提到排序中關于(非)穩定排序的概念還被很多TX鄙視了一番,甚至引來人身攻擊,現在想來都有些后怕。

            這道題目一眼看上去最先讓我想到的居然是那道經典的漢諾塔(Tower of Hanoi)問題(當然,漢諾塔不算排序問題)。

            1) 相似點★:

            a. 都要不斷調整位置,最終實現從小到大排好;

            b. 都要借助中間量進行調整。

            2) 不同處★:

            a. 漢諾塔有多出來的兩根針,翻烙餅只有一只手,明確說明沒有第三只手;

            b. 漢諾塔一次只能移動一個,翻烙餅沒限制;

            c. 漢諾塔要保證小的始終在上面,翻烙餅沒限制;

            d. 漢諾塔移動之前就有序,所以其移動次數是固定的,算法的邏輯也固定(不管是遞歸還是棧操作),翻烙餅沒有這個前提。

            3) 把題目用程序語言描述一下吧★:

            a. Input : n.

            b. Process : generate n random number 0-(n-1), sortting.

            c. Output : 0, 1, ..., n-1, and move times num.

            4) 存儲結構★★★:

            我最開始想到的是:這一摞烙餅其實就是一個雙鏈表,每翻一次相當于將頭節點H與某非頭節點N進行如下變換:

            H->next = N->next

            N->prior = H->prior = NULL

            N->next->prior = H

            如果使用普通的雙鏈表,這兒就有一個很明顯的問題,H和N之間的所有節點(如果有的話)的前趨prior和后繼next互換了,對于n比較大的情況,這個操作明顯浪費時間啊(交換前趨prior和后繼next和題目要求的翻幾次似乎也沒有關系吧?只是我作為一個一線的Coder考慮的太具體了)。如果摒棄前趨和后繼的概念,又該怎樣描述呢?

            唐朝禪宗大師青原行思曾說:參禪之初,看山是山,看水是水;禪有悟時,看山不是山,看水不是水;禪中徹悟,看山仍然山,看水仍然是水

            俗:日,扯那么多,什么意思?

            師:前趨不是前趨,后繼不是后繼。

            俗:日,前趨不是前趨,難道是后繼嗎?

            師:然也。

            Fox:O, my God!整點實際的吧!翻轉一次之后,前趨視為后繼,后繼視為前趨,第奇數次翻轉的前趨是后繼,第偶數次翻轉的后繼是前趨。

            整個鏈表的形態如下:

            H:Head, F:First, G:F's next, B:C's prior, C:Change, D, C's next, L:Last.

            H<==>F<==>G<=...=>B<==>C<==>D<=...=>L

            F與C交換的操作如下(Word、PS畫圖),n表示next,p表示prior:

            這里只需要修改F、D節點的prior,H、C節點的next,其他節點不變。

            后面想了一下,這種方式很難在不添加flag或者對換n、p的情況下正常操作,沒有找到好的方法L如果你有好的方法不妨告訴我

            最后只好作罷,何苦呢?直接使用數組就完了嘛J!既然是數組,除了翻轉移動麻煩一點,理解和操作還是很容易的。

            果然不是搞數學、算法出身的,一上來考慮的就是如何存儲^.^'''',而不是算法本身。

            更可笑的是,扯了半天,最后居然還是用數組

            5) 算法分析★★★★★:

            冒泡排序思想:

            最關鍵的是要抽象出隨機數列特征(含當前和移動后數列特征的抽象),并盡量使每一次翻轉有效(所謂有效,即盡量保證每一次操作都向最后的有序靠近而非背離 )。

            師:要使大頭在后,應使大頭在后。

            俗:日,這是什么狗屁邏輯?

            師:因果。在前既是在后。

            俗:USA, CNN(操你娘)。

            師:翻轉。既不在前,也不在后,使之在前,使之在后。

            俗:日,什么東西?既不在前,也不在后,不前不后,難道在中間啊?

            師:然也。

            Fox:O, my God!整點實際的吧!整個過程分為n輪,在第i(i=0, 1, ..., n-1)輪:

            a. 找到大頭n-i,是否有序?是,轉g;

            b. 是否大頭在后?是,轉f;

            c. 是否大頭在前?是,轉e;

            d. 將隊頭(第一個元素)與大頭n-i對調(別忘了是翻轉,中間元素也變序了),++times

            e. 將隊頭n-i與第n-i個元素對調,++times

            f. ++i,轉a;

            g. 輸出序列(0, 1, ..., n)和翻轉次數times;OVER:D。

            快速排序思想:

            在最開始的時候,我就想到使用快速排序的思想,先使整個數列局部有序,最后調整使全部有序。悲哀的是,在我考慮 4 3 1 2這個數列的時候,我斷定還要通過上面的方式重新像冒泡排序一樣重新來過,立即放棄想法,于是給了上面的思路,而且堅定的認為這個方法已經很好。結果,下午GR告訴我他的反例:4 3 1 2 --> 2 1 3 4|--> 1 2| 3 4,“|”表示從該處翻轉。

            我必須承認,這才是好的方法,我過分拘泥于不去改動已經有序的部分。然而,這家伙只知道反駁我,卻無法給出算法。

            我只好自己重新考慮局部有序之后的問題。

            十分鐘后,我有了如下的答案(目前我能想到的最佳答案),但不得不承認,表述算法比給出一種情況對應的解要麻煩的多的多的多,假定A、B滿足A==B-1,即A、B為相鄰數列(為簡單記,元素和數列統稱數列)。則A、B的組合類型有8種:B(O)A(O)、B(C)A(O)、B(O)A(C)、B(C)A(C)、A(C)B(O)、A(O)B(O)、A(C)B(C)、A(O)B(C),O表示正向(obverse)C表示逆向(reverse),以1 2 3 4為例:

            B(O)A(O):3 4 1 2<2>B(C)A(O):4 3 1 2<4>B(O)A(C):3 4 2 1<5>、B(C)A(C):4 3 2 1<7>;

            A(C)B(C):2 1 4 3<1>A(O)B(C):1 2 4 3<3>A(C)B(O):2 1 3 4<6>、A(O)B(O):1 2 3 4<8>。

            對應操作規則如下:

            a. 0x0101: B(O)A(O) --> B(C)A(O); 3

            b. 0x0001: B(C)A(O) --> A(C)B(O); 2

            c. 0x0101: B(O)A(C) --> B(C)A(C);1

            d. 0x0000: B(C)A(C):如果當前只剩A、B兩個子列,則翻轉一次成A(O)B(O)1 2 3 4為最終結果,否則,認為B(C)A(C)可以作為一個逆序有序數列考慮,暫時無需翻轉;

            e. 0x1010: A(C)B(C) --> A(O)B(C); 3

            f. 0x1110: A(O)B(C) --> B(O)A(C);  2

            g. 0x1011: A(C)B(O) --> A(O)B(O);1

            h. 0x1111: A(O)B(O):A、B可以作為一個有序數列考慮如果當前只有A、B兩個子列,則正序序列A(O)B(O)1 2 3 4為最終結果

            上面規則的制定其實是反向導出的,遵循的原則是,正序有序的A(O)B(O)和逆序有序的B(C)A(C)可以看作一個序列,A(C)B(O)、B(O)A(C)可一步達到,B(C)A(O)、A(O)B(C)可兩步達到,B(O)A(O)、A(C)B(C)可三步達到。即對于4個元素,最壞的的A(C)B(C)需要4步(對應于上面的冒泡法卻只需要3步L)。而且當元素比較多的時候,記住1、2個有序子列是可行的,但對于完全無序的數列,分析出所有有序子列,既不現實,也無必要。

            修改規則如下:隊頭無序&&相鄰數列有序||隊頭有序,翻轉隊頭;否則,將隊頭連同該元素一同翻轉

            由此可見,這算法還要改進:

            a. 判斷Array[0]是否正序連續(連續個數nNum1),如果nNum1==n,轉i,如果nNum1!=1,轉c;

            b. 判斷Array[0]是否逆序連續(連續個數nNum1),如果nNum1==n,翻轉Array,轉f;

            c. 從下標nNum1開始查找Array[0]+1(bObserve = true)和Array[0]-1(bObserve = false)的下標nStart2,如果nNum1==nStart2bOrder1==true,轉e,如果nNum1!=1,置nEnd2=nStart2

            d. 判斷( bObserve == true&&Array[nStart2]+1==Array[nStart2+1] ) || ( bObserve == false&&Array[nStart2]==Array[nStart2+1]+1 ),true則置nEnd2=nStart2,false則置nEnd2=nStart2+1;

            e. 翻轉Array[0] to Array[nEnd2],轉a;

            f. 輸出Arraytimes

            這樣來看,改進后的算法竟簡單了許多!

            不幸的是:按上面給出的算法翻轉合并1 3 5 6 4 8 7 9 2 0:

            1 3 5 6 4 8 7 9| 2| 0

            2 9 7 8 4 6 5| 3| 1 0

            3 5 6| 4| 8 7 9 2 1 0

            4 6| 5| 3 8 7 9 2 1 0

            5 6| 4 3 8 7 9 2 1 0

            6 5 4 3 8| 7| 9 2 1 0

            7 8 3 4 5| 6| 9 2 1 0

            進入死循環了……

            很明顯應該是下面這個樣子:

            1 3 5 6 4 8 7 9 2| 0

            9 8 7 4 6 5| 3 1 2 0

            5 6 4| 7 8 9 3 1 2 0

            6 5 4 7| 8 9 3 1 2 0

            4 5 6 7 8 9 3| 1 2 0

            3 4 5 6 7 8 9 1 2| 0

            1 9 8 7 6 5 4 3 2| 0

            2 3 4 5 6 7 8 9 1| 0

            9 8 7 6 5 4 3 2 1 0|

            0 1 2 3 4 5 6 7 8 9

            執行9次翻轉。算法如何得到呢?

            a. 確定最小無序完整子集SnSn中含n>1個連續數);

            b. 將Sn最前面的有序子集Soo>1)加以考慮,一個子集?兩個子集?

            ______________________________________________________________________________

            O, my God!

            這個問題,從前天晚上到現在,思考、分析、抽象了至少有15個小時以上了:

            a. Apr.18th-19th: 23:00 - 01:30

            b. Apr.19th: 11:00 - 13:00

            c. Apr.19th-20th: 22:00 - 05:30

            d. Apr.20th: 11:00 - 15:00

            結果是,到現在無法給出一個最優的翻轉算法。一個周末都花在這上面了,準備放棄L

            LP催著我讓我回學校,是該回去了!

            posted @ 2008-04-20 14:59 Fox 閱讀(4021) | 評論 (13)編輯 收藏

            Author: Fox

            晚上沒有加班,打游戲打到9點過,后面就又看了一道《編程之美》的題目《中國象棋將帥問題》。

            題目:下過中國象棋的朋友都知道,雙方的“將”和“帥”相隔遙遠,并且它們不能照面。在象棋殘局中,許多高手能利用這一規則走出精妙的殺招。假設棋盤上只有“將”和“帥”二子(如圖1-3所示)(為了下面敘述方便,我們約定用A表示“將”,B表示“帥”):

            1-3副本

            AB二子被限制在己方3×3的格子里運動。例如,在如上的表格里,A被正方形{d10, f10, d8, f8}包圍,而B被正方形{d3, f3, d1, f1}包圍。每一步,AB分別可以橫向或縱向移動一格,但不能沿對角線移動。另外,A不能面對B,也就是說,AB不能處于同一縱向直線上(比如Ad10的位置,那么B就不能在d1d2以及d3)。

            請寫出一個程序,輸出AB所有合法位置。要求在代碼中只能使用一個變量。

            在紙上畫了半天,Soft從臺灣給帶的長壽都讓我抽完了,總算對得起這會兒工夫……

            我的思路大致如下:

            1) 只能使用一個變量nNum ==> 只能使用一個循環,nNum只能用來表示A、B位置的組合,nNum最大為9×9-1=80;

            2) 如何用nNum表示一個A、B位置的組合?

            下圖表示A(紅色)B(藍色)所在位置:

            6 7 8
            3 4 5
            0 1 2
            6 7 8
            3 4 5
            0 1 2

            nNum%9表示A的位置nNum/9表示B的位置,如nNum==15,A==6B==1

            3) 如何確定A、B位置的合法性?

            規則都指定了,合法性的確定也就很簡單了:A%3 != B%3。

            OK,剩下的輸出就很簡單了,為了好看一點,這里希望直接按題目給的圖表示出A、B的位置,如:“A:d10, B:e3”,還有顏色:D。

            A的行號:A/3+8;

            A的列號:A%3+d;

            B的行號:B/3+1;

            B的列號:B%3+d;

            代碼如下(注釋掉的部分只是為了輸出更“漂亮”一點):

             

             1 #include <stdio.h>
             2 //#include <windows.h>
             3 
             4 //HANDLE hStdout;
             5 //CONSOLE_SCREEN_BUFFER_INFO csbiInfo;
             6 //WORD wOldColorAttrs;
             7 
             8 int _tmain(int argc, _TCHAR* argv[])
             9 {
            10     //hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
            11     //GetConsoleScreenBufferInfo(hStdout, &csbiInfo);
            12     //wOldColorAttrs = csbiInfo.wAttributes;
            13 
            14     int nNum = 81;    // nNum表示所有位置(含非法),故nNum = 3 * 3 * 3 * 3
            15     while( nNum-- )
            16     {
            17         if( nNum%9%3 != nNum/9%3 )
            18         {
            19             //SetConsoleTextAttribute(hStdout, FOREGROUND_RED | FOREGROUND_INTENSITY);
            20             printf("A:%x%02d ", nNum%9%3+0xd, nNum%9/3+8);
            21             //SetConsoleTextAttribute(hStdout, FOREGROUND_BLUE | FOREGROUND_INTENSITY);
            22             printf("B:%x%02d  ", nNum/9%3+0xd, nNum/9/3+1);
            23         }
            24         if!(nNum%9) )
            25             printf("\n");
            26     };
            27     printf("\n");
            28     //SetConsoleTextAttribute(hStdout, wOldColorAttrs);
            29     return 0;
            30 }

            輸出:

            點擊查看更清晰原圖:D

            PS: 剛寫完,沒有來得及總結更多,急著向LP炫耀。但上面的思路應該比較清晰了,也不管書上的答案了,反正我感覺我這點代碼效率應該也不會低到哪兒吧:-)?

            posted @ 2008-04-18 00:26 Fox 閱讀(4018) | 評論 (8)編輯 收藏

            Author: Fox

            前兩天在買《計算機程序設計藝術》中文版的時候,偶然發現《編程之美》這本書,當時翻了一下,看到“讓CPU占用率曲線聽你指揮”這樣的題目確實讓人為之一動。寫一段代碼,可以讓CPU占有率曲線畫出平滑的正弦曲線,有點意思:-)。

            當然,最后沒有買這本書,雖然我可以肯定這是本好書。

            我從CSDN讀書上找到幾節,閑來讀一讀。今天來討論一下《讓CPU占用率曲線聽你指揮》。

            題目:寫一個程序,讓用戶來決定Windows任務管理器(Task Manager)的CPU占用率。程序越精簡越好,計算機語言不限。例如,可以實現下面三種情況:

            1.    CPU的占用率固定在50%,為一條直線;

            2.    CPU的占用率為一條直線,但是具體占用率由命令行參數決定(參數范圍1~ 100);

            3.    CPU的占用率狀態是一個正弦曲線。

            在討論具體實現之前,一個非常重要的問題是:什么是CPU占用率

            編程之美》寫道:“在任務管理器的一個刷新周期內,CPU忙(執行應用程序)的時間和刷新周期總時間的比率,就是CPU的占用率,也就是說,任務管理器中顯示的是每個刷新周期內CPU占用率的統計平均值。

            打開“Windows 任務管理器”,“性能”中有“CPU使用記錄”一項,給出的就是CPU占有率曲線。

            至于一個刷新周期到底是多長,書中似乎沒有明確給出,只是說“大約是1秒鐘更新一次”,我打開Windows自帶的時鐘,也感覺大約是1秒鐘。

            另外的常識是:

            單核環境下,空死循環會導致100%的CPU占有率。雙核環境下,CPU總占有率大約為50%,四核會不會是25%左右呢?(我沒有四核,只能猜測了,估計各核間切換也會耗掉點時間,因為我的雙核環境并沒有出現一核100%,另一核空閑的情況)。

            當CPU整個刷新周期(絕大多數時間)空閑時,CPU占有率趨于0。

            書中給出的正弦實現如下:

            1 #include "Windows.h"
            2 #include "stdlib.h"
            3 #include "math.h" 
            4 
            5 const double SPLIT = 0.01;
            6 const int COUNT = 200;
            7 const double PI = 3.14159265;
            8 const int INTERVAL = 300;
            9 
            10 int _tmain(int argc, _TCHAR* argv[])
            11 {
            12     DWORD busySpan[COUNT];  //array of busy times
            13     DWORD idleSpan[COUNT];  //array of idle times
            14     int half = INTERVAL / 2;
            15     double radian = 0.0;
            16     for(int i = 0; i < COUNT; i++)
            17     {
            18         busySpan[i] = (DWORD)(half + (sin(PI * radian) * half));
            19         idleSpan[i] = INTERVAL - busySpan[i];
            20         radian += SPLIT;
            21     }
            22     DWORD startTime = 0;
            23     int j = 0;
            24     while (true)
            25     {
            26         j = j % COUNT;
            27         startTime = GetTickCount();
            28         while ((GetTickCount() - startTime) <= busySpan[j]) ;
            29         Sleep(idleSpan[j]);
            30         j++;
            31     }
            32     return 0;
            33 }


            在單核環境(P4 2.40)下,其表現還是不錯的:

            點擊查看大圖

            在雙核環境(Core2 E4500)下,就有點差強人意不盡人意了:

            點擊查看大圖

            不過,總還能看出是正弦曲線。

            上面兩圖的問題:

            1) 單核時曲線不夠平滑,是由于QQ等程序占用CPU所致;

            2) 雙核時曲線更加抖動,我的理解是除其他程序影響外,由于線程沒有固定運行在一個CPU上導致的,后面看到書上提到線程遷移,個人感覺這個叫法欠妥啊,總覺得線程遷移令人費解。

            可以立即想到的是:讓進程在指定處理器上運行(處理器親緣關系),由Windows提供了兩個API可以做到這一點:GetCurrentProcessSetProcessAffinityMask的。

            修改之后的代碼如下:

            1 #include "Windows.h"
            2 #include "stdlib.h"
            3 #include "math.h" 
            4 
            5 const double SPLIT = 0.01;
            6 const int COUNT = 200;
            7 const double PI = 3.14159265;
            8 const int INTERVAL = 300;
            9 
            10 int _tmain(int argc, _TCHAR* argv[])
            11 {
            12    SetProcessAffinityMask(
            13         GetCurrentProcess(),
            14         0x00000001          //cpu mask
            15         );
            16 
            17     DWORD busySpan[COUNT];  //array of busy times
            18     DWORD idleSpan[COUNT];  //array of idle times
            19     int half = INTERVAL / 2;
            20     double radian = 0.0;
            21     for(int i = 0; i < COUNT; i++)
            22     {
            23         busySpan[i] = (DWORD)(half + (sin(PI * radian) * half));
            24         idleSpan[i] = INTERVAL - busySpan[i];
            25         radian += SPLIT;
            26     }
            27     DWORD startTime = 0;
            28     int j = 0;
            29     while (true)
            30     {
            31         j = j % COUNT;
            32         startTime = GetTickCount();
            33         while ((GetTickCount() - startTime) <= busySpan[j]) ;
            34         Sleep(idleSpan[j]);
            35         j++;
            36     }
            37     return 0;
            38 }


            雙核環境(Core2 E4500)修改之后的輸出如下:

            點擊查看大圖

            我理想中的表現是:

            1) 曲線是平滑的,最好不因其他應用程序或操作的執行而改變;

            2) 不管是單核還是雙核,峰值皆為100%,谷值為0。

            對于第一點,其實就是保證任一刷新周期中的CPU占有率都可以被精確控制在0-100之間,如果你可以使CPU一直保持50%(而不是近似的上下波動),產生一條平滑的曲線就很easy了。

            問題的關鍵在于,除了當前你寫的程序可以控制,其他程序或操作如何控制?或者說:如何控制CPU的運行情況才是關鍵之處

            PS: 一晚上老是斷網,搞得思路頻頻被打斷,興致也損了大半。總之,《編程之美》還是值得玩味一把吧:D。

            posted @ 2008-04-17 00:20 Fox 閱讀(11169) | 評論 (10)編輯 收藏

            Author: Fox

            本文寫給滿足以下條件的你(前面4點對應只讀的你,后面4點對應只寫的你):

            1) 經常閱讀別人的Blog,所謂經常,平均每天閱讀量在100篇左右;

            2) 不希望花費大量時間在輸入網址、鼠標點擊和滾動上;

            3) 有固定的閱讀習慣,指專注于某一領域、某些特定的Blog;

            4) 尚未使用或剛使用Google Reader并愿意使用Google Reader

            5) 經常更新自己的Blog,所謂經常,平均每月更新1篇或以上;

            2) 不希望花費大量時間在復制、粘貼上;

            3) 希望多與其他人交流;

            4) 尚未使用或剛使用Windows Live Writer并愿意使用Windows Live Writer

            1、For只讀的你

            1) 啟用Google Reader

            做互聯網的就是做互聯網的,GoogleGoogle Reader有個帳號就能開啟。

            之前寫過一個給我GF看的一點東西,這兒對于啟用Google Reader不再贅述了,有需要的TX可以看一下或者直接Google

            2) 添加RSS地址

            首先要找到RSS地址,大多數網站提供的Blog,RSS地址都擺在顯眼的地方,對于QQ空間這種算不上Blog的Blog來說,QQ空間的RSS也存在,比如,QQ號碼為10000的用戶,它的RSS就是:http://feeds.qzone.qq.com/cgi-bin/cgi_rss_out?uin=10000

            找到RSS地址之后,就可以將其添加到訂閱列表里面了。

            3) 關于OPML

            如果你想共享或備份你的訂閱,Google Reader具有“導入/導出”功能,不提供具體的使用方式了,Google吧。

            4) 星標和標簽

            看過的好文希望下次再讀,就加個星標吧。

            RSS太多,就使用標簽吧。

            覺得我這兒寫的太簡單,就Google吧,或者看看Google Reader的四個常用小技巧

            2、For只寫的你

            1) 使用Windows Live Writer

            做軟件的就是做軟件的,Microsoft的這個Windows Live Writer是要下載安裝的。

            具體如何去用,下載下來自然就會用了。

            友情提示:Live Writer的格式、超鏈接、查看、草稿、帳戶、日志、分類列表,都很好用。

            2) 關于CSS

            我不太喜歡Blog的頁面太亂,一會兒五號字,一會兒二號字,一會兒宋體,一會兒楷體。

            我喜歡把一些我認為重要的地方鏈接的內容突出顯示,以前沒添加CSS的時候,每次都要自己去一個一個的修改鏈接的字體和顏色,浪費很多時間,如果你的Blog支持CSS,就去看一下怎么使用吧,比如我的CSS就很簡單:

            <style type=text/css>
            #top a{ border-bottom:1px dashed; color:white;  }
            #top a:link{ border-bottom:1px dashed; color:white; }
            #top a:hover{ border-bottom:1px dashed; color:white; }
            #top a:visited{ border-bottom:1px dashed; color:white; }
            .post a:link{ border-bottom:1px dashed; color:maroon; }
            .post a:hover{ border-bottom:1px dashed; color:maroon; }
            .post a:visited{ border-bottom:1px dashed; color:maroon; }
            .postbody a{ color:white; background:maroon;  }
            .postbody a:link{ color:white; background:maroon;  }
            .postbody a:hover{ color:white; background:maroon; }
            .postbody a:visited{ color:white; background:maroon; }
            </style>

            我還使用了DevToolBar幫助我確定了CSS。

            3) 添加Google專用的訂閱

            我為此還專門PS了一張圖片,你可以將下面的代碼放到“公告”里面,當然,你想放到哪兒就放到哪兒:

            <br />訂閱到:<br />
            <a href="><img src="http://m.shnenglu.com/images/cppblog_com/Fox/6064/o_GoogleRss.jpg" border="0" alt="添加 游戲人生 到 Google 閱讀器"></a><br />

            我這兒是有圖片的,只有文字的就是這樣:

            <br />訂閱到:<br />
            <a href=">添加 游戲人生 到 Google 閱讀器</a><br />

            上面的m.shnenglu.com/Fox/Rss.aspx是我的RSS地址,你要換成你的:-)。

            4) 關于郵箱地址

            不要把郵箱地址直接放在頁面上,我之前曾經這樣做,后面每天收到不少的垃圾郵件,就取消了,因為這年頭,寫個郵箱地址搜索器,然后發垃圾郵件太easy了。

            PS: 好用的東西還很多,因為我自己是GFans,所有在工作、學習和生活中使用了Analytics、Gmail、iGoogle、Picasa、Reader、Talk、筆記本、快訊、日歷、網頁歷史記錄、網站管理員工具、文件,反正都是免費的,而且用來比較方便,一個帳號可以搞定很多東西:D。

            posted @ 2008-04-15 15:59 Fox 閱讀(1828) | 評論 (3)編輯 收藏

            Author: Fox

            John von Neumann說:Any one who considers arithmetical methods of producing random digits is , of course, in a state of sin.

            所以,在討論算法實現隨機數的時候,總是說“偽隨機數”。

            現在,應用最廣的隨機數生成算法是由Derrick Henry Lehmer1951年給出的線性同余法

            ????????????? Xn+1 = ( aXn + c ) mod m,? n>=0.

            上一篇偽隨機數的論述中,并沒有給出X0, a, c, m的取值規則,只是給出了ANSI CMicrosoft Visual C++的實現。

            在這兒我們可以自己先思考一下,我們期望從上式中得到的隨機數應該滿足:

            1) 上式的輸出足夠隨機,這是最基本的要求;

            2) 上式給出盡量多的輸出,越接近m個越好(不可能超過m),即周期盡量長,最好為m,這樣才能保證上式滿足均勻分布m個數在周期m中各出現一次m個數出現的概率相等);

            3) 上式的生成速度足夠快。

            最容易想到的,m的取值為計算機字大小(如2^32或2^64)

            但是這兒有個很嚴重的問題:Xn低位的隨機性很弱。原因如下:

            令d|m, 且

            ????????????? Yn = Xn mod d

            ????????????? Yn+1 = ( ( aXn + c ) mod m ) mod d

            ????????????????????? = ( aYn + c ) mod d

            上述表達式的意義即:Yn為Xn低k位(d=2^k),這樣的Yn序列形成周期為d甚至更短的同余序列。舉例說明:d為2^1時,Yn為Xn的最低位(可假定為1或0),若Yn+1 != Yn,則Yn+2 == Yn必定成立,僅當a、c皆為奇數時Yn、Yn+1將0、1交替,否則,為常數(0或1)。

            暫時拋開隨機性不管,先找到周期為m的隨機序列中的取值規則。

            Donald KnuthThe Art of Computer Programming, Volume 2: Seminumerical Algorithms中的3.2.1.2節對m, a, c和X0取值規則的表述:

            1) gcd(c, m) = 1. 即c, m互素,再白一點,c, m除1之外沒有其他公因子;

            2) 任給質數p, p|m ==> p|(a-1). 即m%p==0,則(a-1)%p==0。

            3) 4|m ==> 4|(a-1). 即m%4==0,則(a-1)%4==0。

            這個證明過程對于我這樣的數論基礎不是很扎實的搞應用技術的人來說有點難以理解了。有興趣的話,還是去看3.2.1.2的證明吧:-)。

            上面的規則告訴我們,滿足了上述規則后,可以保證序列周期為m。對于前面提到的關于隨機性的問題,既然Xn低位的隨機性比較弱,可以只取Xn的高位作為輸出。高位的隨機性和統計意義由a, c確定,其取值涉及統計檢驗,具體的也還是看3.3吧。

            這篇文章解決了具有統計意義的隨機數的部分理論問題。

            PS: 之前曾經BS過Windows Live Writer,當時覺得Writer編輯功能太少,不能直接設定鏈接文字的字體顏色,知道CSS可以設定之后,又覺得Word 2007編輯的Blog轉成html之后太大,而且也知道Word 2007上面是可以設置鏈接的target為_blank的。現在發現Writer還是很不錯的了,原來是可以設定格式的,也可以直接編輯html,而且可以Web預覽,鏈接還可以加入到鏈接詞匯表,挺方便的。

            越來越發現Wikipedia是個和Google一樣的好東西!

            posted @ 2008-04-15 14:01 Fox 閱讀(4353) | 評論 (3)編輯 收藏

            轉自:http://m.shnenglu.com/Bugs/archive/2008/04/01/45903.html

            基本要求:
            1、有軟件工程思想,熟悉面向對象思想。
            2、有良好的編碼風格和文檔編寫習慣
            3、熟悉C++語言,了解STL
            4、了解多線程程序設計技術
            5、熱愛游戲、有游戲開發經驗者優先
            6、有團隊開發精神優先

            客戶端程序員:
            1、了解DirectX編程技術,有良好的數學、各種算法基礎優先
            2、有圖形開發經驗者優先
            3、熟悉UI編程技術優先
            4、熟悉引擎開發者優先

            服務器端程序員:
            1、熟悉通信以及網絡安全技術. 熟悉TCP/IP協議及相關編程技術優先
            2、有關系數據庫編程及概念經驗(MySql、PostgreSQL、MS Sql)
            3、了解分布式服務器架構技術優先
            4、了解Lua、Python有游戲腳本系統設計經驗優先

            辦公地點:成都
            有意向的朋友跟貼或發簡歷到本博左側郵箱。

            posted @ 2008-04-11 12:41 Fox 閱讀(864) | 評論 (2)編輯 收藏

            Author: Fox

            一、隨機數

            軟件實現的隨機數生成器(random number generator, RNG)生成的隨機數列大多是惟定數列,因此一般被稱作偽隨機數(pseudorandom number, PRN),而基于熱噪聲(thermal noise)光電效應(photoelectric effect)放射性衰變(radioactive disintegration)等不可預知的物理現象實現的硬件隨機數生成器(hardware random number generator)生成的隨機數才被認為是真隨機數(truly random number)。PRN在計算機領域主要用于仿真(simulation)和密碼學(cryptography)。

            本文僅討論偽隨機數軟件實現算法,并且僅討論滿足均勻分布(uniform distribution, UD)的偽隨機數發生器(pseudorandom number generator, PRNG)。非均勻分布(non-uniform distribution, NUD)的PRNG可通過滿足均勻分布的PRNG與非線性函數生成。

            本文討論的PRNG應滿足以下三點:

            a. 在取值空間內滿足UD,即等概率取得取值空間內任意值。

            b. 充分隨機,即該隨機數列周期(period)應盡量長。此點由所謂的熵(entropy)度量。

            c. 唯一輸入或稱種子(seed)對應唯一輸出PRN。這一點ms是計算機的強項,也是PRN惟定的根源,也是算法被破解的根源。

            由于PRN sequence(PRNS)惟定,所以算法的安全性主要取決于熵的選擇和算法的復雜性

            二、可預測PRNG與不可預測PRNG

            所謂可預測(determined)PRNG,也被稱為統計意義上的PRNG(statistic PRNG),一般只有一個seed,而對于同一個seed,生成的PRNS是惟定的。

            不可預測(indetermined)PRNG,從理論上講,不可預測的PRNG是不存在的,這兒指密碼學安全的PRNG(cryptographically secure PRNG, CSPRNG)。

            三、常用PRNGs

            1、線性同余發生器(linear congruential generators, LCG)

            單博偉標準庫rand()函數的缺陷以及Blitz++隨機數生成的簡介中給出了The C Programming Langurage 2nd的實現,我手頭沒有這本書,所以無從查證,FallHunter應該還有吧。

            ?1 unsigned? int ?next? = ? 1 ;
            ?2
            ?3 /* ?rand:?return?pseudo-random?integer?on?0..32767? */
            ?4 int ?rand( void )
            ?5 {
            ?6 ?next? = ?next? * ? 1103515245 ? + ? 12345 ;
            ?7 ? return ?(unsigned? int )(next / 65536 )? % ? 32768 ;
            ?8 }

            ?9
            10 /* ?srand:?set?seed?for?rand()? */
            11 void ?srand(unsigned? int ?seed)
            12 {
            13 ?next? = ?seed;
            14 }

            VS2003中給的實現是:

            ?1 /* **
            ?2 *rand.c?-?random?number?generator
            ?3 *
            ?4 *???????Copyright?(c)?Microsoft?Corporation.?All?rights?reserved.
            ?5 *
            ?6 *Purpose:
            ?7 *???????defines?rand(),?srand()?-?random?number?generator
            ?8 *
            ?9 ****************************************************************************** */

            10
            11 #include? < cruntime.h >
            12 #include? < mtdll.h >
            13 #include? < stddef.h >
            14 #include? < stdlib.h >
            15
            16 #ifndef?_MT
            17 static ? long ?holdrand? = ? 1L ;
            18 #endif ??/*?_MT?*/
            19
            20 /* **
            21 *void?srand(seed)?-?seed?the?random?number?generator
            22 *
            23 *Purpose:
            24 *???????Seeds?the?random?number?generator?with?the?int?given.??Adapted?from?the
            25 *???????BASIC?random?number?generator.
            26 *
            27 *Entry:
            28 *???????unsigned?seed?-?seed?to?seed?rand?#?generator?with
            29 *
            30 *Exit:
            31 *???????None.
            32 *
            33 *Exceptions:
            34 *
            35 ****************************************************************************** */

            36
            37 void ?__cdecl?srand?(
            38 ????????unsigned? int ?seed
            39 ????????)
            40 {
            41 #ifdef?_MT
            42
            43 ????????_getptd() -> _holdrand? = ?(unsigned? long )seed;
            44
            45 #else ??/*?_MT?*/
            46 ????????holdrand? = ?( long )seed;
            47 #endif ??/*?_MT?*/
            48 }

            49
            50
            51 /* **
            52 *int?rand()?-?returns?a?random?number
            53 *
            54 *Purpose:
            55 *???????returns?a?pseudo-random?number?0?through?32767.
            56 *
            57 *Entry:
            58 *???????None.
            59 *
            60 *Exit:
            61 *???????Returns?a?pseudo-random?number?0?through?32767.
            62 *
            63 *Exceptions:
            64 *
            65 ****************************************************************************** */

            66
            67 int ?__cdecl?rand?(
            68 ???????? void
            69 ????????)
            70 {
            71 #ifdef?_MT
            72
            73 ????????_ptiddata?ptd? = ?_getptd();
            74
            75 ???????? return (?((ptd -> _holdrand? = ?ptd -> _holdrand? * ? 214013L
            76 ???????????? + ? 2531011L )? >> ? 16 )? & ? 0x7fff ?);
            77
            78 #else ??/*?_MT?*/
            79 ???????? return (((holdrand? = ?holdrand? * ? 214013L ? + ? 2531011L )? >> ? 16 )? & ? 0x7fff );
            80 #endif ??/*?_MT?*/
            81 }


            2、LFG, LFSR等

            限于篇幅,有興趣的TX可以參考后面的資料,主要是Wikipedia,上面給的算法還是非常詳細的,CSPRNGs包括Blum Blum Shub、Fortuna等。

            如果出于安全性的考慮,PRNG的輸出不應直接作為種子。在《構建安全的軟件》一書中,作者認為“一個好的PRNG具有這樣的性質:給足夠的熵,即使攻擊者知道全部的算法細節,還是不能猜出生成的數據流”。

            三、PRNG的安全測試

            FIPS-140標準包含了對RNs的測試規范。這個標準我也沒有去仔細看,所以沒法給出更多的信息。被提到的測試包有DIEHARD、pLab

            四、隨機數在游戲中的應用

            1、隨機數為游戲帶來更多的不確定性,不確定性產生可玩性

            這個應該是所有游戲的根本了吧。游戲中玩家、怪物、NPC的很多屬性都是一個范圍,比如攻擊就有最小攻擊和最大攻擊,每次的實際攻擊傷害總在二者之間。

            同樣的,玩家樂此不疲的一次次“洗裝備”、“碰運氣”。其他類推吧?。

            2、游戲的AI更多的依賴于隨機數

            如果怪物、NPC的AI一切盡在玩家的掌握中,游戲的樂趣就大大降低了,這一點在單機游戲中有著很好的體現。War3中,人機對戰,電腦的戰術并不單一(但還是有規律可循,人類玩家尚且如此),這其中固然有多方面 的因素。但隨機數的功勞也不容抹殺。

            3、隨機數減少數據存儲,一個種子可以代替一批數據

            游戲中含有大量數據,如果每一項都要存取,對時間和空間都是一個考驗。對于場景中隨機生成的怪物信息,如果給定一個相同的種子。呃,是不是要簡單一些呢?

            五、相關內容

            至于為什么PRNGs大多使用素數,需要更多的數論知識,對密碼學有了解的TX應該能夠體會到安全和數論之間的緊密聯系。因為手頭沒有數論的書,所以不多加妄測了。到時寫論文的時候,再填充上吧。

            六、參考文獻

            1. 構建安全的軟件. [美]John Viega, Gary McGraw. 鐘向群, 王鵬 譯.? 北京. 清華大學出版社, 2003.

            2. Pseudorandom number generator及相關鏈接. http://en.wikipedia.org/wiki/Pseudorandom_number_generator

            3. PRNG - Pseudo-Random Number Generator. http://statmath.wu-wien.ac.at/prng/

            -------------------------------------------------------------------------

            PS01:手上的幾本書

            從幾位TX那兒拿的幾本書,放在桌上大半年了,一直沒有怎么翻過。想想周末還給他們算了,于是就拿過來翻翻,立此存照,如果以后用到的話,再來查。

            a. 《用TCP/IP進行網際互聯》Vol. III,主要是針對Linux/POSIX套接字的C/S架構實現。因為MMORPG的C/S架構多依賴于TCP,上面第8、10-16章的內容可以關注一下。

            b. 《構建安全的軟件》,上面關于開源、閉源的口水(Chap. 4),關于緩沖區溢出(Chap. 7),關于隨機數(Chap. 10),關于數據庫安全(Chap. 14),關于客戶端安全(Chap. 15),都是值得一看的東西。

            c. 《UNIX環境高級編程》,進程控制(Chap. 8)、線程控制(Chap. 12)、進程通信(Chap. 15, 17)、套接字(Chap. 16)、數據庫(Chap. 20)。

            d. 《UNIX網絡編程》Vol.I,如果涉及到泛UNIX操作系統下網絡編程,可以當作參考書翻。

            e. 《計算機安全原理》,加密(Chap. 5)、認證(Chap. 6)、協議安全(Chap. 7)、入侵檢測(Chap. 13)、惡意攻擊(Chap. 15)、災難恢復(Chap. 19)。

            PS02:微軟宣布不會抬高收購Yahoo價格

            消息來自Wall Street Journal,不過當天可是April Fool

            PS03:關于Wikipedia

            不是說Wikipedia被禁的嗎?很久沒有訪問過了,這么好的東西,被封了還是很遺憾的。

            PS04:有問題

            發現問題,找Google;解決問題,找Wikipedia

            PS05:歡迎補充

            -------------------------------------------------------------------------

            Added on Apr.10th

            今天從CodeProject上看到一篇文章Applied Crypto++: Pseudo Random Number Generators),作者Jeffrey Walton對密碼學和安全比較有研究。

            Jeffrey Walton對Knuth的The Art of Computer Programming Vol.2中關于隨機數的部分作了概括。

            這篇文章從一個工程師的角度給出了隨機數的應用和實現,很具有參考性。

            作者還從FIPS 140-2標準中引用了下面一段話:

            Random Number Generators fall into one of two classes: deterministic and nondeterministic. A deterministic RNG consists of an algorithm that produces a sequence of bits from an initial value called a seed. A nondeterministic RNG produces output that is dependent on some unpredictable physical source that is outside human control.

            這一段話很好的說明,依賴于算法的RNG所生成的隨機數列只可能是偽隨機數,真隨機數依賴于不可預測的物理源

            posted @ 2008-04-03 02:35 Fox 閱讀(6743) | 評論 (15)編輯 收藏

            Author: Fox

            在以前寫 MMORPG中游戲世界的構建 時,提到服務器架構的分類。大多數情況下,每一種不同的服務器只會與其對應的一個服務器和多個客戶端通信。比如,GameServer(GS)只會與WorldServer(WS)通信,也就是說GS只作為WS的客戶端。這次,由于項目需求,新增加了一個SomeServer(SS)作為GS的服務器。

            一、SS網絡連接分析

            由于需要和大量GS建立網絡連接,所以SS使用了IOCP模型。鑒于 上一次寫IOCP 時遭到 Kevin TX的鄙視,所以決定今天多寫一點。SS的網絡模型大致如下:

            0、服務器主線程啟動;

            1、初始化Winsock,SDK func: WSAStartup ();

            2、創建一個使用overlapped I/O的socket,SDK func: WSASocket();

            3、綁定端口,將本地地址與創建的socket關聯起來,SDK func: bind();

            4、創建IOCP對象,SDK func: CreateIoCompletionPort();

            5、創建工作者線程,CreateWorkerThreads();

            6、開始監聽,SDK func: listen();

            7、接受客戶端連接,SDK func: WSAAccept();

            8、當有新的連接請求到達時,將WSAAccept返回的對應的客戶端socket關聯到IOCP;

            9、處理WSASend() or WSARecv()。

            在實際處理時,可能會根據需要建立額外的線程處理socketopt命令,甚至建立專門處理WSAccept的線程。

            關于工作者線程WorkerThread:

            通過GetQueuedCompletionStatus(),獲取I/O類型和對應的socket,如果為接收則通知接收完成并繼續新的WSARecv(),如果為發送則通知發送完成。

            二、GS網絡連接分析

            GS上對于SS客戶端采用的是WSAEventSelect模型,通過網絡事件觸發相應操作。

            0、服務器主線程啟動;

            1、初始化Winsock,SDK func: WSAStartup ();

            2、創建一個使用overlapped I/O的socket,SDK func: WSASocket();

            4、綁定端口,將本地地址與創建的socket關聯起來,SDK func: bind();

            5、創建網絡事件,SDK func: CreateEvent();

            6、設置網絡事件的響應,SDK func: WSAEventSelect();

            7、等待網絡事件,SDK func: WSAWaitForMultipleEvents();

            8、分析網絡事件類型并處理,SDK func: WSAEnumNetworkEvents()。

            這里之所以采用CreateEvent而不是WSACreateEvent,是因為由CreateEvent創建的事件允許為auto reset的,而WSACreateEvent創建的事件是manually reset的。

            三、實現過程中的小插曲

            在GS的客戶端實現中遇到幾個問題。

            首先是在消息處理上,GS發到SS的消息,SS并沒有完全接受到,而SS發送到GS的消息一切正常。后來跟了一下SS消息隊列,發現SS居然可以收到GS發送到WS的消息!然后就在GS上找原因,原來是WS在和SS共用消息隊列,以前GS只對應一個服務器,無所謂共用。現在加了SS,自然要分開處理,否則WS和SS都可能收到發給對方的消息。

            后面一個bug從周一開始已經強奸了我四天了。即使SS已經關閉,WSAEnumNetworkEvents返回的事件對應FD_CONNECT的iErrorCode值始終為0。因為中間涉及到多線程和多個服務器分別對應的客戶端,連接到WS的沒有問題,就是SS的客戶端有問題。到今天上午為止,我已經把GS的網絡處理邏輯全部靜態分析了一遍,沒有任何發現。臨近中午吃飯的時候,不得已只好用WS的客戶端socket去連接SS,居然出現同樣問題!而我的WS和SS都是放在我機器上的,這樣來看,就只有端口不同了!

            果然,當我把SS的監聽端口修改之后,問題解決了。因為我是使用8088端口監聽GS連接的。當我把端口換成80,同樣問題再次出現,而且SS無法通過80端口監聽。

            接下來提幾個問題:

            1、 被卡巴斯基監控的端口8088和服務器開啟的監聽端口8088有什么聯系?為什么沒有沖突?卡巴僅僅只是從該端口獲取數據嗎?為什么網絡事件的FD_CONNECT的對應iErrorCode為0(表明連接成功)?

            2、 80是常規http端口,它與8080、8088這些http端口的區別在哪兒?這些端口綁定成功與否的原則是什么?

            ?

            PS:文中關于IOCP和WSAEventSelect模型更為詳細的實現,可以參考 Network Programming for Microsoft Windows 2nd 的第五章:Winsock I/O Methods。

            最后寫完了,發覺自己寫的很垃圾,完全就是記流水帳。轉念一想,為什么呢?自己基礎不扎實嘛,第一次接觸IOCP和網絡模型,也就這樣了。

            今天太晚了,要睡了,上面的問題明天再考慮吧 J

            posted @ 2008-03-28 01:41 Fox 閱讀(3689) | 評論 (8)編輯 收藏

            Author: Fox

            昨天越俎代庖面試了一個家伙。

            看完了他的筆試題目,感覺后背有點涼,但這些東西看看也就過去了,說實話,那些C++的題目多少有點BT

            但我一直覺得DS的東西,如果你當初學的時候是很認真學習過并思考過的,其實是不需要去記憶的,所以我就問了一個關于穩定排序和不穩定排序的問題。我想,只要你理解了各種排序算法的思想,很easy。

            只是這哥們兒忘記了什么是穩定排序,我還以為他把快速排序、堆排序當作穩定排序只是沒記住。看來,老師從小教育的"一道題目你即使不會也要盡量去答"這種思想遺毒頗深。如果抱著這種思想做程序員,公司多半要垮掉。

            想一想穩定排序的概念吧:兩個同值元素(不知為什么,我一直記得嚴老師書上用的是49,看來我還是在讀死書死讀書,最后可能會讀書死L)在排序前后相對位置保持不變,即本來在前面的還在前面(所謂"塵歸塵,土歸土",看來最近思想有點消極,難怪沒有激情L)。再想想各種排序的思想,我們很容易得到這樣的結論:最普通的O(n2)的算法,一個一個從前比到后,自然不會影響到同值元素的相對位置,而O(nlogn)的算法,由于多路比較,可能導致本來相對位于后面的元素先比較和移動,造成不穩定。這樣一想,自然知道簡單的插入、選擇、歸并排序都是穩定的,而改進的高效率的算法則不穩定。

            后面另一個同事在詢問他做的Demo的事情,因為是DX的東西,我不懂,沒插嘴,就隨便看他的簡歷。

            看到其中一項,有提到他曾經給大一、大二的學生做過C++培訓。我本沒打算提他筆試中的C++部分的,但既然曾經為人師表(因為我曾經做過學生、也做過老師),C++基礎掌握到這種程度就不對了。尤其對于一個空的C++類默認生成哪些成員函數居然寫的一塌糊涂(友情提示:你也不用BS他,如果你沒有看過Lippman的《Inside of the C++ Object Model》,建議你先不要發言J)。

            我一般對語言特性不太敢發表觀點(因為我的C++基礎不扎實L),但我對簡單的算法或思想小有興趣(沒有你想象中那么高)。可是,筆試中唯一的一個需要coding的題目他又沒寫。我只好說,C++的東西你掌握怎么樣我也可以不看,但這個memcpy的實現,你怎么也得有點想法吧?不然怎么去寫代碼呢?剛好在面他之前,還和同事討論過memcpy的問題(如果你給出one byte by one byte的實現會遭BS的J,因為你居然沒有考慮過計算機系統本身的數據處理)。

            本來還想問他一個關于sizeof()的問題,后來覺得也沒什么必要,關于union的對齊,要按照單位最長的成員對齊這一點自己都覺得有點BT就算了。

            其實,我想說的是,很多東西,你不能認為你掌握的很好(除非你真的掌握的很好),所謂很好,拿C++來說,就是把你認為你好的地方,你可以不翻其他東西,把它寫下來,基本跟ISO C++保持90%以上的相似度就可以了。當然,這樣說有點賤了。

            畢竟,做游戲程序員(其他也差不多吧)需要的是:

            帶著激情去編碼,帶著虛心去學習,帶著挑戰去交流,帶著壓力去工作。

            激情,能讓你的思維滿具創意,代碼極其飄逸;

            虛心,能讓你的知識不斷積累,從而達到厚積薄發;

            挑戰,能讓你的團隊充滿活力,交流活潑嚴謹;

            壓力,能讓你的心態保持平衡,勝不妄喜,敗不惶餒。

            因為自己這兩周心態受到非智力因素干擾,日子過得有點渾噩。寫下來,主要是為了放松一下,也提醒自己。

            不怕無知,但怕無畏。

            -----------------------------------------------------------------

            PS:補記于2008/03/26

            還是把上午寫的一個mymemcpy放上來吧。里面沒有對des < src + len的重疊情況進行討論,因為大致google了一下,似乎很少人這樣做(倒不是因為不能實現)。

            void *mymemcpy( void *src, void *des, size_t len )
            {
            ?char *tempsrc = (char *)src;
            ?char *tempdes = (char *)des;

            ?size_t offset = len / 4;
            ?for( size_t i=0; i<offset; ++i )
            ?{
            ??*(unsigned long *)tempdes = *(unsigned long *)tempsrc;
            ??tempdes += sizeof(unsigned long);
            ??tempsrc += sizeof(unsigned long);
            ?}
            ?
            ?offset = len - len % 4;
            ?for( size_t i=0; i<offset; ++i )
            ?{
            ??*tempdes++ = *tempsrc++;
            ?}
            ?return des;
            }

            剛才想求證一下memcpy在地址重疊的情況下,是否會考慮從后往前copy的情況。結果看到云風的blog上很早的一篇文章,也是講memcpy的,角度不同。

            我想澄清一點,我寫這篇blog的初衷只是總結幾個技術問題,因此就沒有把面試的前因后果講一下,反倒讓很多朋友誤解,以為我怎么怎么樣了。

            事實情況是,這幾個問題都是本來的筆試題目當中的,面試的TX從上午10:00前后做到11:30過,等我和另一個同事13點過去的時候,我一直沒怎么說話。只是在一邊看他的簡歷和題目,文中已經說了,是看到他的簡歷之后才提的問題。當時是有10道左右的C++題目,他做對的其實只有一道。

            而且,我在提問題的時候也都將問題跟他一起分析了的(除了memcpy之外),自我感覺說話還是很得體的,寫文章的風格是另一碼事兒。

            我沒有絲毫瞧不起這位TX的意思,也完全沒有顯擺的想法。

            PS :忽然想到自己最近為什么癖性十足,因為最近在關注一個家伙的 Blog ,如果不侵權,我想用用他的 Blog 的名字《 不許聯想 》,作者是帶三個表王小峰(《三聯生活周刊》記者)。所以,如果有人想拍我,建議先看看他的東西,學習一下措辭 J 。一個同事,說天涯也行,我個人覺得天涯有點相互吹捧的味道。

            惡心,但沒有惡意 J

            posted @ 2008-03-20 21:17 Fox 閱讀(4120) | 評論 (52)編輯 收藏

            僅列出標題
            共7頁: 1 2 3 4 5 6 7 
            婷婷久久综合九色综合绿巨人| 日本WV一本一道久久香蕉| 精品多毛少妇人妻AV免费久久 | 久久青青草原亚洲av无码| 日日狠狠久久偷偷色综合96蜜桃 | 久久亚洲精品成人av无码网站 | 久久久久久伊人高潮影院| 精品少妇人妻av无码久久| 国产精品久久久99| 无码精品久久一区二区三区| 久久精品人人做人人爽97 | 久久免费国产精品一区二区| 日本欧美国产精品第一页久久| 午夜欧美精品久久久久久久| 国产—久久香蕉国产线看观看| 无码AV中文字幕久久专区| 久久激情五月丁香伊人| 久久综合给合久久狠狠狠97色| 久久久久无码国产精品不卡| 99久久免费国产精品热| 亚洲av成人无码久久精品| 中文成人无码精品久久久不卡 | 久久不射电影网| 亚洲AV日韩精品久久久久久| 一本久久免费视频| 久久www免费人成精品香蕉| 99久久99久久| 亚洲熟妇无码另类久久久| 亚洲伊人久久成综合人影院| 国产99久久九九精品无码| 国产精品18久久久久久vr| 久久久噜噜噜久久中文福利| 婷婷综合久久中文字幕蜜桃三电影| 亚洲日韩欧美一区久久久久我| 久久一区二区免费播放| 国产精品一区二区久久精品无码| 久久国产免费观看精品| 国内精品久久国产大陆| 久久久婷婷五月亚洲97号色| 996久久国产精品线观看| 精品久久一区二区三区|