有沒有一段程式碼,讓你覺得人類的智慧也可以璀璨無比?

matrix67發表於2016-03-26

網友在知乎的一個提問帖:

有沒有一段程式碼,讓你覺得人類的智慧也可以璀璨無比?

不一定要是完整演算法,就是那種看著看著就覺得嗨爆了,驚為天人的結構或語句。

下面是【燒茄子】引用了知名博主 Matrix67 的一篇博文:

用三段 140 字元以內的程式碼生成一張 1024×1024 的圖片

Kyle McCormick 在 StackExchange 上發起了一個叫做 Tweetable Mathematical Art 的比賽,參賽者需要用三條推這麼長的程式碼來生成一張圖片。

具體地說,參賽者需要用 C++ 語言編寫 RD 、 GR 、 BL 三個函式,每個函式都不能超過 140 個字元。每個函式都會接到 i 和 j 兩個整型引數(0 ≤ i, j ≤ 1023),然後需要返回一個 0 到 255 之間的整數,表示位於 (i, j) 的畫素點的顏色值。舉個例子,如果 RD(0, 0) 和 GR(0, 0) 返回的都是 0 ,但 BL(0, 0) 返回的是 255 ,那麼影象的最左上角那個畫素就是藍色。

參賽者編寫的程式碼會被插進下面這段程式當中(我做了一些細微的改動),最終會生成一個大小為 1024×1024 的圖片。

我選了一些自己比較喜歡的作品,放在下面和大家分享。

首先是一個來自 Martin Büttner 的作品:

它的程式碼如下:

同樣是來自 Martin Büttner 的作品:

這是目前暫時排名第一的作品。它的程式碼如下:

下面這張圖片仍然出自 Martin Büttner 之手:

難以想象, Mandelbrot 分形圖形居然可以只用這麼一點程式碼畫出:

Manuel Kasten 也製作了一個 Mandelbrot 集的圖片,與剛才不同的是,該圖描繪的是 Mandelbrot 集在某處區域性放大後的結果:

它的程式碼如下:

這是 Manuel Kasten 的另一作品:

生成這張圖片的程式碼很有意思:函式依靠 static 變數來控制繪畫的程式,完全沒有用到 i 和 j 這兩個引數!

這是來自 githubphagocyte 的作品:

它的程式碼如下:

這是來自 githubphagocyte 的另一個作品:

這是一張使用 diffusion-limited aggregation 模型得到的圖片,程式執行起來要耗費不少時間。程式碼很有意思:巧妙地利用巨集定義,打破了函式與函式之間的界限,三段程式碼的字數限制便能合在一起使用了。

最後這張圖來自 Eric Tressler :

這是由 logistic 對映得到的 Feigenbaum 分岔圖。和剛才一樣,對應的程式碼也巧妙地利用了巨集定義來節省字元:

下面是 高城 的分享,伯樂線上已徵得許可

我在這裡提供我見識到的三個精彩演算法的解析,強烈地推薦給初學的演算法愛好者,它們可能會令你眼界大開,同時堅定你在演算法大道上勇往直前的信念。

#3. 二進位制是人類的好朋友,線上的樹的最近公共祖先(LCA)演算法:

利用數的二進位制表示可以產生很多加速演算法,online-LCA是其中之一。許多演算法的加速是對數率的,就是利用了數的二進位制表示。
首先定義二維陣列:prede[N+1][B+1], N表示樹的結點的數量,結點以數字1到N代指,B滿足條件:2^(B)>=N
令fa[i]表示結點i的父結點,那麼prede[i][b]的含義是:

也就是說,prede[i][b]指的是從結點i往上走2^(b)步,所到達的結點。如果走到了盡頭,就令prede[i][b]為0。
我們只需要O(NlogN)的複雜度,就可以完成prede的初始化。此外,我們還需要預處理出所有結點的高度,也就是depth[i],定義為:

當遇到詢問LCA(x, y),我們只需要採取如下行動,就可以O(logN)的代價獲得答案:

上述程式碼的精髓在於兩個for(int i = B; i >= 0; i –),這裡利用了數的二進位制表示。可以證明,對於任何嚴格小於2^(B+1)的非負整數t,下面的程式碼執行之後可以令a == t,

#2. 集合之交,樹狀陣列,動態更改、查詢陣列字首和演算法。

實現樹狀陣列所需的程式碼極為簡易,實際上它是一棵殘缺的線段樹,它可以實現一部分線段樹的功能(但凡可以化為區間求和的問題基本上都能解決),但是畢竟不如線段樹功能完整,有興趣的讀者應該學習一下線段樹的知識。

問題描述:利用預處理的字首和陣列pre[N + 1],我們可以O(1)的代價對靜態的陣列A[N + 1]求取區間和:

pre[i] = A[0] + A[1] + A[2] + … + A[i];

A[a] + A[a+1] + A[a+2] + … + A[b] = pre[b] – pre[a-1];

但是當需要對陣列A進行動態的更改時,上述程式碼就失效了。我們需要一種演算法,可以動態地更改以及查詢字首和陣列pre[N+1]。下面首先展示樹狀陣列的程式碼,然後解釋其數學原理,它的插入和查詢的代價都是O(logN):

演算法中最關鍵的語句是位操作i&(-i),讀者在稿紙上算一算就可以知道:

i -= i&(-i)的功能是令i的最低的非0位變為0;

i += i&(-i)的功能是令i的最低的非0位變為0,並往更高一位進一。
理解樹狀陣列的行為,需要構造兩個集合:

Define lowbit(i) = i&(-i);

up(a) = {a, a1, a2, …}, ai = a(i-1) + lowbit(a(i-1));

down(a) = {a, a1, a2, …, 0}, ai = a(i-1) – lowbit(a(i-1));

可以證明,對於任何a <= b的正整數對(a,b),up(a)和down(b)的交集都有且僅有一個元素。對這個定理進行含糊的說明是很容易的,a == b的情況不必考慮,a < b時,總有一個最大的i,使得b的第i位大於a的第i位(也就是b的第i位是1,而a的第i位是0),那麼對b產生down(b),對a產生up(a),它們的唯一交集就是(1<<i)。注意這裡討論的第i位的i是從0開始索引的。讀者可以在稿紙上找若干數對進行實驗來加深印象。
有了上述定理,我們就不難意會insert函式和query函式的作用了。

#1. 機器浮點數的祕密,”巧奪天工”的完美例項,基於標準浮點數的快速開平方倒數演算法
這是一個公開的祕密,這是一個所有程式設計師得以欣賞的智慧之美。她在許多程式設計師的心目中高居“最美程式碼”的第一位,所有溢美之詞都無法表達他們所感受到的震撼。
一定會有許多人想在這裡貼這段程式碼,少年,來我這裡,我幫你揭開她神祕的面紗。

公式太多,貼圖講解。


相關文章