Skip to content

LISP - 列表作为集合 vs 哈希表作为集合

Lisp - 集合实现:列表 vs. 哈希表

Section titled “Lisp - 集合实现:列表 vs. 哈希表”

集合是独一无二的元素的集合。在 Common Lisp 中,有两种主要的方法来实现集合,每种方法都有独特的性能特点和使用场景:使用简单的列表或使用哈希表(hash table)。

表示集合最简单的方法是使用列表,其中你需要手动强制实现唯一性。Common Lisp 提供了辅助函数,使这变得容易且符合惯用法。

;; 创建一个列表形式的集合。
(defvar *my-list-set* '(1 2 3 4 5))
;; 使用 MEMBER 检查成员是否存在。
;; MEMBER 返回从找到的元素开始的列表剩余部分(一个“真”值),
;; 如果未找到则返回 NIL(假)。
(print (member 3 *my-list-set*)) ; ==> (3 4 5)
(print (member 6 *my-list-set*)) ; ==> NIL
;; 使用 ADJOIN 添加元素。
;; ADJOIN 仅在元素不存在时才添加它。
;; 它是非破坏性的,并返回一个新列表。
(let ((new-set (adjoin 6 *my-list-set*)))
(print new-set)) ; ==> (6 1 2 3 4 5)
;; 尝试添加一个已存在的元素不会有任何效果。
(let ((same-set (adjoin 3 *my-list-set*)))
(print same-set)) ; ==> (1 2 3 4 5)
;; 使用 REMOVE 删除元素。
(let ((removed-set (remove 3 *my-list-set*)))
(print removed-set)) ; ==> (1 2 4 5)
(3 4 5)
NIL
(6 1 2 3 4 5)
(1 2 3 4 5)
(1 2 4 5)
  • 实现简单:易于理解,并使用标准列表函数。
  • 保留插入顺序:adjoin 等函数会添加到列表前端,实际上创建了反向的插入顺序。这个顺序是可预测的。
  • 成员检查效率低(O(n)):要检查元素是否存在 (member),Lisp 可能需要扫描整个列表,这使得它在大集合中速度较慢。
  • 添加效率低(O(n)):添加元素 (adjoin) 需要首先进行成员检查,导致 O(n) 复杂度。
  • 适用于小集合:非常适合性能要求不高且重视简单性的小型集合。

对于高性能集合,哈希表是更优的选择。我们将集合的元素用作哈希表中的键。值是无关紧要的,通常设置为 t。

;; 创建一个空的哈希集合(我们必须为键比较指定一个“测试”)。
(defvar *my-hash-set* (make-hash-table :test 'equal))
;; 添加多个项的辅助函数。
(defun add-to-set (set &rest items)
(dolist (item items) (setf (gethash item set) t)))
;; 向哈希集合添加元素。
(add-to-set *my-hash-set* 1 2 3 4 5)
;; 使用 GETHASH 检查成员是否存在。
;; GETHASH 返回值和第二个布尔值,指示元素是否存在。
(multiple-value-bind (value present-p) (gethash 3 *my-hash-set*)
(format t "Value for 3: ~a, Present: ~a~%" value present-p))
(multiple-value-bind (value present-p) (gethash 6 *my-hash-set*)
(format t "Value for 6: ~a, Present: ~a~%" value present-p))
;; 使用 REMHASH 删除元素。
(remhash 1 *my-hash-set*)
(format t "Size of set after removing 1: ~a~%" (hash-table-count *my-hash-set*))
Value for 3: T, Present: T
Value for 6: NIL, Present: NIL
Size of set after removing 1: 4
  • 高效的成员检查(平均 O(1)):检查元素是否存在非常快,平均复杂度为常数时间。
  • 高效的添加/删除(平均 O(1)):添加和删除元素平均也是常数时间操作。
  • 不保留顺序:哈希表不保证其键的任何特定顺序。
  • 空间开销:哈希表的内存占用比列表高,特别是对于非常小的集合。
  • 需要良好的哈希函数:性能取决于哈希函数。对于自定义对象,可能需要定义自定义的哈希函数。
特性列表作为集合哈希表作为集合
成员检查慢 (O(n))快 (平均 O(1))
添加/删除慢 (O(n))快 (平均 O(1))
保留顺序是否
最佳用途小型、性能非关键集合大型或性能关键集合
实现非常简单,使用内置列表函数略复杂,需要哈希表设置