尤拉計劃287題:四分樹編碼(一個簡單的壓縮演算法)

lt發表於2017-05-09

四分樹編碼使我們能夠將一個2N×2N的黑白圖片表示為一個位元串(0和1)。該位元串可以從左到右解讀為:
◦第一個位元位描述完整的2N×2N區域;
◦“0”表示對當前區域進行分割:
將當前的2n×2n區域分割為4個2n-1×2n-1區域,
接下來的位元位按照左上、右上、左下、右下的順序分別描述這四個區域;
◦“10”表示當前區域只包含黑色畫素;
◦“11”表示當前區域只包含白色畫素。

考慮如下的4×4圖象(彩色標記意味著需要進行分割的位置):

enter image description here

這個影像可以用多個不同的位元串表示,例如:
“001010101001011111011010101010”,長度為30,或者
“0100101111101110”,長度為16,後者是描述這幅圖象的最短位元串。

對於一個正整數N,記DN是按照如下染色方案得到的2N×2N圖象:
◦左下角的畫素其座標為x = 0,y = 0,
◦如果(x - 2N-1)2 + (y - 2N-1)2 ≤ 22N-2,那麼該座標的畫素為黑色,
◦否則該座標的畫素為白色。

描述D24的最短位元串長度是多少?

用julia來模擬一下

N=2

#b=Array{Array}(N);
#b[i]=zeros(Int,N)

b=[zeros(Int,2^N) for i in 1:2^N]

function setcolor_D(N)
 N1=N-1
 for x in 0:2^N-1
  for y in 0:2^N-1
   if (x-2^N1)^2+(y-2^N1)^2≤2^2N1
    b[x+1][y+1]=1
   end
  end
 end
end

b

輸出

4-element Array{Array{Int32,1},1}:
 [0, 0, 0, 0]
 [0, 0, 0, 0]
 [0, 0, 0, 0]
 [0, 0, 0, 0]

4-element Array{Array{Int32,1},1}:
 [0, 0, 1, 0]
 [0, 1, 1, 1]
 [1, 1, 1, 1]
 [0, 1, 1, 1]

#N=3
julia> b
8-element Array{Array{Int32,1},1}:
 [0, 0, 0, 0, 1, 0, 0, 0]
 [0, 0, 1, 1, 1, 1, 1, 0]
 [0, 1, 1, 1, 1, 1, 1, 1]
 [0, 1, 1, 1, 1, 1, 1, 1]
 [1, 1, 1, 1, 1, 1, 1, 1]
 [0, 1, 1, 1, 1, 1, 1, 1]
 [0, 1, 1, 1, 1, 1, 1, 1]
 [0, 0, 1, 1, 1, 1, 1, 0]

從公式和圖形看,黑色部分是以(2n-1,2n-1)為圓心,2n-1為半徑的圓盤和矩陣的重合部分。
上述陣列的陣列其實並非適合這道題的資料結構,更好的辦法是用二維陣列,如下程式碼畫出示例的矩陣(上下倒置)。

c=ones(Int,4,4);
c[1,1]=0;
c[2,1]=0;
c[1,2]=0;
c[2,2]=0;
c[3,3]=0;
c[4,4]=0;
#結果
julia> c
4××4 Array{Int32,2}:
 0  0  1  1
 0  0  1  1
 1  1  0  1
 1  1  1  0

然後編寫如下的程式碼

function D(b,N)
 N1=N-1
 for x in 0:2^N-1
  for y in 0:2^N-1
   if (x-2^N1)^2+(y-2^N1)^2<=2^2N1
    b[x+1,y+1]=1
   end
  end
 end
 return b
end

function code(a,len)
if a==ones(Int,len,len) return "10" end
if a==zeros(Int,len,len) return "11" end
half=div(len,2)
s="0";
s=s * code(view(a,1:half,1:half),half)
s=s * code(view(a,1:half,half+1:half*2),half)
s=s * code(view(a,half+1:half*2,1:half),half)
s=s * code(view(a,half+1:half*2,half+1:half*2),half)
return s
end

N=2

c=zeros(Int,2^N,2^N)
D(c,N)
code(c,2^N)

function p287(N)
c=zeros(Int,2^N,2^N)
D(c,N)
return length(code(c,2^N))
end

code函式針對示例的輸出

julia> code(c,4)
"0111010011101011"

julia> length(code(c,4))
16
#對於題目要求的生成矩陣
julia> p287(2)
30
julia> map(x->(x,p287(x)),1:10)
10-element Array{Tuple{Int32,Int32},1}:
 (1,9)
 (2,30)
 (3,86)
 (4,212)
 (5,499)
 (6,1052)
 (7,2242)
 (8,4552)
 (9,9382)
 (10,18790)

但更大的N,記憶體溢位了,需要用另外的方法。
將大矩陣分解成小矩陣,2*2的四方格,如果不全同色需要9位(=1*0佔的1位+4*每個1*1單元格的2位),如果同色,需要2位,更大的2n*2n矩陣,純色只要2位,不純色的同上處理。下圖顯示了n=2~4的分解。
enter image description here
分別用2個陣列儲存0和不同尺寸的矩陣個數。當N=5時,資料如下:

julia> f0
2-element Array{Int32,1}:
 71
  0

julia> [(i,f[i]) for i in 1:2^N if f[i]>0]
4-element Array{Tuple{Int32,Int32},1}:
 (1, 124)
 (2, 61)
 (4, 25)
 (8, 4)

把5~11的結果做成一個表格,得出以下規律,最大的矩陣個數等於4,最小的是2n+2-4,0的個數等於(子矩陣個數之和-1)/3。
enter image description here

相關文章