「ヒープ (heap)」は「半順序木 (partial ordered tree)」と呼ばれる木構造の一種で、普通は二分木を使った二分ヒープのことを指します。ヒープを利用すると、最小値をすぐに見つけることができ、新しくデータを挿入する場合も、高々要素の個数 (n) の対数 (log2 n) に比例する程度の時間で済みます。
ヒープは配列を使って簡単に実装することができます。また、二分木を使ったヒープの実装では Leftist Heap と Skew Heap というアルゴリズムがあります。今回のライブラリ heap には 3 種類のヒープが実装されています。アルゴリズムの詳しい説明は、以下の拙作のページをお読みくださいませ。
アーカイブファイル minlib.tar.gz をインストールする、または プログラムリスト にある以下の 4 つのファイルを、~/common-lisp/ 以下の適当なサブディレクトリ (たとえば heap など) に配置してください。
* (asdf:test-system :heap)
; compiling file ... 略 ...
----- test start -----
(HEAP-P (SETQ H (MAKE-HEAP)))
=> T OK
(HEAP-EMPTYP H)
=> T OK
(HEAP-COUNT H)
=> 0 OK
(DOLIST (X '(5 6 4 7 3 8 2 9 1 0) (HEAP-COUNT H)) (HEAP-PUSH H X))
=> 10 OK
(HEAP-EMPTYP H)
=> NIL OK
(HEAP-PEEK H)
=> 0 OK
(DO ((A NIL)) ((HEAP-EMPTYP H) (REVERSE A)) (PUSH (HEAP-POP H) A))
=> (0 1 2 3 4 5 6 7 8 9) OK
(HEAP-EMPTYP H)
=> T OK
(HEAP-COUNT H)
=> 0 OK
(HEAP-P (SETQ H (MAKE-HEAP KEY #'CAR ELT> #'<)))
=> T OK
(HEAP-EMPTYP H)
=> T OK
(HEAP-COUNT H)
=> 0 OK
(DOLIST
(X '((5 A) (6 B) (4 C) (7 D) (0 E) (3 F) (8 G) (2 H) (9 I) (1 J))
(HEAP-COUNT H))
(HEAP-PUSH H X))
=> 10 OK
(HEAP-EMPTYP H)
=> NIL OK
(HEAP-PEEK H)
=> (9 I) OK
(DO ((A NIL)) ((HEAP-EMPTYP H) (REVERSE A)) (PUSH (HEAP-POP H) A))
=> ((9 I) (8 G) (7 D) (6 B) (5 A) (4 C) (3 F) (2 H) (1 J) (0 E)) OK
(HEAP-EMPTYP H)
=> T OK
(HEAP-COUNT H)
=> 0 OK
(HEAP-P (SETQ H1 (MAKE-HEAP)))
=> T OK
(HEAP-P (SETQ H2 (MAKE-HEAP)))
=> T OK
(DOLIST (X '(2 4 0 8 6) (HEAP-COUNT H1)) (HEAP-PUSH H1 X))
=> 5 OK
(DOLIST (X '(3 1 5 9 7) (HEAP-COUNT H2)) (HEAP-PUSH H2 X))
=> 5 OK
(HEAP-COUNT (SETQ H3 (HEAP-MERGE H1 H2)))
=> 10 OK
(DO ((A NIL)) ((HEAP-EMPTYP H3) (REVERSE A)) (PUSH (HEAP-POP H3) A))
=> (0 1 2 3 4 5 6 7 8 9) OK
(LEFTIST-HEAP-P (SETQ H (MAKE-LEFTIST-HEAP)))
=> T OK
; 結果は heap と同じなので省略
(LEFTIST-HEAP-P (SETQ H (MAKE-LEFTIST-HEAP KEY #'CAR ELT< #'>)))
=> T OK
; 結果は heap と同じなので省略
(LEFTIST-HEAP-P (SETQ H1 (MAKE-LEFTIST-HEAP)))
=> T OK
(LEFTIST-HEAP-P (SETQ H2 (MAKE-LEFTIST-HEAP)))
=> T OK
; 結果は heap と同じなので省略
(SKEW-HEAP-P (SETQ H (MAKE-SKEW-HEAP)))
=> T OK
; 結果は leftist-heap と同じなので省略
----- test end -----
TEST: 72
OK: 72
NG: 0
ERR: 0
T
;;;
;;; sample_heap.lisp : サンプルプログラム
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(require :utils)
(use-package :utils)
(require :heap)
(use-package :heap)
;;; 簡単なテスト (その2)
(defun test-sub (h buff)
(let (s e)
(setq s (get-internal-run-time))
(dotimes (i (length buff))
(heap-push h (aref buff i)))
(do ((x (heap-pop h)))
((heap-emptyp h))
(when (> x (heap-peek h))
(error "test-pop error"))
(setf x (heap-pop h)))
(setq e (get-internal-run-time))
(format t "~f~%" (float (/ (- e s) internal-time-units-per-second)))))
(defun test (n)
(let ((buff (make-array n)))
(dotimes (i n) (setf (aref buff i) (random 1d0)))
(format t "----- heap test -----~%")
(test-sub (make-heap) buff)
(format t "----- leftist heap test -----~%")
(test-sub (make-leftist-heap) buff)
(format t "----- skew heap test -----~%")
(test-sub (make-skew-heap) buff)))
;;;
;;; ソート
;;;
;;; データの生成
(defun make-data (n)
(do ((n n (1- n))
(a nil))
((zerop n) a)
(push (random 1d0) a)))
;;; ヒープソート
(defun heap-sort (xs)
(let ((h (make-heap :elt> #'<)))
(dolist (x xs) (heap-push h x))
(do ((a nil))
((heap-emptyp h) a)
(push (heap-pop h) a))))
(defun leftist-heap-sort (xs)
(let ((h (make-leftist-heap :elt< #'>)))
(dolist (x xs) (heap-push h x))
(do ((a nil))
((heap-emptyp h) a)
(push (heap-pop h) a))))
(defun skew-heap-sort (xs)
(let ((h (make-skew-heap :elt< #'<)))
(dolist (x xs) (heap-push h x))
(do ((a nil))
((heap-emptyp h) a)
(push (heap-pop h) a))))
;;; イントロソート (quick sort + heap sort)
(defun intro-sort (xs &optional (depth 0))
(cond
((null (cdr xs)) xs)
((> depth 18)
(heap-sort xs))
(t
(multiple-value-bind
(ys zs)
(partition (lambda (x) (< x (car xs))) (cdr xs))
(append (intro-sort ys (1+ depth))
(cons (car xs) (intro-sort zs (1+ depth))))))))
(defun test-sort-sub (func data)
(let ((s (get-internal-run-time)) e)
(funcall func data)
(setf e (get-internal-run-time))
(float (/ (- e s) internal-time-units-per-second))))
(defun test-sort (n)
(let ((data (make-data n))
(fs (list #'heap-sort #'leftist-heap-sort #'skew-heap-sort #'intro-sort)))
(format t "----- random data -----~%")
(dolist (f fs)
(format t "~f " (test-sort-sub f data)))
(format t "~%----- sort data -----~%")
(setf data (sort data #'<))
(dolist (f fs)
(format t "~f " (test-sort-sub f data)))
(format t "~%----- reverse data -----~%")
(setf data (reverse data))
(dolist (f fs)
(format t "~f " (test-sort-sub f data)))))
* (load "sample_heap.lisp") T * (test 100000) ----- heap test ----- 0.226559 ----- leftist heap test ----- 0.302942 ----- skew heap test ----- 0.255162 NIL * (test 200000) ----- heap test ----- 0.506039 ----- leftist heap test ----- 0.725579 ----- skew heap test ----- 0.514637 NIL * (test 400000) ----- heap test ----- 1.159445 ----- leftist heap test ----- 1.671449 ----- skew heap test ----- 1.363209 NIL * (test-sort 100000) ----- random data ----- 0.236255 0.293327 0.232409 0.100099 ----- sort data ----- 0.296272 0.034602 0.264311 0.328634 ----- reverse data ----- 0.203358 0.359678 0.010844 0.241006 NIL * (test-sort 200000) ----- random data ----- 0.53167 0.751211 0.559291 0.239769 ----- sort data ----- 0.659985 0.059906 0.760565 0.833823 ----- reverse data ----- 0.438872 0.825665 0.021632 0.525916 NIL * (test-sort 400000) ----- random data ----- 1.177686 1.72241 1.4076 0.593926 ----- sort data ----- 1.473107 0.139375 1.808519 1.984675 ----- reverse data ----- 0.953541 2.044576 0.073662 1.847195 NIL 実行環境 : SBCL 2.1.11, Ubunts 22.04 (WSL2), Intel Core i5-6200U 2.30GHz
;;;
;;; heap.lisp : ヒープ
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(provide :heap)
(defpackage :heap (:use :cl))
(in-package :heap)
(export '(make-heap
heap-p
make-leftist-heap
leftist-heap-p
make-skew-heap
skew-heap-p
heap-push
heap-pop
heap-peek
heap-copy
heap-merge
heap-count
heap-clear
heap-emptyp))
;;; 総称関数の定義
(defgeneric heap-push (h x))
(defgeneric heap-pop (h))
(defgeneric heap-peek (h))
(defgeneric heap-copy (h))
(defgeneric heap-merge (h1 h2))
(defgeneric heap-count (h))
(defgeneric heap-clear (h))
(defgeneric heap-emptyp (h))
;;; 定義
(defstruct heap
(buff (make-array 256 :fill-pointer 0 :adjustable t))
(key #'identity)
(elt> #'>))
;;;
;;; 作業用関数
;;;
;;; 要素の比較
(defun elt> (h x y)
(funcall (heap-elt> h)
(funcall (heap-key h) (aref (heap-buff h) x))
(funcall (heap-key h) (aref (heap-buff h) y))))
;;; 要素の交換
(defun swap (buff x y)
(psetf (aref buff x) (aref buff y)
(aref buff y) (aref buff x)))
;;; ヒープの構築
(defun upheap (h n)
(do ((p (floor (1- n) 2) (floor (1- n) 2)))
((or (minusp p) (not (elt> h p n))))
(swap (heap-buff h) p n)
(setf n p)))
;;; ヒープの再構築
(defun downheap (h n nums)
(do ((c (+ (* n 2) 1) (+ (* n 2) 1)))
((>= c nums))
(when (and (< (1+ c) nums) (elt> h c (1+ c)))
(incf c))
(when (not (elt> h n c))
(return))
(swap (heap-buff h) n c)
(setf n c)))
;;;
;;; 操作関数の定義
;;;
;;; 空か
(defmethod heap-emptyp ((h heap))
(zerop (fill-pointer (heap-buff h))))
;;; 要素数
(defmethod heap-count ((h heap))
(fill-pointer (heap-buff h)))
;;; クリア
(defmethod heap-clear ((h heap))
(setf (fill-pointer (heap-buff h)) 0))
;;; データの追加
(defmethod heap-push ((h heap) x)
(vector-push-extend x (heap-buff h))
(upheap h (- (heap-count h) 1)))
;;; 先頭データの参照
(defmethod heap-peek ((h heap))
(if (heap-emptyp h)
(error "heap : heap is empty")
(aref (heap-buff h) 0)))
;;; データの取り出し
(defmethod heap-pop ((h heap))
(prog1
(heap-peek h)
(unless (heap-emptyp h)
(let ((buff (heap-buff h)))
(setf (aref buff 0) (vector-pop buff))
(downheap h 0 (fill-pointer buff))))))
;;; コピー
(defmethod heap-copy ((h heap))
(let ((newheap (make-heap ::key (heap-key h) :elt> (heap-elt> h))))
(dotimes (i (heap-count h) newheap)
(heap-push newheap (aref (heap-buff h) i)))))
;;; ヒープのマージ (遅い)
(defmethod heap-merge ((h1 heap) (h2 heap))
(unless (and (eq (heap-key h1) (heap-key h2))
(eq (heap-elt> h1) (heap-elt> h2)))
(error "heap-merge, can not merge"))
(let ((h3 (heap-copy h1)))
(dotimes (i (heap-count h2) h3)
(heap-push h3 (aref (heap-buff h2) i)))))
;;;
;;; Leftist Heap
;;;
;;; 節の定義, 終端は nil
(defstruct lnode item rank left right)
;;; マージ
(defun merge-lnode-sub (x hs1 hs2)
(let ((r1 (if (null hs1) 0 (lnode-rank hs1)))
(r2 (if (null hs2) 0 (lnode-rank hs2))))
(if (>= r1 r2)
(make-lnode :item x :rank (1+ r2) :left hs1 :right hs2)
(make-lnode :item x :rank (1+ r1) :left hs2 :right hs1))))
(defun merge-lnode (hs1 hs2 key elt<)
(cond
((null hs1) hs2)
((null hs2) hs1)
(t
(let ((x (lnode-item hs1))
(y (lnode-item hs2)))
(if (funcall elt< (funcall key x) (funcall key y))
(merge-lnode-sub x (lnode-left hs1) (merge-lnode (lnode-right hs1) hs2 key elt<))
(merge-lnode-sub y (lnode-left hs2) (merge-lnode (lnode-right hs2) hs1 key elt<)))))))
;;; 型の定義
(defstruct leftist-heap
(root nil) (size 0) (key #'identity) (elt< #'<))
;;; ヒープは空か
(defmethod heap-emptyp ((h leftist-heap))
(null (leftist-heap-root h)))
;;; 要素数を求める
(defmethod heap-count ((h leftist-heap))
(leftist-heap-size h))
;;; ヒープを空にする
(defmethod heap-clear ((h leftist-heap))
(setf (leftist-heap-root h) nil
(leftist-heap-size h) 0))
;;; コピー
(defmethod heap-copy ((h leftist-heap)) (copy-leftist-heap h))
;;; 挿入
(defmethod heap-push ((h leftist-heap) x)
(setf (leftist-heap-root h)
(merge-lnode (make-lnode :item x :rank 1 :left nil :right nil)
(leftist-heap-root h)
(leftist-heap-key h)
(leftist-heap-elt< h)))
(incf (leftist-heap-size h)))
;;; 最小値を取り出す
(defmethod heap-pop ((h leftist-heap))
(when (heap-emptyp h)
(error "Leftist Heap is empty"))
(let ((root (leftist-heap-root h)))
(prog1
(lnode-item root)
(setf (leftist-heap-root h)
(merge-lnode (lnode-left root)
(lnode-right root)
(leftist-heap-key h)
(leftist-heap-elt< h)))
(decf (leftist-heap-size h)))))
;;; 最小値を求める
(defmethod heap-peek ((h leftist-heap))
(when (heap-emptyp h)
(error "Leftist Heap is empty"))
(lnode-item (leftist-heap-root h)))
;;; ヒープのマージ
(defmethod heap-merge ((h1 leftist-heap) (h2 leftist-heap))
(let ((key (leftist-heap-key h1))
(elt< (leftist-heap-elt< h1)))
(unless (and (eq (leftist-heap-key h2) key)
(eq (leftist-heap-elt< h2) elt<))
(error "heap-merge, can not merge"))
(let ((h3 (make-leftist-heap :key key :elt< elt<)))
(setf (leftist-heap-root h3)
(merge-lnode (leftist-heap-root h1)
(leftist-heap-root h2)
key
elt<)
(leftist-heap-size h3)
(+ (heap-count h1) (heap-count h2)))
h3)))
;;;
;;; Skew Heap
;;;
;;; 節の定義, 終端は nil
(defstruct snode item left right)
;;; マージ
(defun merge-snode (hs1 hs2 key elt<)
(cond
((null hs2) hs1)
((null hs1) hs2)
(t
(let ((x (snode-item hs1))
(y (snode-item hs2)))
(if (funcall elt< (funcall key x) (funcall key y))
(make-snode :item x
:left (merge-snode (snode-right hs1) hs2 key elt<)
:right (snode-left hs1))
(make-snode :item y
:left (merge-snode (snode-right hs2) hs1 key elt<)
:right (snode-left hs2)))))))
;;; 型の定義
(defstruct skew-heap
(root nil) (size 0) (key #'identity) (elt< #'<))
;;; キューは空か?
(defmethod heap-emptyp ((h skew-heap))
(null (skew-heap-root h)))
;;; 要素数を求める
(defmethod heap-count ((h skew-heap))
(skew-heap-size h))
;;; キューを空にする
(defmethod heap-clear ((h skew-heap))
(setf (skew-heap-root h) nil
(skew-heap-size h) 0))
;;; コピー
(defmethod heap-copy ((h skew-heap)) (copy-skew-heap h))
;;; 挿入
(defmethod heap-push ((h skew-heap) x)
(setf (skew-heap-root h)
(merge-snode (make-snode :item x :left nil :right nil)
(skew-heap-root h)
(skew-heap-key h)
(skew-heap-elt< h)))
(incf (skew-heap-size h)))
;;; 最小値を取り出す
(defmethod heap-pop ((h skew-heap))
(when (heap-emptyp h)
(error "Skew Heap is empty"))
(let ((root (skew-heap-root h)))
(prog1
(snode-item root)
(setf (skew-heap-root h)
(merge-snode (snode-left root)
(snode-right root)
(skew-heap-key h)
(skew-heap-elt< h)))
(decf (skew-heap-size h)))))
;;; 最小値を求める
(defmethod heap-peek ((h skew-heap))
(when (heap-emptyp h)
(error "Skew Heap is empty"))
(snode-item (skew-heap-root h)))
;;; ヒープのマージ
(defmethod heap-merge ((h1 skew-heap) (h2 skew-heap))
(let ((key (skew-heap-key h1))
(elt< (skew-heap-elt< h1)))
(unless (and (eq (skew-heap-key h2) key)
(eq (skew-heap-elt< h2) elt<))
(error "heap-merge, can not merge"))
(let ((h3 (make-skew-heap :key key :elt< elt<)))
(setf (skew-heap-root h3)
(merge-snode (skew-heap-root h1)
(skew-heap-root h2)
key
elt<)
(skew-heap-size h3)
(+ (heap-count h1) (heap-count h2)))
h3)))
リスト : heap.asd (defsystem :heap :description "Heap (プライオリティキュー)" :version "0.1.0" :author "Makoto Hiroi" :license "MIT" :depends-on () :in-order-to ((test-op (test-op :heap_tst))) :components ((:file "heap")))
;;;
;;; heap_tst.lisp : heap のテスト
;;;
;;; Copyright (C) 2023 Makoto Hiroi
;;;
;;; Released under the MIT license
;;; https://opensource.org/license/mit/
;;;
(provide :heap_tst)
(defpackage :heap_tst (:use :cl :mintst :heap))
(in-package :heap_tst)
(export '(test))
(defvar h nil)
(defvar h1 nil)
(defvar h2 nil)
(defvar h3 nil)
(defun test-sub1 ()
(run (heap-emptyp h) t)
(run (heap-count h) 0)
(run (dolist (x '(5 6 4 7 3 8 2 9 1 0) (heap-count h)) (heap-push h x)) 10)
(run (heap-emptyp h) nil)
(run (heap-peek h) 0)
(run (do ((a nil))
((heap-emptyp h) (reverse a))
(push (heap-pop h) a))
'(0 1 2 3 4 5 6 7 8 9))
(run (heap-emptyp h) t)
(run (heap-count h) 0))
(defun test-sub2 ()
(run (heap-emptyp h) t)
(run (heap-count h) 0)
(run (dolist (x '((5 a) (6 b) (4 c) (7 d) (0 e)
(3 f) (8 g) (2 h) (9 i) (1 j))
(heap-count h))
(heap-push h x))
10)
(run (heap-emptyp h) nil)
(run (heap-peek h) '(9 i))
(run (do ((a nil))
((heap-emptyp h) (reverse a))
(push (heap-pop h) a))
'((9 I) (8 G) (7 D) (6 B) (5 A)
(4 C) (3 F) (2 H) (1 J) (0 E)))
(run (heap-emptyp h) t)
(run (heap-count h) 0))
(defun test-sub3 ()
(run (dolist (x '(2 4 0 8 6) (heap-count h1)) (heap-push h1 x)) 5)
(run (dolist (x '(3 1 5 9 7) (heap-count h2)) (heap-push h2 x)) 5)
(run (heap-count (setq h3 (heap-merge h1 h2))) 10)
(run (do ((a nil))
((heap-emptyp h3) (reverse a))
(push (heap-pop h3) a))
'(0 1 2 3 4 5 6 7 8 9)))
(defun test ()
(initial)
(run (heap-p (setq h (make-heap))) t)
(test-sub1)
(run (heap-p (setq h (make-heap :key #'car :elt> #'<))) t)
(test-sub2)
(run (heap-p (setq h1 (make-heap))) t)
(run (heap-p (setq h2 (make-heap))) t)
(test-sub3)
;;
(run (leftist-heap-p (setq h (make-leftist-heap))) t)
(test-sub1)
(run (leftist-heap-p (setq h (make-leftist-heap :key #'car :elt< #'>))) t)
(test-sub2)
(run (leftist-heap-p (setq h1 (make-leftist-heap))) t)
(run (leftist-heap-p (setq h2 (make-leftist-heap))) t)
(test-sub3)
;;
(run (skew-heap-p (setq h (make-skew-heap))) t)
(test-sub1)
(run (skew-heap-p (setq h (make-skew-heap :key #'car :elt< #'>))) t)
(test-sub2)
(run (skew-heap-p (setq h1 (make-skew-heap))) t)
(run (skew-heap-p (setq h2 (make-skew-heap))) t)
(test-sub3)
(final))
リスト : heap_tst.asd (defsystem :heap_tst :description "test for heap" :version "0.1.0" :author "Makoto Hiroi" :license "MIT" :depends-on (:mintst :heap) :components ((:file "heap_tst")) :perform (test-op (o s) (symbol-call :heap_tst :test)))