2013年2月10日 星期日

[C Programming] 線性篩法-延伸(中)

(延伸問題)
2. 此地的記憶體用量明顯比上一題的傳統篩法大許多。
    可否將它作節省呢?
 首先我們不要prev[]只留下next[],那對於next[i]來說,
    如果i-1沒有被刪除,那i的上一個是i-1,但若i-1已經被刪除了,
    我們用next[i-1]來存原來的prev[i],換句話說,
    i的上一個就是i-1與next[i-1]中值較少的那一個。
    請問,還有什麼邊界條件要考慮,並且將此概念寫成程式。
    這個技巧在第一個問題之下能否適用呢?

個人解答:
以下的解法非常非常非常(沒錯,就是三個非常)容易搞混,
所以請務必仔細的讀每一句話,並且留意:
""和"索引值"的差別!
首先,在第一個延伸問題的前提下,這個技巧仍然可以適用,
只不過,index的基礎移動變為2了。
原本的的核心概念是:
當自己被刪掉時,告訴自己的上一個數:"你的下一個變成XX",
再告訴自己的下一個數:"你的上一個變成OO"。
這樣子就結束了。
但在現在,我們只剩下next可以放"下一個",
所以在上一個數的存放的時候,我們要利用已被"刪除"的數的空間。
整體而言,當一個數被刪掉的時候,我們應該要做三件事:

1. 把next[x]值,存給前一個index的next[]。
2. 在index為next[x]的位置的前一個(-2),存入前一個位置的index。
3. 將自己的位置,也存入前一個位置的index。

在這個機制下,要如何判斷一個數x的前面一個是哪位的方式,
就是先找往前2單位的index,
看它裡面的值是否比index大,較大代表未刪去,較小代表已刪去。
前者的狀況表示x-2即是x的前一個,後者的狀況,
x-2裡面存放的,就是x的前一個數的index。

很難理解是嗎?
我們舉幾個例子:
考慮3、5、7、9、11,一開始是
index  3  5  7  9    11
value  5  7  9  11  13
9被刪去的時候,我們做上述的三件事:
1. 告訴前一個數,你的next[]要改存11了。
    9-2是7,next[7]=9,表示它目前是沒被刪除的
    所以我們把11存到next[7]裡面。
2. 在9的下一個位置next[9]的前一個(-2)位置,存入前一個位置(7)的index。
    也就是在11-2=9的地方存入7。
3. 將自己的位置,也存入前一個位置的index。(也就是在9的地方存入7)
這當中其實3可以不用做結果也一樣
會這麼做只是因為在定義上來說,我們希望只要是已刪去的數,
儲存的值都要比自己小。
做完以後的樣子會變成:

index  3  5  7    9    11
value  5  7  11  7    13

再舉個例子,考慮31、33、35、37、39,
篩去的順序會是33->39->35,依序做完的狀況如下:

index    31   33   35   37   39
value    33   35   37   39   41

after 1  35   31   37   39   41  
after 2  35   31   37   41   37
after 3  37   31   31   41   37
過程不再贅述,就是仍然按照上面三個步驟進行就是了。
或許有人會說,看起來2和3是做相同的事情呀!
你每次繞來繞去,最後不就是把自己改掉就好了,
那我們不做2直接做3就好啦!

2和3兩個情況,在大多數是相同的,但還是有例外:
以下的刪去順序是先27再25,因為3比較早篩。

index    23   25   27   29
value    25   27   29   31

after 1  25   29   25   31
after 2  29   23   23   31
發現了嗎?因為29要知道自己的前面是23的話,
我們必須告訴27,要27裡面放23進去,
這時候29-2是27,而不是25,如果我們只改掉25裡面的值,
就會產生問題。

除此以外還有一個很重要的邊界條件:
在原問題中,我們使用NULL做為邊界。
在C語言中,NULL其實就是指0的意思
但記得我們的迴圈是怎麼判斷繼續的嗎?
沒錯,就是乘積<n。
使用NULL的狀況下,
在我們這樣子把值指定給"刪去"後的index時,
會發生我們會讀到NULL的狀況,
這時候mult會一直等於0,也就一直跳不出去
而且偵測到NULL就break的方式,
也會受限於0的值會傳遞到奇怪的地方而有時會出錯。
怎麼辦呢?
既然用0不行,那我們將其設成MAXSIZE+1如何?
如此一來,當跑到最後面,迴圈肯定能夠跳出來。
當然缺點是能跑的值會更加受限於資料型態的最大範圍
但這個方法本來就會受限於乘法隨數字增大,
而令計算時間變長的問題,其實也不能跑多大的數字XD

判斷前一個值的時候,我們使用
if(LEFT(x) < next[LEFT(x)])   當中LEFT(x)可以定義成x-2或x-1,
端看今天我們有沒有先行將2的倍數處理掉。
如果前一位(i-2)的index比所存的value小,代表沒被刪掉,
所以i-2就是i的前一個。
否則,就是i-2所存的value值,是i的前一個的index。

Code的部分,我們將prev相關的通通除去,
而在最後的走訪print出所有質數的時候,
將條件改成:for(i = 2; i!= MAXSIZE+1; NEXT(i))即可。
為了方便一點閱讀,多設了一個變數tmp,
讓if和else執行的狀況看起來更有條理。

Code:
/* Coded by Desolve(雨蕭) 2013.2.9
   REMOVE先判斷前一個的位置後,做三件事情:
   1. 把next[x]值,存給前一個index的next[]。
   2. 在index為next[x]的位置的前一個(-2),存入前一個位置的index。
   3. 將自己的位置,也存入前一個位置的index。
   第3點可以不用做,這只是為了概念上較容易懂;
   但注意2和3不完全等義,所以不能只做3而不做2。
*/
#define REMOVE(x) {          \
 if(LEFT(x) < next[LEFT(x)]){     \
  next[LEFT(x)] = next[x]; \
  next[LEFT(next[x])] = LEFT(x); \
  next[x] = LEFT(x);  \
 }else{    \
  tmp = next[LEFT(x)];   \
  next[tmp] = next[x]; \
  next[LEFT(next[x])] = tmp; \
  next[x] = tmp;    \
 }\
}
#define LEFT(x) x-2

#define INITIAL(n) { unsigned long i;    \
    for(i=3; i<=n; i+=2)      \
     next[i]=i+2;    \
    next[2]=3, next[n]=MAXSIZE+1;  \
  } 

沒有留言:

張貼留言