ヒープソートで使う二分ヒープ(binary heap)。副作用を使わないリスト版を作ろうと思ったが、いいアイデアがない。
結局、よくある配列版のアルゴリズムで妥協した…
;;; すみませんがトップレベルの変数を使いますよ、と
(defvar *heap*)
=> *heap*
(defvar *n*)
=> *n*
;;; 初期化関数
(defun init-heap (N)
(setf *heap* (make-vector N -1) *n* 0)
N)
=> init-heap
;;; 追加する関数(最小値が根になるよう並べる)
(defun heap-push (x)
(let ((i *n*) (i-parent))
(incf *n*)
;; 親と比べながら上へ移動
(while (and (> i 0)
(< x (elt *heap* (setf i-parent (/ (- i 1) 2)))))
(aset *heap* i (elt *heap* i-parent))
(setf i i-parent))
(aset *heap* i x)))
=> heap-push
;;; 取り出す関数
(defun heap-pop ()
(if (zerop *n*)
nil
(let ((ret (elt *heap* 0))
(i 0)
(i-child nil)
(v-child nil)
(v-last (elt *heap* (1- *n*))))
;; 子と比べながら下へ移動
(while (and (setf i-child (select-child-idx i))
(< (setf v-child (elt *heap* i-child)) v-last))
(aset *heap* i v-child)
(setf i i-child))
(aset *heap* i v-last)
(decf *n*)
ret)))
=> heap-pop
;;; 補助関数(i番目の要素の子を求める。子が2つある場合は値が小さいほうを選ぶ)
(defun select-child-idx (i)
(let ((cleft (+ (* 2 i) 1))
(cright (+ (* 2 i) 2)))
(cond ((and (< cright *n*)
(< (elt *heap* cright) (elt *heap* cleft)))
cright)
((< cleft *n*) cleft)
(t nil))))
=> select-child-idx
やっぱりElispで配列を扱うのはめんどくさい。変数のsetf, asetが本当につらい…
続いて動作確認。
;;; 10要素のヒープを作る (init-heap 10) => 10 ;; 乱数10個作って push する (mapcar 'heap-push (loop for i from 1 to 10 collect (random 100))) => (29 80 21 8 70 96 19 93 62 86) ;; 10回ぶん pop してみる (loop for i from 1 to 10 collect (heap-pop)) => (8 19 21 29 62 70 80 86 93 96)
ちゃんと小さい順に pop されているのでOKだが、もっときれいに書きたい。
Wikipediaの二分ヒープのページを見てたら「スレッド木」というキーワードが載っていたので今度調べる。あるいは "Purely Functional Data Structures" とかか。
0 件のコメント:
コメントを投稿