我們經常使用的數的進製為
「常數進製」,即
始終逢p進1
。例如,
p進製數
k可表示為
k = a0*p^0 + a1*p^1 + a2*p^2 + ... + an*p^n
(其中0 <= ai <= p-1
),它可以表示任何乙個自然數。
對於這種常數進製表示法,以及各種進製之間的轉換大家應該是很熟悉的了,但大家可能很少聽說
變進製數。這裡我要介紹一種特殊的變進製數,它能夠被
用來實現全排列的hash函式,
並且該hash函式能夠實現完美的防碰撞和空間利用(不會發生碰撞,且所有空間被完全使用,不多不少)。這種全排列
hash
函式也被稱為全排列數化技術。下面,我們就來看看這種變進製數。
設從第n位到第1位進製依次是pn,pn-1...p2,p1,則任何乙個數可以表示為
下面證明上式的正確性:
假設變進製數的第m位am=pm,要進製,那麼
即正確的向高位進1。這說明該變進製數能夠正確進製,從而是一種合法的計數方式。
舉個例子:
假設從第4位到第1位進製依次是4,9,6,8,k=1024
則可表示成k=2*(9*6*8) + 3*(6*8)+2*8+0=2*432+3*48+2*8=1024
以下內容是**
我們考查這樣一種變進製數:第1位逢
2進1,第
2位逢3進
1,……,第n位逢
n+1進
1。它的表示形式為
k = a1*1
!+ a2*2
!+ a3*3
!+ ... + an*n
!(其中
0 <= ai <= i
),也可以擴充套件為如下形式(因為按定義
a0始終為
0),以與
p進製表示相對應:
k = a0*0
!+ a1*1
!+ a2*2
!+ a3*3
!+ ... + an*n
!(其中
0 <= ai <= i
)。(後面的變進製數均指這種變進製數,且採用前一種表示法)
先讓我們來考查一下該變進製數的進製是否正確。假設變進製數k的第
i位ai為
i+1,需要進製,而
ai*i
!=(i+1)*i
!=1*(i+1)
!,即正確的向高位進
1。這說明該變進製數能夠正確進製,從而是一種合法的計數方式。
接下來我們考查
n位變進製數
k的性質:(1
)當所有位
ai均為
i時,此時
k有最大值
max[k] = 1*1
!+ 2*2
!+ 3*3
!+ ... + n*n
!= 1
!+ 1*1
!+ 2*2
!+ 3*3
!+ ... + n*n
!- 1
= (1+1)*1
!+ 2*2
!+ 3*3
!+ ... + n*n
!- 1
= 2!
+ 2*2
!+ 3*3
!+ ... + n*n
!- 1
= ...
= (n+1)!-1
因此,n位k進製數的最大值為(n+1)!-1。(2
)當所有位
ai均為
0時,此時
k有最小值0。
因此,n
位變進製數能夠表示0到
(n+1)!-1
的範圍內的所有自然數,
共(n+1)!個。
在一些狀態空間搜尋演算法中,我們需要快速判斷某個狀態是否已經出現,此時常常使用
hash
函式來實現。其中,有一類特殊的狀態空間,它們是由全排列產生的,比如
n數碼問題。對於
n個元素的全排列,共產生
n!個不同的排列或狀態。下面將討論如何使用這裡的變進製數來實現乙個針對全排列的
hash
函式。從數的角度來看,全排列和變進製數都用到了階乘。如果我們能夠用0到
n!-1這
n!個連續的變進製數來表示
n個元素的所有排列,那麼就能夠把全排列完全地數化,建立起全排列和自然數之間一一對應的關係,也就實現了乙個完美的
hash
函式。那麼,我們的想法能否實現呢?答案是肯定的,下面將進行討論。
假設我們有b0,
b1,b2,
b3,...,bn共
n+1個不同的元素,並假設各元素之間有一種次序關係
b0。對它們進行全排列,共產生
(n+1)
!種不同的排列。對於產生的任一排列c0,
c1,c2,
..,cn,其中第
i個元素ci(
1 <= i <= n
)與它前面的
i個元素構成的
逆序對的個數為di(
0 <= di <= i
),那麼我們得到乙個逆序數序列d1,d2,...,dn(0 <= di <= i)。這不就是前面的
n位變進製數的各個位麼?於是,我們用
n位變進製數
m來表示該排列:
m = d1*1! + d2*2! + ... + dn*n!
因此,每個排列都可以按這種方式表示成乙個n位變進製數。下面,我們來考查
n位變進製數能否與
n+1個元素的全排列建立起一一對應的關係。由於n
位變進製數能表示
(n+1)
!個不同的數,而
n+1個元素的全排列剛好有
(n+1)
!個不同的排列,且每乙個排列都已經能表示成乙個
n位變進製數。如果我們能夠證明任意兩個不同的排列產生兩個不同的變進製數,那麼我們就可以得出結論:
★ 定理1——n+1個元素的全排列的每乙個排列對應著乙個不同的n位變進製數。
對於全排列的任意兩個不同的排列p0,
p1,p2,
...,
pn(排列p)和
q0,q1,
q2,...,
qn(排列
q),從後往前查詢第乙個不相同的元素,分別記為pi和
qi(0 < i <= n)。(
1)如果
qi > pi
,那麼,
如果在排列q中
qi之前的元素x與
qi構成逆序對,即有
x > qi
,則在排列p中
pi之前也有相同元素
x > pi
(因為x > qi
且qi > pi
),即在排列p中
pi之前的元素x也與
pi構成逆序對,所以
pi的逆序數大於等於
qi的逆序數。又qi與
pi在排列
p中構成
pi的逆序對,所以
pi的逆序數大於
qi的逆序數。(2
)同理,如果
pi > qi
,那麼qi
的逆序數大於
pi的逆序數。
因此,由(
1)和(
2)知,排列
p和排列
q對應的變進製數至少有第
i位不相同,
即全排列的任意兩個不同的排列具有不同的變進製數。至此,定理
1得證。
計算n個元素的乙個排列的變進製數的演算法大致如下(時間複雜度為
o(n^2)):
int factorials[8] = ;
template size_t permutationtonumber(const t permutation, int n)
// factorials[j]儲存著j!
result += count * factorials[j];
}return result;
}
說明:(1
)由於n
!是乙個很大的數,因此一般只能用於較小的n。
(2)有了計算排列的變進製數的演算法,我們就可以使用乙個大小為
n!的陣列來儲存每乙個排列的狀態,使用排列的變進製數作為陣列下標,從而實現狀態的快速檢索。如果只是標記狀態是否出現,則可以用一位來標記狀態。
一種隨機數生成演算法
隨機數生成類 class randnumber randnumber randnumber unsigned long s 0 else unsigned short randnumber random unsigned long n double randnumber frandom unsign...
回文數的另一種解法
題目描述 判斷乙個整數是否是回文數。回文數是指正序 從左向右 和倒序 從右向左 讀都是一樣的整數。示例 1 輸入 121 輸出 true 示例 2 輸入 121 輸出 false 解釋 從左向右讀,為 121 從右向左讀,為 121 因此它不是乙個回文數。示例 3 輸入 10 輸出 false 解釋...
親和數是一種古老的數
題目 實現100000以內的相親數對輸出 相親數 除自身以外的約數和,比如 220 1 2 4 5 10 11 20 22 44 55 110 284,284 1 2 4 71 142 220,所以220和284構成相親數對。輸出格式 從小到大每行一對相親數對,中間乙個空格隔開。看到這個題目,第乙個...