第2種スターリング数をO(nk/2)で求める関数
第2種スターリング数S(n,k)は、n個の区別できる要素をk個の集合(区別できない箱)にまとめる場合の組合せの数を表す。
主な注意点は以下の通り。
- n=0のとき、0組に分ける場合が1通りで、それ以外が0通りになる。
- n>0のとき、0組に分けられないので、k=0では0通りになる。
- n<kのとき、空の集合はないものとして扱うので0通りになる。
数の求め方は、
- まず1つを取り出すと(例えばA)、残りの分け方は、1つもAと同じ組にしないか(甲)、Aと同じ組にすることを考えるか(乙)のどちらかである。
- 甲の場合、kを1つ減らして考えられるので、残りの組合せはS(n-1,k-1)となる。
- 乙の場合、Aを気にせずにS(n-1,k)を考えるが、Aがどのkに入るかはk通り考えられるので、組合せはkS(n-1,k)通り。
- したがって、S(n,k)=S(n-1,k-1)+kS(n-1,k)の漸化式が得られる。
この漸化式と注意点をまとめれば求められる。詳しくは組合せ数学の教科書を参考して欲しい。
漸化式通りに第2種スターリング数を求める関数が作れ、それが1つ前の記事の関数になるが、多重再帰になっているので非常に遅い。
表にすると、数はn行目にn+1個のみ現れ、1つ上の行*1が分かれば求められるので、nの小さい列から計算すれば簡単に求められる。ということで、次の関数を書いた。
(defun stirling-2nd-kind-line (&optional lst) (do ((k 0 (1+ k)) (b lst (cdr b)) (x 0 (car b)) (l nil (append (list (+ x (* k (car b)))) l))) ((null b) (append (reverse l) (list 1))))) (defun stirling-2nd-kind (n k) (do ((i -1 (1+ i)) (b nil (stirling-2nd-kind-line b))) ((<= n i) (or (elt b k) 0))))
計算量はO(2^n)からO(nk/2)に抑えられている(と思う)。ただ、表を作るときは同じ計算が多くなって煩わしいので、表を作る専用の関数にするか、次のような、計算コストの大きい行ごとにメモ化する関数を作るとよい(そのためにn行を求める関数を分けてある)。
(let ((tb (make-hash-table :size 47)) (max 0)) (defun stirling-2nd-kind-m (n k) (while (<= max n) (setf (gethash max tb) (stirling-2nd-kind-line (gethash (1- max) tb))) (incf max)) (or (elt (gethash n tb) k) 0)))
*1:もっと細かく言えばk列まで。